00012-medium-chainable-options

Back

type Chainable<T = {}> = {
  option: <K extends string, V>(key: K, value: V) => Chainable<Omit<T, K> & Record<K, V>>
  get(): T
}

Solution by nupthale #33579

the answer is inspired from #598

type Chainable<U={}> = {
    option<TK extends string,TV>(k:Exclude<TK,keyof U>,v:TV):Chainable<U&{
        [P in Exclude<TK,keyof U>]: TV // double guarantee
    }>
    get():U
}

Solution by whatever8080 #33015

// 你的答案
type Chainable<T extends Record<string,any> = {}> = {
  option<K extends string,V extends any>(key: K extends keyof T?never: K, value: V): Chainable< {
    [key in keyof T as key extends K?never:key]:T[key]
  }& {
    [key in K]:V
  }>
  get(): T
}

Solution by walker-hzx #32934

// 解答をここに記入
type Chainable<T extends object = {}> = {
  option: <Key extends string, Value>(
    key: Key extends keyof T
      ? never
      : Key,
    value: Value
  ) => Chainable<Omit<T, Key> & Record<Key, Value>>;
  get: () => T;
}

Solution by Yasunori-aloha #32813

type Chainable<T = {}> = {
  option<K extends PropertyKey, V>(key: K extends keyof T ? never : K, value: V): Chainable<Omit<T, K> & Record<K, V>>
  get(): T
}
  1. T = {} gives a default value so type arguments is not required
  2. K needs to be a PropertyKey in order to be passed to Record, you can use string here since there is only string in test cases
  3. K extends keyof T ? never : K: K is never type when key already exists in Chainable, any type except never itself can pass to never type
  4. From test case 2 & 3 we know that same key needs to be overwritten, so we Omit the old key every time we merge the new key
const result2 = a
  .option('name', 'another name')
  // @ts-expect-error
  .option('name', 'last name')
  .get()

const result3 = a
  .option('name', 'another name')
  // @ts-expect-error
  .option('name', 123)
  .get()

type Expected2 = {
  name: string
}

type Expected3 = {
  name: number
}

I have a question though. Why overwrite duplicate key if you wants it to be an error?

Solution by MarvinXu #32713

interface Result {
  foo: number
  name: string
  bar: {
    value: string
  }
}

type Chainable = {
    option: (arg1: string, arg2: number | string | { value: string }) => Chainable
    get: () => Result
}

declare const config: Chainable

const result = config
  .option('foo', 123)
  .option('name', 'type-challenges')
  .option('bar', { value: 'Hello World' })
  .get()

Solution by ZhulinskiiDanil #32670

type Chainable<T = {}> = {
  option: <NewK extends string, NewV extends unknown>(
    key: NewK extends keyof T ? never : NewK, 
    value: NewV,
  ) => Chainable<Omit<T, NewK> & Record<NewK, NewV>>;
  get(): T;
}

没做出来,这题有点难。思考方向完全错了,没有想到递归,这不太应该。

NewK extends keyof T ? never : NewK 用来过滤当 NewK 已经存在 T 中的时候,转为 never,从而达到报错的目的。

其实我觉得到这里就可以了,反正已经有报错了。不太需要后面的那个 Omit

Solution by mistkafka #32639

type Chainable<T = {}> = {
  option<R extends string, U>(key: R extends keyof T ? never : R, value: U): Chainable<{ [P in Exclude<keyof T, R>]: T[P] } & Record<R, U>>
  get(): T
}

Solution by nivenice #32367

// your answers

type Chainable<T = {}> = {
  option: <K extends string, V>(key: K extends keyof T ?
    V extends T[K] ? never : K
    : K, value: V) => Chainable<Omit<T, K> & Record<K, V>>
  get: () => T
}

declare const config: Chainable

const result = config
  .option('foo', 123)
  .option('name', 'type-challenges')
  .option('bar', { value: 'Hello World' })
  .get()



Solution by laqudee #32348

처음 풀이: option 메서드의 파라미터에 조건부 타입을 사용할 생각을 하지 못하고 정적인 타입만을 이용해 억지로 풀어냄

type Chainable<Result extends Record<string, unknown> = {}> = {
  option<Key extends (Extract<keyof Result, Key> extends never ? string : never), Value>(key: Key, value: Value):
    Key extends never 
      ? never
      : Chainable<{ [key in keyof Result]: Result[key] } & Record<Key, Value>>
  get(): Result
}

개선된 풀이

type Chainable<Result = {}> = {
  option<Key extends string, Value>(key: Key extends keyof Result ? never : Key, value: Value):
    Chainable<Omit<Result, Key> & Record<Key, Value>>
  get(): Result
}

알아갈 것: 메서드의 파라미터 타입에 조건부 타입을 적용시킬 생각을 하자

Solution by dev-hobin #32220

type Chainable<T = {}> = {
  option: <K extends string, V>(
    key: K extends keyof T ? never : K,
    value: V
  ) => Chainable< Omit<T, K> & Record<K, V> >
  get(): T
}

We define Chainable type with a generic type T that extends from an object. The method get returns the generic T itself and the option method types as following:

Solution by joyanedel #32151

// your answers
type Chainable<T = {}> = {
  option<Key extends string, Value>(key: Key, value: Value): Chainable<Omit<T, Key> & Record<Key, Value>>
  get(): T
}

