Extension loader pattern only recognizes ext2 instead of ext1 and 2

Hey, can someone help? posted the question on stack overflow as well, but I am hard stuck stuck, this is the code:
interface ExtensionArgs {
name: string;
}

class Extension<Args extends ExtensionArgs, AUI extends AutumnUI<any>> {
#name: Args['name'];
get name(): Args['name'] {
return this.#name;
}
#autumnUI: AUI;
get autumnUI() {
return this.#autumnUI;
}
constructor(args: Args, aUI: AUI) {
this.#name = args.name;
this.#autumnUI = aUI;
}
}

type Merge<A, B> = {
[K in keyof A | keyof B]: K extends keyof A & keyof B
? A[K] | B[K]
: K extends keyof B
? B[K]
: K extends keyof A
? A[K]
: never;
};

type AutumnUIBase = {
extensions: {};
};

type AutumnUI<AUI extends AutumnUIBase> = {
extensions: AUI['extensions'];
add: <
const AU extends AutumnUI<AutumnUIBase>,
const callBack extends (arg: AU) => Extension<ExtensionArgs, AU>,
const NewExtension extends ReturnType<callBack>,
>(
e: callBack,
) => {
extensions: Merge<AU['extensions'], Record<NewExtension['name'], NewExtension>>;
add: AutumnUI<AU>['add'];
};
};

declare const createAutumnUI: AutumnUI<{
extensions: {};
}>;

function ExtensionLoader<
const AUI extends AutumnUI<AutumnUIBase>,
const AUIC extends () => AUI,
const EARGS extends ExtensionArgs,
>(unusedAUI: AUIC, arg: EARGS): (autumnUI: AUI) => Extension<EARGS, AUI> {
return (autumnUI: AUI) => new Extension(arg, autumnUI);
}

const ext1 = ExtensionLoader(() => createAutumnUI, { name: 'ext1' });

const ext2 = ExtensionLoader(() => createAutumnUI.add(ext1), { name: 'ext2' });
//It only shows the command from ext2 and not ext1

const step1 = createAutumnUI.add(ext1).extensions.ext1;
const step2_0 = createAutumnUI.add(ext1).add(ext2).extensions.ext1; // should be avaliable here as well
const step2_1 = createAutumnUI.add(ext1).add(ext2).extensions.ext2;
interface ExtensionArgs {
name: string;
}

class Extension<Args extends ExtensionArgs, AUI extends AutumnUI<any>> {
#name: Args['name'];
get name(): Args['name'] {
return this.#name;
}
#autumnUI: AUI;
get autumnUI() {
return this.#autumnUI;
}
constructor(args: Args, aUI: AUI) {
this.#name = args.name;
this.#autumnUI = aUI;
}
}

type Merge<A, B> = {
[K in keyof A | keyof B]: K extends keyof A & keyof B
? A[K] | B[K]
: K extends keyof B
? B[K]
: K extends keyof A
? A[K]
: never;
};

type AutumnUIBase = {
extensions: {};
};

type AutumnUI<AUI extends AutumnUIBase> = {
extensions: AUI['extensions'];
add: <
const AU extends AutumnUI<AutumnUIBase>,
const callBack extends (arg: AU) => Extension<ExtensionArgs, AU>,
const NewExtension extends ReturnType<callBack>,
>(
e: callBack,
) => {
extensions: Merge<AU['extensions'], Record<NewExtension['name'], NewExtension>>;
add: AutumnUI<AU>['add'];
};
};

declare const createAutumnUI: AutumnUI<{
extensions: {};
}>;

function ExtensionLoader<
const AUI extends AutumnUI<AutumnUIBase>,
const AUIC extends () => AUI,
const EARGS extends ExtensionArgs,
>(unusedAUI: AUIC, arg: EARGS): (autumnUI: AUI) => Extension<EARGS, AUI> {
return (autumnUI: AUI) => new Extension(arg, autumnUI);
}

const ext1 = ExtensionLoader(() => createAutumnUI, { name: 'ext1' });

const ext2 = ExtensionLoader(() => createAutumnUI.add(ext1), { name: 'ext2' });
//It only shows the command from ext2 and not ext1

