1

There is single interface IStorage where every key has two types:

interface IStorage { key: boolean | CustomStore<boolean>, key2: number | CustomStore<number> } type CustomStore<T> = { value: T, init: (value: T) => void; set: (value: T) => void; } 

The class LiveStorage have defaultStorage (first type of IStorage) and storage (second):

class LiveStorage<D = { [key: string]: any }, S = { [key: string]: CustomStore<any> }> { private defaultStorage: D storage: S constructor(defaultStorage: D) { this.defaultStorage = defaultStorage as D this.storage = {} as S } } 

When create instance, key has both types:

let liveStorage = new LiveStorage<IStorage, IStorage>({}) liveStorage.storage.key // boolean | CustomStore<boolean> 

Are there ways to key will have only CustomStore<boolean> by default? With single interface and without (liveStorage.storage.key as CustomStore<boolean>). I know that is only compile time, just want to get suggestion from IDE without (x as type) every time.

2
  • what typescript version you are using? Commented Aug 13, 2023 at 16:09
  • 1
    @AshotAleqsanyan typescript 5.1.6, vs code Commented Aug 13, 2023 at 16:12

1 Answer 1

1

Essentially you want defaultStorage to be of a "base" key-value type, and then you want storage to be a transformed version of that type. For each property key K of the base type T, you want to take the property value type T[K] and map it to CustomStore<T[K]>. So we can define a mapped type that does this:

type CustomStorageFor<T> = { [K in keyof T]: CustomStore<T[K]> }; 

For your example then, you wouldn't want to use your IStorage type directly, with unions of base and mapped properties. Instead, you would define IStorage to be the base type

interface IStorage { key: boolean key2: number } 

And then you get the CustomStorage version by mapping it:

type CustomStorageForIStorage = CustomStorageFor<IStorage>; /* type CustomStorageForIStorage = { key: CustomStore<boolean>; key2: CustomStore<number>; } */ 

Now your LiveStorage class can be generic in just the base type T, and storage will be given type CustomStorageFor<T>:

class LiveStorage<T extends object> { private defaultStorage: T storage: CustomStorageFor<T>; constructor(defaultStorage: T) { this.defaultStorage = defaultStorage this.storage = {} as CustomStorageFor<T>; // you need to implement this } } 

Now when you call the LiveStorage constructor, the compiler will infer the type argument T to be that of the constructor argument (and I've constrained T to the object type so that you'll get an error if you try to pass in a string or something):

const defaultStorage: IStorage = { key: true, key2: 1 }; let liveStorage = new LiveStorage(defaultStorage); // let liveStorage: LiveStorage<IStorage> liveStorage.storage.key // CustomStore<boolean> 

In fact you don't even need the IStorage type if you don't want to give it a name; the compiler will happily synthesize an equivalent object type from the constructor argument:

let liveStorage = new LiveStorage({ key: true, key2: 1 }); // let liveStorage: LiveStorage<{ key: boolean; key2: number; }> liveStorage.storage.key // CustomStore<boolean> 

Either way will work.

Playground link to code

Sign up to request clarification or add additional context in comments.

3 Comments

How to modify CustomStorageFor in order to fix error Type 'string' cannot be used to index type 'CustomStorageFor<T>'. It occurs when I try init this.storage[key] = this.createStore(key)
Looks like you have a followup question; you should consider posting a new question if you can't figure it out. Comments sections on existing questions aren't great places to explore followups; at a minimum I need to see a minimal reproducible example (e.g., where is key coming from) and that really belongs in a real question post. Also it's quite possible that the followup issue isn't really directly related to this one, and is just the next thing you ran into. I'd guess you have a for...in or Object.keys() related typing question and that's not really relevant to mapped types.
I couldn't fix, added new question

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.