#Ts ignoring conditional type infering

20 messages · Page 1 of 1 (latest)

mellow fulcrum
#

I have some fairly dense type definitions for a chainable, promiselike/thenable object in TS.

My issue is that depending on the chained methods my then method should return a single or array or the final promise result, but even though I conditionally type it based on a variable called "many" passed though the internal methods, the results allways comes as a union of Promise<Result | Result[]> instead of the Promise<Result> or Promise<Result[]>.

Some help would be greately appreciated!

I do realize I might be leaving out some context, please feel free to answer or DM me if you need some extra information.

Here are the relevant files. If necessary I will upload my current package so you can play with it, since from the shared scripts you will not be able to test it locally.

(These scripts are not complete)

Files:

  1. The type definition file: https://gist.github.com/Adise0/bf5571dc71395838619cf7c724990317
  2. The class that generates this return object: https://gist.github.com/Adise0/a9d59e8f6e2a8d223cd285ee86bb52cc
  3. The class that calls the generation of the object: https://gist.github.com/Adise0/4f4fd773af25e3c1a77a269fff357ab0

As you can see I expect that, when calling the find method, the result should be an array of the SQLResult class, and when calling findById or findByPrimaryKey the result should be a single object.

In code this is working as expected, I do get my single/array results according to the designed implementation. TS however does not want to colaborate.

Gist

Return object type definition. GitHub Gist: instantly share code, notes, and snippets.

Gist

GitHub Gist: instantly share code, notes, and snippets.

Gist

GitHub Gist: instantly share code, notes, and snippets.

strong thistle
#

sounds like you have a conditional in a signature?

#

you've shared your code in a format that's just.. not great for support
some tips for future reference:

  • you can put multiple files in a single gist
  • don't share your entire project, share the parts that are relevant as an mcve
    with a minimal example, you can embed code directly (please not as a file) or for this server in particular, a link to the ts playground that will be embedded by the bot
#

that aside; conditionals over generics in signatures don't really work well. you might want overloads instead?

mellow fulcrum
#

Thanks for the information, will take it into account and I will edit the message in a second to incorporate them.

As for your possible asnwer, I have tried overloads in the findThen and acterThen methods, but Ts allways grabs the final type from the ReturnObjectSchema, how would I overload the then callback? Or should I just create a higher "wrapper" type for the then and just join it with the rest of the method signatures?

#

Also this is nowhere near the entire SQL package, let alone the entire project. I shared the parts I thought were relevant since these files reference each other, at the end of the day the result comes from the model, wuhich takes it from the object constructor class which takes it from the type definition file. I'll try to shorten these files to have only the relevant pasts howerver.

strong thistle
#

idk, i don't even know what the function in question is

mellow fulcrum
#

Yeah it's a bit complicated

strong thistle
#

hence why an mcve is vital

mellow fulcrum
#

Would you be willing to hop on a call so I can explain? If not I completely understand

strong thistle
#

and in reducing the problem you can often stumble into the solution yourself once it's not obscured by irrelevant details

mellow fulcrum
#

I'll try to create an mcve now

strong thistle
mellow fulcrum
#

Alright, I'ma try creating the mcve, thanks for your answers

mellow fulcrum
#

Hello everyone. I still have not created that mcve since I don't think it's the completely correct path here, to do so I'd have to re-write a lot of stuff which might obscure the problem I'm facing in my code. However I think I can narrow down the affected code.

Basically I have a thenable object that allows it's methods to be chained.
this object's type should set the then result type to an array or a single object based on a boolean called many.

I've also, written a small example to clearly see what my issue is:

type ExampleMethods = "foo" | "bar";
interface ExampleResult {
  prp1: string;
  prp2: number;
}

type ExampleReturnObject<
  Many extends boolean,
  Schema extends object,
  Omits extends ExampleMethods = never
> = Omit<
  {
    [Key in ExampleMethods | "then"]: Key extends "foo"
      ? () => ExampleReturnObject<Many, Schema, Omits | Key>
      : Key extends "bar"
      ? () => ExampleReturnObject<Many, Schema, Omits | Key>
      : Key extends "then"
      ? Many extends true
        ? () => Schema[]
        : () => Schema
      : never;
  },
  Omits
>;

const exampleValue: ExampleResult = {
  prp1: "hello",
  prp2: 1234,
};

const getExampleObject = <Many extends boolean>(many: Many) => {
  const exampleObject: ExampleReturnObject<Many, ExampleResult> = {
    foo: () => getExampleObject(many),
    bar: () => getExampleObject(many),
    then: (many ? () => [exampleValue] : () => exampleValue) as Many extends true
      ? () => ExampleResult[]
      : () => ExampleResult,
  };
  return exampleObject;
};

const result = getExampleObject(true).bar();
const result2 = getExampleObject(false).bar();

EDIT: Put both examples and re-named types for better clarity

#

What's wierd now is that, I'm not sure if I've left out a critical part, but this example does not behave equaly. This shows the return type as allways being a single "exampleObject". I've tried conditinal return, overloads of the then functions and I can't find the solution,. what's worst is I can't replicate the original "issue" ether.

From my experience typescript can have some issues with complicated types evewn if the core strucutre is the same and show inconsistent results, that's my best guess as for why the real issue and this example don't show themselfs as the same, even if what's failing to work as expected is failing in both

#

It's also interesting that if I don't explicitly state the type of the then method i get an error like the following one:

Type '(() => ExampleResult[]) | (() => ExampleResult)' is not assignable to type 'Many extends true ? () => ExampleResult[] : () => ExampleResult'.
  Type '() => ExampleResult[]' is not assignable to type 'Many extends true ? () => ExampleResult[] : () => ExampleResult'.ts(2322)
(property) then: Many extends true ? () => ExampleResult[] : () => ExampleResult

Even if you can clearly see this should be infeered since Many is indeed a boolean that's derived from the function parameters:

const getExampleObject = <Many extends boolean>(many: Many) => {
  const exampleObject: ExampleReturnObject<Many, ExampleResult> = {
    foo: () => getExampleObject(many),
    bar: () => getExampleObject(many),
    then: (many ? () => [exampleValue] : () => exampleValue)
  };
  return exampleObject;
};
mellow fulcrum
#

Nevermind, I managed to acomplish what I wanted in the example (I forgor the awaits 💀 )

#

@strong thistle Turns out my issue was tat ts was int infereing the type across the entire function chain that ran in order to GET the return object, the error was not in the return object itself (At least I don't think, i've changed it so much I may have accidentally fixed it along the way).
My issue was in fact, on the other files, where I did not extract and pass the "many" type so ts only read it as boolean, not as the passed boolean!
I'm glad I was able to resolve it. Thank you so much for trying to help anyway