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>
19 : DeepWriteable<T[P]>;
20};
21/** opposite of `DeepWriteable`, for values that are shared and must not change */
22export type DeepReadonly<T> = T extends Date ? T
23 : T extends Uint8Array ? Readonly<T>
24 : T extends Map<infer K, infer V> ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
25 : T extends Set<infer V> ? ReadonlySet<DeepReadonly<V>>
26 : T extends ReadonlyArray<infer I> ? ReadonlyArray<DeepReadonly<I>>
27 : T extends object ? { readonly [P in keyof T]: DeepReadonly<T[P]> }
28 : T;
29
30declare const brand: unique symbol;
31export type Brand<T> = { [brand]: T };
32
33/**
34 * redeclared here because it is only provided by `lib: ["dom"]` and not
35 * the JavaScript standard types.
36 */
37export interface VoidFunction {
38 (): void;
39}
40
41/** convenience type. Construct with `defer` */
42export interface Dispose extends Disposable, VoidFunction {}
43
44/**
45 * ```ts
46 * using _ = ts.defer(() => { ... });
47 * ```
48 * Additionally, this can be used to construct the convenience type
49 * `ts.Dispose`
50 */
51export function defer(fn: () => void): Dispose {
52 const f = fn as Dispose;
53 f[Symbol.dispose] = f;
54 return f;
55}
56
57export type Mixin<A, B> = A & B;
58
59/**
60 * mutates `base` to implement all methods of `other`, using `other` as state.
61 */
62export function mixin<A extends object, B extends object>(
63 base: A,
64 other: B,
65): Mixin<A, B> {
66 const prototype = Object.getPrototypeOf(other);
67 for (
68 const key of [
69 ...Object.getOwnPropertyNames(prototype),
70 ...Object.getOwnPropertySymbols(prototype),
71 ]
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 ? T
86 : T extends { toJSON(): infer J } ? ToJson<J>
87 : T extends Set<unknown> | Map<unknown, unknown> | Record<string, never> ? EmptyObject
88 : T extends unknown[] ? {
89 [K in keyof Omit<T, keyof unknown[]>]: T[K] extends undefined | void ? null
90 : ToJson<T[K]>;
91 }
92 : T extends object ? { [K in keyof Omit<T, symbol>]: ToJson<T[K]> }
93 : never;
94export type Json = JsonValue | undefined | void;
95export type JsonValue =
96 | string
97 | number
98 | boolean
99 | null
100 | JsonValue[]
101 | { [k: string]: Exclude<JsonValue, undefined> };