1/**
2 * helpers for the typescript language
3 *
4 * @module ts
5 */
6
7/** return type of `setTimeout` regardless of the environment */
8export type Timer = ReturnType<typeof setTimeout>;
9/** return type of `setInterval` regardless of the environment */
10export type Interval = ReturnType<typeof setInterval>;
11/** opposite of the built-in `Readonly` type */
12export type Writeable<T> = { -readonly [P in keyof T]: T[P] };
13/** A type that can be `await`ed to return `T` */
14export type Awaitable<T> = T | PromiseLike<T>;
15/** opposite of the built-in `Readonly` type, but recursive */
16// TODO: tuples?
17export type DeepWriteable<T> = {
18 -readonly [P in keyof T]: T[P] extends ReadonlyArray<infer I> ? Array<I> : DeepWriteable<T[P]>;
19};
20/** opposite of `DeepWriteable`, for values that are shared and must not change */
21export type DeepReadonly<T> = T extends Date
22 ? T
23 : T extends Uint8Array
24 ? Readonly<T>
25 : T extends Map<infer K, infer V>
26 ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
27 : T extends Set<infer V>
28 ? ReadonlySet<DeepReadonly<V>>
29 : T extends ReadonlyArray<infer I>
30 ? ReadonlyArray<DeepReadonly<I>>
31 : T extends object
32 ? { readonly [P in keyof T]: DeepReadonly<T[P]> }
33 : T;
34
35declare const brand: unique symbol;
36export type Brand<T> = { [brand]: T };
37
38/**
39 * redeclared here because it is only provided by `lib: ["dom"]` and not
40 * the JavaScript standard types.
41 */
42export interface VoidFunction {
43 (): void;
44}
45
46/** convenience type. Construct with `defer` */
47export interface Dispose extends Disposable, VoidFunction {}
48
49/**
50 * ```ts
51 * using _ = ts.defer(() => { ... });
52 * ```
53 * Additionally, this can be used to construct the convenience type
54 * `ts.Dispose`
55 */
56export function defer(fn: () => void): Dispose {
57 const f = fn as Dispose;
58 f[Symbol.dispose] = f;
59 return f;
60}
61
62export type Mixin<A, B> = A & B;
63
64/**
65 * mutates `base` to implement all methods of `other`, using `other` as state.
66 */
67export function mixin<A extends object, B extends object>(base: A, other: B): Mixin<A, B> {
68 const prototype = Object.getPrototypeOf(other);
69 for (const key of [
70 ...Object.getOwnPropertyNames(prototype),
71 ...Object.getOwnPropertySymbols(prototype),
72 ]) {
73 if (key === "constructor") continue;
74 if (key in base) continue;
75 const fn = prototype[key];
76 if (typeof fn !== "function") continue;
77 (base as Record<string | symbol, unknown>)[key] = fn.bind(other);
78 }
79 return base as A & B;
80}
81
82export type EmptyObject = Record<never, unknown>;
83
84/** TODO: this type has many subtle bugs */
85export type ToJson<T> = T extends JsonValue
86 ? T
87 : T extends { toJSON(): infer J }
88 ? ToJson<J>
89 : T extends Set<unknown> | Map<unknown, unknown> | Record<string, never>
90 ? EmptyObject
91 : T extends unknown[]
92 ? {
93 [K in keyof Omit<T, keyof unknown[]>]: T[K] extends undefined | void
94 ? null
95 : ToJson<T[K]>;
96 }
97 : T extends object
98 ? { [K in keyof Omit<T, symbol>]: ToJson<T[K]> }
99 : never;
100export type Json = JsonValue | undefined | void;
101export type JsonValue =
102 | string
103 | number
104 | boolean
105 | null
106 | JsonValue[]
107 | { [k: string]: Exclude<JsonValue, undefined> };