microsoft / kiota-typescript

TypeScript libraries for Kiota-generated API clients.
https://aka.ms/kiota/docs
MIT License
32 stars 25 forks source link

Singletons are not singletons in Typescript #1244

Open andreaTP opened 2 weeks ago

andreaTP commented 2 weeks ago

In Apicurio Registry we are attempting to use the client SDK generated for Typescript ( link ).

More specifically, we are following the consolidated pattern of:

Although we started running into issues: Screenshot from 2024-06-21 15-46-31

After much research and debugging we found out that ParseNodeFactoryRegistry.defaultInstance doesn't always refer to the same object, and, depending on: "from which module are you invoking it" you get a different instance of it ( there are multiple resources in SO that refer to this e.g. ).

Workaround

Running kiota generate --serializer none --deserializer none and registering manually the serializers and deserializers in the end module, i.e.:

const pnfr = new ParseNodeFactoryRegistry();
const json = new JsonParseNodeFactory();
...

pnfr.contentTypeAssociatedFactories.set(json.getValidContentType(), json);
...

new FetchRequestAdapter(new AnonymousAuthenticationProvider(), pnfr, ...);

Solution?

This bug is hard to discover and track down, and it easily compromises the "getting started" experience. There are a few options on how to solve it, but I'm happy to listen to a proposal from someone more expert than me in TS.

andreaTP commented 2 weeks ago

cc. @EricWittmann

baywet commented 2 weeks ago

Thanks for bringing this up. It's most likely caused because the bundled application actually has two copies of the abstractions, and yes that's a pain. Hard to investigate for people new to kiota and/or to JavaScript.

As part of making the clients truly portable, we're planning to build bundle packages which would do two things:

andreaTP commented 2 weeks ago

It's most likely caused because the bundled application actually has two copies of the abstractions

Haven't realized! Why?

register the serialization providers through a derived request adapter

Do you mean attaching the defaultInstance to the adapter? In this case, everything will flow from the instantiation of the adapter. Would this work even in case instantiation happens in a factory distributed along the SDK? I mean in a separate module.

baywet commented 2 weeks ago

Haven't realized! Why?

If you run npm list @microsoft/kiota-abstractions (or equivalent with your package manager) it's likely that it'll list two versions that haven't been deduped.

Do you mean attaching the defaultInstance to the adapter?

No I meant basically moving the code that's in the client now to register the providers to this new adapter's constructor. And change it a little bit so it'd:

  1. accept providers passed to the constructor by the developer, and use those if any.
  2. if none, use what's in the default instance if any.
  3. if none, use the providers to register, and set that for the default instance at the same occasion.

Would this work even in case instantiation happens in a factory distributed along the SDK? I mean in a separate module.

I'm not sure I follow the question?