Skip to content

Instantly share code, notes, and snippets.

@ackvf
Last active November 17, 2023 14:49
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ackvf/de21847e78083034252961d550963579 to your computer and use it in GitHub Desktop.
Save ackvf/de21847e78083034252961d550963579 to your computer and use it in GitHub Desktop.
TypeScript type toolbelt

TypeScript type toolbelt

other gists
🔗 TS/JS utility functions
🔗 React utils


  • Deep Object Dot Path - Type support for deep objects with dot notation.

    declare function get(path: DotBranch<Obj>)
    const v = get('a.y.z') // string
  • type guards

    declare const something: A | B
    narrow<A>(something, v => !!v.propertyOnAOnly)) ? something /* type A */ : something /* type B */
    
    // @deprecated use `satisfies` operator
    is<readonly string[]>()(['a',  2 ] as const) // ERROR 2 is not assignable to string
  • global ambient declarations and type utils

    PickAs<A, keyof A> | PickAs<A, `${keyof A}:{string}`> - Pick and rename keys

    type A = { a: string, b: number, c: boolean, d: any }
    type B = PickAs<A, 'a:x' | 'b:y' | 'c'>  // { x: string; y: number; c: boolean; }
  • ts helper functions

 

/*
Stack Overflow
https://stackoverflow.com/a/67779282/985454
TS Playground
https://www.typescriptlang.org/play?#code/MYewdgzgLgBCBGArGBeGBvAsAKBjAhgFwY555EwDaA5PtQDQzXzUC69pZAHsdV1w054AnsSy4yeAF68pUwRLwBfDhJWd4YoTACuxKACcdAU1XKzMYMQAsAJlVKCES+GgwA9O5gAeALQwoAAsAS2dQmGCAWwAHEAMofDBYADM4gOFo4xgAMhgAN3wAGxMIsGTjA2MwYGMcHGCkiuT8GpgAeQAhACkSCQpxSQpoAwaAc0pWbR4YYbHtUV7JaWJZsFHtJU5NiU1Fsj0YeBAQQuNErc4rGDAdSPgKnG267BxPGF8Pz6-vn9+--4BgKBwJBQNeXkAvBuAcR2YAAFQr4YQwADuwSCARCzlBv2eUAyWQa5QMADUiiZnGhDCU3hDABI7EMAuzu6CBZArFLIAAzxmRAyTgSA5ARApSJhxA6O5WVybJMjDSkpgHM6XUFUGF4EKSMJFXSmQgOAVbSQqBFFVJ7Ocxi4UCqABNnFSsgB+XXGXn85DEZUwZ4G-EwAAiAEYTQHxR0DIlgIFvEbEIxmoUWQA+DxeABEwHTMAAPjB0-gAHRcbN5guF4SFqSl-PwQs6dN+zKB2yh8OR6oxuOppZ4N4Fmvp+CDrO5-NFktj8vCQdFyvVqd1htNrIAJRDaDDUAAKvjY0hGMHkyuYKvW5vxbvMvv40w6MeXthbcZgAjKjBkjpqlBguAYKNjCgbxYRgK0bTAe1A3bKMuyQZNkwACmifAgmIWEAEpiC3K9jBvRhYVTN5t0xCJnAgQI4iCRJbQ-NJqDAEBn2oHBn1ffB30-b9fzAf9AIAOUY3CQLAu1nC3CMYLwpgGKY+CkJQwI0MwqCdz3ON8IfVi3yyTjgB-P8AKgAAZM5kmA0DrVElSJM7KTqFOfBkmoOTkNQuFlOwtSDzhB8cFASBYDZPA0EMhDaELOh0L81xYBkrIQv4wSwuoKLsH8twHL5BLjNM5LUvBd5-AhQABHYhQAxHcKn4YEZcqAgMJFAgqUwMXCVqoAK7FOq67qet6n5fUfIjGpUmAcMqvqJsmqacUfBUty6EAGm8ABBCzwMg1ZRkYDo1qszbUzQVaRIg5xqGoGAXR24gOQAEnQZalELO6OiUDlcX9WERkiNFgjyYwAGljGECBY12k6YGWsBhDjF8oAOvZKH+0oYAAayB902lYL1EdYMHIMh6GkFhi7rmMP6DBgYh-seSg0eEDHJlm-0A2MYxokB4HQeO-GoZhvT4YGKgkYaVH0b5THsf+3HuecAm+dgF02hxvHnEqfBbQ1JFEmECYSbAMmKipynSfJmm6YZgbez7LwiphbdjDcIJwk67RJAVWEkbQT6oh+v6OZB7trfzLNXbIObPcDVn2aBgO4Kt-t8EHYdnit10YGJDd2iQSgAyl0O8AVYlzxgABVMBuO3EAAElGgMFk9O47wM4fVPC4AZlDKP-ab2we17fsZ3ztNxumj4Brm6DbLaFWId5om9MYABZQSZ6OE4zh4vN6ME86t8y87KSMYwDs4Zfnxnx0x23pjtBdcSO2jATn1jFuyCwyfoxMxyX8t+Obequ2DtYBOyxNiIeCojIAAY2xQBstGG8fd+4ZkTouEcs5izoJnFOOcVYk71mzMPPwMwKLxECNRWiFN76SXUtJHer9ewQMzlQqe3lr7GGcsPAcqCpyjjLBOTBmDcGLnwUHIhIDSIEEKIUGAsQIAQGCPAU4oskSuUCPqRQSwIHF2YfAmh9lTIcP7Lw8cGDsEViEWWJcBDU42LIG8IhmsYCZWUTIhSzh5JBAdGQ2AGswDUFikAj8OhSEVFSq3f0RkO4XlgQ-WCt5HR9zeAAZXwJELI+BTpxWYhot2ETrAwLgXEhMRQUxBxSWkpwTB95DzeKPb4A03gAAEoAQF8FaTIek2kGAMGkMa1Bzbi3OhrB21xxQzBQqEZISIghZHSoYfADRYDUE2tQQsJ4dGBCfrhaeMtZ6E0QLDfCM9NomjOowJGuyTloAGe0E+EgLmWXBizNm3c2ivzvh-TZglYw40POKBaS1YTnPgmOeai0wDAWBZwd+UAAUQqBTAf6vlsBNJaW0rgHSoBdJ6RTPpNy2hDJACMhisAIATIgFMjEsyYqRkWUwFZazsATxiTBL+ZkdmPJ5vsw5cJjmGDGKchgiK+UjDWCafFdy8APPWmJLuMcf4SA+SyzsbKflSz+bC8FkLEXwWhSpOF2qkXvWbJ5a8HKZV7PlowbcIqBW5A2S-SVo0Z63XQNqCmD0npurKDqF6b0JCotae02G2K0g7XdAqE54QSVOHkaMMA+BFFZDVFSlwAVaVJE4EqnCPzlrsBgB0V+2NtyMxTn-d4ADRqBPES7HJYdmZROsrErZCCg5cMsWgsxk4+EVkEQuSxIifR1oLszfJ0TCmqsDpw4x5Zu0mPnHghstjU7gOZgAVgKbE7w6AlCILsV4fWpth1DvCc2bcTDLxeVvLQDhQc7CrrPdoy9ZrWEsESV4dAuh9BHxgNsU9WRtyNtNds1hE5DEZn4I2Y9tS6mfAaV4AAEsYQomQ64j1gxhzDIIhAnjlvPWAaBVwvjiLabwm1GDa2Re4AAVNRmAABhcA5MWm6HLn+Dk+AxzwEFIs4UiyKj130jxDjORDiCmo+4E8ZcK7V1roJxuJd4YIRLjPbWJMEIo2ICXdCqBUx5EWjRYgh7Qkq04Bp4g7qYBVx0ygPTBmSZV2NsZgw8G4QImEKMHpX4aJYd835maZATzbgWYUbw254Y2t2ZQSzAB9AAYsEOuUAq42kiIwQsGXLNEegLjF02XYBGYNi5pmJrxSLwqABFatq1gTAiyFlayYaCnDWEENgM9IFZohpQSBjMg7NKDRikNFQcWjX9NQLc5WDAAW3DoaIpwwv1eWvBc60axkZLjQmpNQo07LP5WKvMNw7g6jzPAYIow6UneOA5PxjK8DXTustbrrBHp3QmxV3CwXgihaW8mV6xqAOQIvVASblWaB0FYA+BU25IFPuB+97wYOhXMDYJD-00OgNlfh4jxgyOcfACR7aFH49mbiiSXNtEYWIsurupZz1NOfUUz9Z1ygy10sZa3GTwoFPC29eNpQEt-3RqQLHSpTnFOb2o7PZAjd0SxdAXCm+oLkCABsMC5feAV4WYAhZCfJiAA
*/
const obj = {
a: {
a: ['a', 'b'],
x: 'xx',
y: {
z: 'zz',
},
},
b: {
u: true,
},
c: 42,
} as const // <- this is important for type & value inference
interface OBJ {
a: {
a: string[]
x: string
y: {
z: string
}
}
b: {
u: boolean
}
c: number
}
// ---------------------------------------------------------------------------------------------------------------------
// 👇 Play with this ----------------------
type inferValues = true // 👈📝 use value `typeof obj` to infer both type & value, or type `OBJ` to only infer types
type Obj = inferValues extends true ? typeof obj : OBJ
type D1 = DotBranch<Obj, false> // "c" | "a.x" | "a.y.z" | "b.u"
type D2 = DotBranch<Obj> // "a" | "b" | "c" | "a.x" | "a.y" | "a.y.z" | "b.u"
type R1 = DotType<Obj, D1>
type R2 = DotType<Obj, 'a'>
declare function get<P extends DotBranch<Obj>>(path: P): DotType<Obj, P> // This is shorthand for 'node'
declare function getNode<P extends DotBranch<Obj, 'node'>>(path: P): DotType<Obj, P>
declare function getLeaf<P extends DotBranch<Obj, 'leaf'>>(path: P): DotType<Obj, P>
const val = get('a.a')
const node = getNode('')
const leaf = getLeaf('')
// -- 👀👆 -------- 📝👆 try here, this is it
// ---------------------------------------------------------------------------------------------------------------------
// The Dot Type --------------------------------------------------------------------------------------------------------
type DotJoin<A extends string, B extends string> = A extends '' ? B : `${A}.${B}`
type PrimitiveKeys<O extends AnyObject> = {
[K in keyof O]: O[K] extends AnyObject ? never : K
}[keyof O]
type DeepKeys<O extends AnyObject> = {
[K in keyof O]: O[K] extends AnyObject ? O[K] extends readonly any[] ? never: K : never
}[keyof O]
// - 👇 Test this -----------------
type PK = PrimitiveKeys<Obj> // "c"
type DK = DeepKeys<Obj> // "a" | "b"
type V1 = Obj[DK]
type V2 = UnionToIntersection<V1>
type V3 = DeepKeys<V2> // "y"
// --------------------------------
type DotBranch<O extends AnyObject, Mode extends boolean | 'node' | 'leaf' = true> =
Mode extends true | 'node'
? DotBranchNode<O>
: DotBranchLeaf<O>
// - 👇 Test this -----------------
type L0 = DotBranch<Obj> // "a" | "b" | "c" | "a.x" | "a.y" | "a.y.z" | "b.u" // <- shorthand for DotBranch<Obj, 'node'>
type L1 = DotBranch<Obj, 'node'> // "a" | "b" | "c" | "a.x" | "a.y" | "a.y.z" | "b.u" // <- this is all possible key paths
type L2 = DotBranch<Obj, 'leaf'> // "c" | "a.x" | "a.y.z" | "b.u" // <- only leaf key paths (paths that don't nest further)
type L3 = DotBranch<Obj, true> // Same as 'node'
type L4 = DotBranch<Obj, false> // Same as 'leaf'
// --------------------------------
// @ts-expect-error Type 'keyof O' does not satisfy the constraint 'string'.
type DotBranchNode<O extends AnyObject, P extends string = '', K extends string = keyof O> =
K extends DeepKeys<O>
? DotBranchNode<O[K], DotJoin<P, K>> | DotJoin<P, K>
: DotJoin<P, K>
// @ts-expect-error Type 'keyof O' does not satisfy the constraint 'string'.
type DotBranchLeaf<O extends AnyObject, P extends string = '', K extends string = keyof O> =
K extends DeepKeys<O>
? DotBranchLeaf<O[K], DotJoin<P, K>>
: DotJoin<P, K>
type DotType<O extends AnyObject, T extends string & DotBranch<O>> =
T extends `${infer A}.${infer B}`
// @ts-expect-error B of type string is not assignable to the constraint
? DotType<O[A], B>
: O[T]
// - 👇 Test this -----------------
type D3 = DotBranchNode<Obj> // "a" | "b" | "c" | "a.x" | "a.y" | "a.y.z" | "b.u"
type D4 = DotBranchLeaf<Obj> // "c" | "a.x" | "a.y.z" | "b.u"
type D5 = DotBranch<{}> // never
type T1 = DotType<Obj, 'a'> // 42
type T2 = DotType<Obj, 'b'> // { u: true }
type T3 = DotType<Obj, 'a.x'> // "xx"
// --------------------------------
// Helpers -------------------------------------------------------------------------------------------------------------
type AnyObject = Record<string, any>
/** Converts union `a | b` into intersection `a & b` */
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends
(k: infer I) => void ? I : never
// Playground ----------------------------------------------------------------------------------------------------------
type Tail<T> = T extends [infer _FirstItem, ...infer Rest] ? Rest : never
type DotMerge<A extends string[]> = Tail<A>['length'] extends 0
? A[0]
// @ts-expect-error Type 'DotMergeTuple<Tail<A>>' is not assignable to type 'string | number | bigint | boolean'.
: `${A[0]}.${DotMerge<Tail<A>>}`
type T01 = DotMerge<['a']>
type T02 = DotMerge<['a', 'b']>
type T03 = DotMerge<['a', 'b', 'c', 'd']>
type DotSplit<T> = T extends `${infer A}.${infer B}`
? [A, ...DotSplit<B>]
: [T]
type T04 = DotSplit<'a'>
type T05 = DotSplit<'a.b'>
type T06 = DotSplit<'a.b.c.d'>
////// -----------------------------------------------------------------------------------------------------------------
/*//// -----------------------------------------------------------------------------------------------------------------
This file is an "Ambient declarations file". The types defined here are available globally.
More info here: https://stackoverflow.com/a/73389225/985454
Don't use `import` and `export` in this file directly! It breaks ambience.
To import external types in an ambient declarations file (this file) use the following:
*//**
* @example
* declare type React = import('react')
*//*
To contribute ambient declarations from any file, even non-ambient ones, use this:
*//**
* @example
* declare global {
* interface Window {
* AJS: any
* }
* }
*//*
/*//// -----------------------------------------------------------------------------------------------------------------
////// -----------------------------------------------------------------------------------------------------------------
type AnyObject<T = any> = Record<string, T>
type AnyFunction = (...args: any[]) => any
type AnyAsyncFunction = (...args: any[]) => Promise<any>
type AllowArray<T> = T | T[]
type ValuesOf<T> = T[keyof T]
/* Strings */
type Stringish = string | number | boolean | null | undefined
type Length<S extends string> = Split<S>['length']
/**
* Joins array of const strings into a single const string that retains its const type value.
* @see `holograph/src/utils/index@join()`
*
* Note, it is necessary to convert the **readonly** const strings to Writeable for this to work.
* @example
* Join<Writeable<S>, Sep>
* Join<["a", "b"], " "> // "a b"
*/
type Join<S extends Stringish[], Sep extends string = ''> =
Tail<S>['length'] extends 0
? `${S[0]}`
: `${S[0]}${Sep}${Join<Tail<S>, Sep>}`
/** Splits a string into an array of substrings. */
type Split<S extends string, Sep extends string = ''> =
S extends `${infer U}${Sep}${infer V}`
? [U, ...Split<V, Sep>]
: S extends "" ? [] : [S]
/* Tuples */
/** Changes type of tuple members. */
type ReTuple<T extends any[], NewType = any> = { [K in keyof T]: NewType }
/** Makes tuple members optional. */
type OptionalTuple<T extends any[]> = { [K in keyof T]: T[K] | undefined }
/** Creates a tuple of set length and type. */
type ExactTuple<T, Length extends number, /** don't */__A extends any[] = []> = __A extends { length: Length } ? __A : ExactTuple<T, Length, [...__A, T]>
/** Removes first item from a tuple and returns the rest. */
type Tail<T> = T extends [infer _FirstItem, ...infer Rest] ? Rest : never
/**
* This type allows to add JSDoc annotation to any type without causing conflicts.
*
* @example
* type IconButtonProps =
* & JSDoc
* & ShorthandVariantsAndSizes
*
* type JSDoc = Documentation<ShorthandVariantsAndSizes, {
* /★★
* * **variant**: Color theme for the button.
* * - `light` variant is white on all themes.
* ★/
* variant?: unknown // <- note: this `?: unknown` optional type is important
* // ... other props
* }>
*/
type Documentation<T, TargetType extends { [K in keyof T]?: unknown }> = TargetType
// ---------------------------------------------------------------------------------------------------------------------
/**
* **All** properties or **none** must be provided.
*/
type AllOrNone<T> = T | { [K in keyof T]?: never }
/**
* Requires **at least one** property to be provided.
*/
type AtLeastOne<T, Keys extends keyof T = keyof T> =
Normalize<
& Pick<T, Exclude<keyof T, Keys>>
& { [K in Keys]-?:
& Required<Pick<T, K>>
& Partial<Record<Exclude<Keys, K>, T[K]>>
}[Keys]
>
/**
* Requires **only one** property to be provided.
*/
type OnlyOne<T, Keys extends keyof T = keyof T> =
Normalize<
& Pick<T, Exclude<keyof T, Keys>>
& { [K in Keys]-?:
& Required<Pick<T, K>>
& Partial<Record<Exclude<Keys, K>, never>>
}[Keys]
>
/**
* Allows **only one** property to be provided or none.
*/
type OnlyOneOrNone<T, Keys extends keyof T = keyof T> =
Normalize<
& Pick<T, Exclude<keyof T, Keys>>
& { [K in Keys]?:
& Pick<T, K>
& Partial<Record<Exclude<Keys, K>, never>>
}[Keys]
>
// ---------------------------------------------------------------------------------------------------------------------
/**
* Enhanced version of `Pick<T, keyof T>` that allows to pick and **rename** properties.
* https://stackoverflow.com/a/75963430/985454
*
* @example
* PickAs<{ a: 1, b: 2, c: 3 }, 'a:x' | 'b:y' | 'c'> // { x: 1, y: 2, c: 3}
*/
type PickAs<T, K extends `${string}:${string}` | Exclude<keyof T, K extends `${infer A}:${string}` ? A : never>> =
ShallowNormalize<
& UnionToIntersection<K extends `${infer A}:${infer B}` ? { [key in B]: T[A] } : never>
& Pick<T, Extract<K, keyof T>>
>
/*`*/
type Modify<T, R extends PartialAny<T>> = Omit<T, keyof R> & R
type ModifyDeep<A, B extends DeepPartialAny<A>> = {
// https://stackoverflow.com/a/74216680/985454
[K in keyof A | keyof B]: // For all keys in A and B: ...
/**/ K extends keyof A // ───┐
/**/ ? K extends keyof B // ───n ─ y ? key K exists in both A and B ...
/* */ ? A[K] extends AnyObject // │ ─┴──┐
/* */ ? B[K] extends AnyObject // │ ─── n ─ y ? both A and B are objects ...
/* */ ? B[K] extends readonly any[] // │ │ ┼── ? but if B is an array ...
/* */ ? B[K] // │ │ │ └─ ... 🠆 use B as the final type (new type)
/* */ : ModifyDeep<A[K], B[K]> // │ │ └─── ... 🠆 else We need to go deeper (recursively)
/* */ : B[K] // │ ├─ ... B is a primitive 🠆 use B as the final type (new type)
/* */ : B[K] // │ └─ ... A is a primitive 🠆 use B as the final type (new type)
/**/ : A[K] // ├─ ... key only exists in A 🠆 use A as the final type (original type)
/**/ : B[K] // └─ ... key only exists in B 🠆 use B as the final type (new type)
}
type ModifyDeep1<A, B extends DeepPartialAny<A>> = {
// https://stackoverflow.com/a/74216680/985454
[K in keyof A | keyof B]: // For all keys in A and B: ...
/**/K extends keyof A // ───┐
/* */ ? K extends keyof B // ── n ─ y ? key K exists in both A and B ...
/* */ ? A[K] extends AnyObject // │ ─┴──┐
/* */ ? B[K] extends AnyObject // │ ─── n ─ y ? both A and B are objects ...
/* */ ? ModifyDeep1<A[K], B[K]> // │ │ └─ ... 🠆 We need to go deeper (recursively)
/* */ : B[K] // │ ├─ ... B is a primitive 🠆 use B as the final type (new type)
/* */ : B[K] // │ └─ ... A is a primitive 🠆 use B as the final type (new type)
/* */ : A[K] // ├─ ... key only exists in A 🠆 use A as the final type (original type)
/* */ : B[K] // └─ ... key only exists in B 🠆 use B as the final type (new type)
}
/** Makes each property optional and turns each leaf property into any, allowing for type overrides by narrowing any. */
type DeepPartialAny<T> = {
[P in keyof T]?: T[P] extends AnyObject ? DeepPartialAny<T[P]> : any
}
type PartialAny<T> = {
[P in keyof T]?: any
}
type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>
}
/** Normalize type by recursively applying any type aliases and merging explicit intersections. */
type Normalize<T> =
T extends (...args: infer A) => infer R ? (...args: Normalize<A>) => Normalize<R>
: { [K in keyof T]: Normalize<T[K]> }
/** Merges intersection `{a} & {b}` into `{a, b}`. *(Similar to `Normalize` but simpler.)* */
type ShallowNormalize<T> = { [K in keyof T]: T[K] }
type UnionToIntersection<U> = (U extends any
? (k: U) => void
: never) extends (k: infer I) => void
? I
: never;
/**
* This type lights red when two types are not equal. Useful where you need to keep two objects / types in sync.
*
* @example
* type A = ValuesOf<typeof CHAIN_IDS>
* type B = keyof typeof CHAIN_NAMES
* type C = keyof typeof SCAN_EXPLORER_LINKS
* type check =
* & CheckIntegrity<A, B>
* & CheckIntegrity<B, A>
* // or shorter (optional)
* & CheckIntegrity<A, C, A>
*/
type CheckIntegrity<T, T2 extends T, T3 extends T2 = T> = never
// Window and library interfaces overrides -------------------------------------------------------------------------------------
interface Window {
d: any // debug object, put anything here without TypeScript complaining about "Property 'd' does not exist on Window."
}
// Tuples --------------------------------------------------------------------------------------------------------------
// 👉 In order to detect both an array or tuple, it is necessary to use `T extends readonly any[]`
type Tup = [any, ...any[]]
type Tur = readonly [...Tup]
type X1 = any[] extends any[] ? true : false // true
type X2 = any[] extends readonly any[] ? true : false // true
type X3 = any[] extends Tup | Tur ? true : false // false
type X4 = ['a','b'] extends any[] ? true : false // true
type X5 = ['a','b'] extends readonly any[] ? true : false // true
type X6 = ['a','b'] extends Tup | Tur ? true : false // true
type Y1 = [] extends any[] ? true : false // true
type Y2 = [] extends readonly any[] ? true : false // true
type Y3 = readonly [] extends any[] ? true : false // false
type Y4 = readonly [] extends readonly any[] ? true : false // true
type Z1 = [any] extends any[] ? true : false // true
type Z2 = [any] extends readonly any[] ? true : false // true
type Z3 = readonly [any] extends any[] ? true : false // false
type Z4 = readonly [any] extends readonly any[] ? true : false // true
// ---------------------------------------------------------------------------------------------------------------------
/**
* Inferring tuples
*
* https://stackoverflow.com/questions/50366989/why-does-typescript-tuple-type-inference-work-like-this
*/
function tuple1<T extends [] | {}>(tuple: T): T { return tuple }
function tuple2<T extends [] | any[]>(tuple: T): T { return tuple }
function tuple3<T extends [any] | any[]>(tuple: T): T { return tuple }
const a = tuple1([1, 'a', true]) // type: [number, string, boolean]
const b = tuple2([1, 'a', true]) // type: [number, string, boolean]
const c = tuple3([1, 'a', true]) // type: [number, string, boolean]
/* */
/* Type guards */
/** Ensures **type safety** while not hindering **type inference**. Don't forget to use `as const` and `readonly` or `Readonly<T>`, although `Readonly` will not work with deep objects.
* @example
* is<Readonly<Option<Exclude<ChainNames, 'disconnect_action'>>[]>>()([] as const) // `Readonly<>` is important with `as const`
*
* const keywords = is<readonly string[]>()(['a', 'b'] as const)
* const keywordz = is<readonly string[]>()(['a', 2 ] as const) // ERROR
* type T1 = typeof keywords // inferred as ['a', 'b'] and not string[]
*
*/
export const is = <TargetType>() => <T extends TargetType>(arg: T): T => arg
/** Narrows type in an if block.
* @example
* declare const something: A | B
*
* if (narrow<A>(something, v => !!v.propertyOnAOnly)) {
* // `something` is of type A
* } else {
* // `something` is of type B
* }
*/
export const narrow = <T extends any>(value: any, condition: (v: T) => boolean): value is T => condition(value)
/* Type compatibility table
|x |
--+-----+
y |x = y| (typeof X "assignable from" typeof Y)
--+-----+
*/
/* | never | undefined | void | null | T | unknown | any |
-----------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+
never |*/ x = x; /*|*/ u = x; /*|*/ v = x; /*|*/ n = x; /*|*/ t = x; /*|*/ k = x; /*|*/ a = x; /*|
undefined |*/ x = u; /*|*/ u = u; /*|*/ v = u; /*|*/ n = u; /*|*/ t = u; /*|*/ k = u; /*|*/ a = u; /*|
void |*/ x = v; /*|*/ u = v; /*|*/ v = v; /*|*/ n = v; /*|*/ t = v; /*|*/ k = v; /*|*/ a = v; /*|
null |*/ x = n; /*|*/ u = n; /*|*/ v = n; /*|*/ n = n; /*|*/ t = n; /*|*/ k = n; /*|*/ a = n; /*|
T |*/ x = t; /*|*/ u = t; /*|*/ v = t; /*|*/ n = t; /*|*/ t = t; /*|*/ k = t; /*|*/ a = t; /*|
unknown |*/ x = k; /*|*/ u = k; /*|*/ v = k; /*|*/ n = k; /*|*/ t = k; /*|*/ k = k; /*|*/ a = k; /*|
any |*/ x = a; /*|*/ u = a; /*|*/ v = a; /*|*/ n = a; /*|*/ t = a; /*|*/ k = a; /*|*/ a = a; /*|
-----------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+
*/
// Type compatibility table
// https://github.com/earshinov/typescript-type-compatibility-table/blob/master/type-compatibility-table.ts
function fx(): never { throw Error("Never returns"); }
function fv(): void { }
var x: never = fx();
var u: undefined = undefined;
var v: void = fv();
var n: null = null;
var t: {} = {}; // random type
var k: unknown = {};
var a: any = {};
// Observations (under `strict: true`):
//
// 1. `never` is assignable-from only `never`
// 2. `never` is assignable-to any type
// 3. `any` and `unknown` are assignable-from any type
// 4. `unknown` is assignable-to only `any` and `unknown`
// 5. `any` is assignable-to any type (except for `never`, see p.1)
// 6. `undefined` is assignable-to `void`
// 7. `undefined`, `null`, `T` are incompatible
//
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment