'Typescript: Having problems with mapping nested Record values wrapped in vue3 reactive proxy

UPDATED VERSION:

I have several implementations of a generic class, which is wrapped in the vue3 composition api function "reactive", which is basically a proxy which keeps track of any updates:

export declare function reactive<T extends object>(target: T): UnwrapNestedRefs<T>;

How can I implement a class so that the constructor takes an object of such implementations, and have such inner types unwrapped as parameter of a callback function which is the second argument of the constructor?

Like this:

const campaignWrapped = reactive(new MyWrapper<Campaign>(campaign));
const batchesWrapped = reactive(new MyWrapper<Batch[]>(batches));

new MyClass({ first: campaignWrapped, second: batchesWrapped }, (states) => {
    states.first; // Should be of type Campaign
    states.second; // Should be of type Batch[]
  });

It seems like it's not possible to derive the type from the proxy object, because extends MyClass<infer U> ? ... doesn't match anymore. The only way around seems to be adding an interface to the Class and check for this interface instead. Even if I unwrap UnwrapNestedRefs first and infer it's type, I still can't get the real inner type.


OLD VERSION: (simplified)

(just for the context, as my wrong attempt might help someone to understand the difference between union and intersect - which wasn't clear to me)

I want to get the unboxed types in a callback, which have to be derived from generic types.

So far so good. It seems to work if I have only one record entry.

But as soon as there is another, it fails.

The resulting type looks ALMOST correct:

Record<"first", Campaign> | Record<"second", Batch[]>

How can I tell typescript to use intersect and not union? So the type would become:

Record<"first", Campaign> & Record<"second", Batch[]>

(BONUS QUESTION: Is it possible to get rid of the LSW interface and use the type of the class directly? For me the extends LazyStateWrapper<infer U> ? ... didn't work.)

Working example in typescriptlang.org playground here

And here is the code inline:

type UnwrapInner<X, S extends string> = X extends LSW<infer X>
  ? Record<S, X>
  : never;

type UnwrapLazyStateWrapper<T> = T extends Record<infer S, infer U> ? (S extends string ? UnwrapInner<T[S], S> : never) : never;

export class TransientLazyState<X, T extends object> {
  constructor(states: X, cb: (states: UnwrapLazyStateWrapper<X>) => T | undefined) {
    console.log('todo');
  }
}

interface LSW<T extends object> {
  getStateClone(): T | undefined;
}

export class LazyStateWrapper<T extends object> implements LSW<T> {
  private obj: T;
  constructor(instance: T) {
    this.obj = instance;
  }

  getStateClone(): T | undefined {
    return {} as T;
  }
}

type Campaign = { id: number; foo: string };
type Batch = { id: number; bar: string };

const campaign: Campaign = { id: 123, foo: 'x' };
const batches: Batch[] = [];

const campaignWrapped = new LazyStateWrapper(campaign);
const batchesWrapped = new LazyStateWrapper(batches);

const test1 = new TransientLazyState({ first: campaignWrapped }, (states) => {
    const xy = states.first; // Yay. Works! Is of type "Campaign"
    console.log(xy);
    return undefined;
  });

const test2 = new TransientLazyState({ first: campaignWrapped, second: batchesWrapped }, (states) => {
    const xy = states.first; // Doesn't work anymore, once there are more records. But to me the type looks ALMOST correct. ERROR: Property 'first' does not exist on type 'Record<"first", Campaign> | Record<"second", Batch[]>'. Property 'first' does not exist on type 'Record<"second", Batch[]>'.(2339)
    console.log(xy);
    return undefined;
  });


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source