const step1 = createAutumnUI.add(ext1).extensions.ext1;
const step2_0 = createAutumnUI.add(ext1).add(ext2).extensions.ext1; // should be avaliable here as well
const step2_1 = createAutumnUI.add(ext1).add(ext2).extensions.ext2;
after the
createAutumnUI.add(ext1).add(ext2).extensions
createAutumnUI.add(ext1).add(ext2).extensions
It should be aware of the ext1 and ext2 extensions. but somehow during the .add it loses the type information and only knows about the latest added extension. How can I modify the code so its aware of both extensions? Playground: https://tsplay.dev/N7D2Gm (67 Lines) I tried many things and I am expecting that it should work together, the reason why I, in the ExtensionLoader have an unusedAUI is so that while you define you extension you can "depent" on a specific set of extensions to be loaded and build upon them. Later when it actually then loads the extension it will only "build" it once.
TS Playground - An online editor for exploring TypeScript and JavaS...
The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
1 Reply
zerquix18
zerquix18•3mo ago
I gave this to Claude 3.5 Sonnet which gave me this solution after 3 iterations:
interface ExtensionArgs {
name: string;
}

class Extension<Args extends ExtensionArgs, AUI extends AutumnUI<any>> {
readonly name: Args['name'];
constructor(args: Args, aUI: AUI) {
this.name = args.name;
}
}

type Merge<A, B> = {
[K in keyof A | keyof B]: K extends keyof A & keyof B
? A[K] | B[K]
: K extends keyof B
? B[K]
: K extends keyof A
? A[K]
: never;
};

type AutumnUIBase = {
extensions: Record<string, Extension<any, any>>;
};

type AutumnUI<AUI extends AutumnUIBase> = {
extensions: AUI['extensions'];
add: <
const callBack extends (arg: AutumnUI<AUI>) => Extension<ExtensionArgs, AutumnUI<AUI>>,
const NewExtension extends ReturnType<callBack>,
>(
e: callBack,
) => AutumnUI<{
extensions: Merge<AUI['extensions'], Record<NewExtension['name'], NewExtension>>;
}>;
};

declare const createAutumnUI: AutumnUI<{
extensions: {};
}>;

function ExtensionLoader<
const AUI extends AutumnUI<AutumnUIBase>,
const EARGS extends ExtensionArgs,
>(arg: EARGS): (autumnUI: AUI) => Extension<EARGS, AUI> {
return (autumnUI: AUI) => new Extension(arg, autumnUI);
}

const ext1 = ExtensionLoader({ name: 'ext1' });
const ext2 = ExtensionLoader({ name: 'ext2' });

const step1 = createAutumnUI.add(ext1).extensions.ext1;
const step2 = createAutumnUI.add(ext1).add(ext2).extensions;
interface ExtensionArgs {
name: string;
}

class Extension<Args extends ExtensionArgs, AUI extends AutumnUI<any>> {
readonly name: Args['name'];
constructor(args: Args, aUI: AUI) {
this.name = args.name;
}
}

type Merge<A, B> = {
[K in keyof A | keyof B]: K extends keyof A & keyof B
? A[K] | B[K]
: K extends keyof B
? B[K]
: K extends keyof A
? A[K]
: never;
};

type AutumnUIBase = {
extensions: Record<string, Extension<any, any>>;
};

type AutumnUI<AUI extends AutumnUIBase> = {
extensions: AUI['extensions'];
add: <
const callBack extends (arg: AutumnUI<AUI>) => Extension<ExtensionArgs, AutumnUI<AUI>>,
const NewExtension extends ReturnType<callBack>,
>(
e: callBack,
) => AutumnUI<{
extensions: Merge<AUI['extensions'], Record<NewExtension['name'], NewExtension>>;
}>;
};

declare const createAutumnUI: AutumnUI<{
extensions: {};
}>;

function ExtensionLoader<
const AUI extends AutumnUI<AutumnUIBase>,
const EARGS extends ExtensionArgs,
>(arg: EARGS): (autumnUI: AUI) => Extension<EARGS, AUI> {
return (autumnUI: AUI) => new Extension(arg, autumnUI);
}

const ext1 = ExtensionLoader({ name: 'ext1' });
const ext2 = ExtensionLoader({ name: 'ext2' });

const step1 = createAutumnUI.add(ext1).extensions.ext1;
const step2 = createAutumnUI.add(ext1).add(ext2).extensions;
and it seems to work from my editor and the playground. I'll confess I don't fully understand what's going on here but maybe this working version (if it's indeed working) is enough to unblock you 🙂
No description
Want results from more Discord servers?
Add your server