Solution by trinhvinhtruong96 #32054

// your answers
type Chainable<R = {}> = {
  option<K extends string, V>(key: K extends keyof R ? never : K, value: V): Chainable<Omit<R, K> & Record<K, V>>
  get(): R
}

Solution by pea-sys #31907

declare const config: Chainable

const result = config
  .option('foo', 123)
  .option('name', 'type-challenges')
  .option('bar', { value: 'Hello World' })
  .get()

// expect the type of result to be:
interface Result {
  foo: number
  name: string
  bar: {
    value: string
  }
}

type Chainable<T = {}> = {
  option: <S extends string, V extends unknown>(s: S, v: V) => Chainable<T & Record<S, V>>
  get(): T 
}

Solution by anovicenko74 #31768

type Chainable<T = {}> = {
  option<K extends string, V>(key: K, value: V): Chainable<Omit<T, K> & {[key in K]: V}>
  get(): T
}

Solution by kai-phan #31620

// your answers

type Chainable<T extends object = {}> = {
  option<K extends string, V>(
    key: K,
    value: V
  ): Chainable<{ [Key in keyof T]: T[Key] } & Record<K, V>>;
  get(): {
    [Key in keyof T]: T[Key];
  };
};


Solution by AhmedRagabKamal #31487

type Chainable<T extends Record<string, any> = {}> = {
  option<K extends string, V>(
    key: K extends keyof T ? never : K,
    value: V
  ): Chainable<Omit<T, K> & { [Key in K]: V }>
  get(): T
}

Solution by LcsGa #31424

type Chainable<T = {}> = {
  option: <Param extends string, Value>(
    param: Param,
    value: Value,
  ) => Chainable<
    {
      [key in keyof T as key extends Param ? never : key]: T[key];
    } & {
      [key in Param as key extends keyof T
        ? T[key] extends Value
          ? never
          : key
        : key]: Value;
    }
  >;
  get: () => T;
};

Solution by arnokay #31398

type Chainable<R = {}> = {
  option<K extends string, V>(key: K extends keyof R ? never : K, value: V): Chainable<Omit<R, K> & Record<K, V>>
  get(): R
}

Solution by jinyoung234 #31392

type Chainable<R = object> = {
  option<K extends string, V>(
    key: K extends keyof R ? never : K,
    value: V
  ): Chainable<Omit<R, K> & Record<K, V>>;
  get(): R;
};

Solution by vipulpathak113 #31258

// 你的答案
type Chainable<U = {}> = {
  option<K extends string, T>(key: K extends keyof U ? never : K, value: T): Chainable<Omit<U, K> & Record<K, T>>
  get(): U
}

Solution by TRIS-H #31226

type Chainable<Obj = {}> = {
  option<k extends string,v extends any>(key: k extends keyof Obj ? never: k, value: v): Chainable<Omit<Obj, k> & Record<k, v>>
  get(): Obj

Solution by eward957 #31208

SUPER EZ

// your answers
type Chainable<T extends object = {}> = {
  option<KeyType extends string, ValueType>(key: Exclude<KeyType, keyof T>, value: ValueType): Chainable<Omit<T, KeyType> & Record<KeyType, ValueType>>
  get(): T
}

Solution by kakasoo #31178

This is based on someone elses answer that didn't quite pass the tests and was over complicated.

type Chainable<R = object> = {
  option<K extends  string, V>(key: K extends keyof R ? never : K, value: V): Chainable<Omit<R, K> & Record<K, V>>
  get(): R
}

Solution by timrutter #31040

type Chainable<T = {}> = {
  option: <K extends string, V>(key: K extends keyof T ?
    V extends T[K] ? never : K
    : K, value: V) => Chainable<Omit<T, K> & Record<K, V>>
  get: () => T
}

Solution by MyeonghoonNam #30846

type Chainable<T extends { [ a: string ]: any } = {}> = {
  option<K extends string, V>(key: K extends keyof T ? never : K, value: V): Chainable<{ [ A in keyof T as A extends K ? never : A ]: T[ A ] } & { [ key in K ]: V }>,
  get(): { [ A in keyof T as A ]: T[ A ] }
}

Solution by vprokashev #30791

type Chainable<T = {}> = {
  option<K extends string, V>(key: Exclude<K, keyof T>, value: V): Chainable<Omit<T,K>& Record<K,V>>
  get(): T
}

Solution by her0707 #30570

type Chainable<T extends { [key: string]: any } = { [key: string]: any }> = {
  option<K extends string, V>(key: K, value: Readonly<V>): Chainable< T & Record<K, Readonly<V>> >
  get(): T
}

declare const config: Chainable

const result = config
  .option('foo', 123)
  .option('name', 'type-challenges')
  .option('bar', { value: 'Hello World' })
  .get()

// expect the type of result to be:
interface Result {
  foo: number
  name: string
  bar: {
    value: string
  }
}

Solution by rxMATTEO #30078

type Chainable<T extends {}> = {
  option<K extends string, V>(key: K, value: V): T & {[k in K]: V}
  get(): { [P in keyof T]: T[P]} 
}

Solution by somasekimoto #29925

type Chainable<Result = {}> = {
  option<TKey extends string, TValue>(key: TKey, value: TValue): Chainable<Omit<Result, TKey> & { [key in TKey]: TValue }>
  get(): Result
}

Solution by gstcarv #29911