Mirror of https://github.com/roostorg/coop
github.com/roostorg/coop
1/* eslint-disable max-lines */
2import {
3 type Opaque,
4 type Primitive,
5 type Simplify,
6 type SnakeCase,
7 type UnwrapOpaque,
8} from 'type-fest';
9
10import { __throw } from './misc.js';
11
12export type WithUndefined<T extends object> = { [K in keyof T]?: undefined };
13
14export type {
15 SnakeCase as CamelToSnakeCase,
16 CamelCase as SnakeToCamelCase,
17 ReadonlyDeep,
18} from 'type-fest';
19
20/**
21 * Equivalent to SnakeCasePropertiesDeep from type-fest, except that it also
22 * recursively transforms the keys of objects in array/tuple types.
23 */
24export type SnakeCasedPropertiesDeepWithArrays<T> = T extends
25 | readonly []
26 | readonly [...never[]]
27 ? readonly []
28 : T extends readonly [infer U, ...infer V]
29 ? readonly [
30 SnakeCasedPropertiesDeepWithArrays<U>,
31 ...SnakeCasedPropertiesDeepWithArrays<V>,
32 ]
33 : T extends readonly [...infer U, infer V]
34 ? readonly [
35 ...SnakeCasedPropertiesDeepWithArrays<U>,
36 SnakeCasedPropertiesDeepWithArrays<V>,
37 ]
38 : T extends ReadonlyArray<infer ItemType>
39 ? ReadonlyArray<SnakeCasedPropertiesDeepWithArrays<ItemType>>
40 : T extends object
41 ? { [K in keyof T as SnakeCase<K>]: SnakeCasedPropertiesDeepWithArrays<T[K]> }
42 : T;
43
44export type StringKeys<T extends object> = keyof T & string;
45
46export type RequiredWithoutNull<T> = {
47 [P in keyof T]-?: Exclude<T[P], null>;
48};
49
50export type Mutable<T> = T extends readonly (infer U)[]
51 ? U[]
52 : { -readonly [K in keyof T]: T[K] };
53
54/**
55 * Synchronous iterables can be used as async iterables (i.e., code can await
56 * the values they yield and it's a no-op), so functions that sometimes need to
57 * accept async iterables should also be able to be called with a sync iterable;
58 * this makes that easier to type.
59 */
60export type IterableOrAsyncIterable<T> = Iterable<T> | AsyncIterable<T>;
61
62/**
63 * Extracts the public method names from a type T, returning a union of string
64 * literal types. Also works on plain objects, for which it returns the name of
65 * any function-holding properties.
66 */
67export type PublicMethodNames<T> = {
68 // eslint-disable-next-line @typescript-eslint/no-explicit-any
69 [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never;
70}[keyof T];
71
72/**
73 * Returns a value cast to be typed as a compatible opaque type.
74 *
75 * @template OpaqueType The opaque type to cast the given value `value` to.
76 * @param value The value that is to be cast to the given opaque type.
77 */
78export function instantiateOpaqueType<OpaqueType extends Opaque<unknown>>(
79 value: UnwrapOpaque<OpaqueType>,
80): OpaqueType {
81 return value as OpaqueType;
82}
83
84/**
85 * Takes a value typed as an opaque type and returns it cast to its runtime
86 * representation.
87 *
88 * @param value The opaque value that is to be casted to its runtime type.
89 */
90export function unwrapOpaqueValue<OpaqueType extends Opaque<unknown>>(
91 value: OpaqueType,
92): UnwrapOpaque<OpaqueType> {
93 return value as UnwrapOpaque<OpaqueType>;
94}
95
96// A convenient helper for building types that have different values based on
97// some boolean generic.
98export type If<Cond extends boolean, IfTrue, IfFalse> = Cond extends true
99 ? IfTrue
100 : Cond extends false
101 ? IfFalse
102 : IfTrue | IfFalse;
103
104export type NullableKeysOf<T> = {
105 [K in keyof T]-?: null extends T[K] ? K : never;
106}[keyof T];
107
108export type Bind1<
109 F extends (arg0: A0, ...args: never[]) => unknown,
110 A0 = never,
111> = F extends (arg0: A0, ...args: infer Args) => infer R
112 ? (...args: Args) => R
113 : never;
114
115export type Bind2<
116 F extends (arg0: A0, arg1: A1, ...args: never[]) => unknown,
117 A0 = never,
118 A1 = never,
119> = F extends (arg0: A0, arg1: A1, ...args: infer Args) => infer R
120 ? (...args: Args) => R
121 : never;
122
123export type Bind3<
124 F extends (arg0: A0, arg1: A1, arg2: A2, ...args: never[]) => unknown,
125 A0 = never,
126 A1 = never,
127 A2 = never,
128> = F extends (arg0: A0, arg1: A1, arg2: A2, ...args: infer Args) => infer R
129 ? (...args: Args) => R
130 : never;
131
132export type Bind4<
133 F extends (
134 arg0: A0,
135 arg1: A1,
136 arg2: A2,
137 arg3: A3,
138 ...args: never[]
139 ) => unknown,
140 A0 = never,
141 A1 = never,
142 A2 = never,
143 A3 = never,
144> = F extends (
145 arg0: A0,
146 arg1: A1,
147 arg2: A2,
148 arg3: A3,
149 ...args: infer Args
150) => infer R
151 ? (...args: Args) => R
152 : never;
153
154/**
155 * Takes a tuple type, and returns a new tuple type with the first N elements
156 * removed.
157 */
158export type DropN<T extends readonly unknown[], N extends number> = N extends 0
159 ? T
160 : T extends readonly [unknown, ...infer U] // @ts-ignore
161 ? DropN<U, Nat2Number<Dec<Number2Nat<N>>>>
162 : never;
163
164// Types for doing math in TS, by representing each natural number as a tuple
165// type with n elements, and exploiting its ability to track a tuple's length
166// as a number literal type.
167type Zero = readonly [];
168type SomeNat = readonly [...unknown[]];
169type Succ<N extends SomeNat> = readonly [...N, unknown];
170type Dec<N extends SomeNat> = N extends readonly [unknown, ...infer T]
171 ? T
172 : never;
173type Nat2Number<N extends SomeNat> = N['length'];
174type Number2Nat<
175 I extends number,
176 N extends SomeNat = Zero,
177> = I extends Nat2Number<N> ? N : Number2Nat<I, Succ<N>>;
178// type NumericToNat<I extends string> = I extends `${infer T extends number}`
179// ? Number2Nat<T>
180// : never;
181
182// type Min2<N extends SomeNat, M extends SomeNat> = ((
183// ...args: N
184// ) => any) extends (...args: M) => any
185// ? N
186// : M;
187
188// type Min<T extends SomeNat[]> = T extends [
189// infer M,
190// infer N,
191// ...infer R extends SomeNat[],
192// ]
193// ? //@ts-ignore
194// Min2<M, Min<[N, ...R]>>
195// : T extends [infer M, infer N]
196// ? Min2<M, N>
197// : T extends [infer M]
198// ? M
199// : Zero;
200
201// type Max2<N extends SomeNat, M extends SomeNat> = ((
202// ...args: N
203// ) => any) extends (...args: M) => any
204// ? M
205// : N;
206
207// type MaxOfUnion<It extends number> = Nat2Number<
208// Parameters<
209// UnionToIntersection<{ [K in It]: (...it: Number2Nat<K>) => any }[It]>
210// >
211// >;
212
213// type MinOfUnion<It extends number> = Exclude<It, MaxOfUnion<It>> extends never
214// ? It
215// : MinOfUnion<Exclude<It, MaxOfUnion<It>>>;
216
217// type Add<N extends SomeNat, M extends SomeNat> = readonly [...N, ...M];
218// type Multiply<N extends SomeNat, M extends SomeNat> = M extends Zero
219// ? Zero
220// : Add<N, Multiply<N, Dec<M>>>;
221// type Subtract<N extends SomeNat, M extends SomeNat> = M extends Zero
222// ? N
223// : Subtract<Dec<N>, Dec<M>>;
224
225/**
226 * If you have an object type w/ a call signature but also some extra properties
227 * (e.g., `{ (): Promise<void>, restoreOriginal: () => void }`), this returns
228 * just the call signature (`() => Promise<void>` in the example above).
229 */
230// eslint-disable-next-line @typescript-eslint/no-explicit-any
231export type CallSignature<T extends (...args: any) => any> = (
232 ...args: Parameters<T>
233) => ReturnType<T>;
234
235export type NonEmptyString = Opaque<string, 'NonEmptyString'>;
236
237export function isNonEmptyString(it: unknown): it is NonEmptyString {
238 return typeof it === 'string' && it !== '';
239}
240
241export function tryParseNonEmptyString(it: unknown) {
242 return isNonEmptyString(it)
243 ? it
244 : __throw(new Error('Was not an NonEmptyString'));
245}
246
247export function areAllValuesNonEmptyStrings<T extends { [K: string]: string }>(
248 it: T,
249): it is { [K in keyof T]: T[K] & NonEmptyString } {
250 return Object.values(it).every(isNonEmptyString);
251}
252
253export function areAllArrayValuesNonEmptyString<T>(
254 it: readonly T[],
255): it is readonly (T & NonEmptyString)[] {
256 return it.every(isNonEmptyString);
257}
258
259export type NonEmptyArray<T> = [T, ...T[]];
260
261// This overload is needed to help TS reduce the resulting type properly. I.e.,
262// if `isNonEmptyArray` is called with a mutable `T[]`, the result will be:
263// `T[] & readonly NonEmptyArray<T>`, which TS can't simplify neatly to
264// NonEmptyArray<T>.
265export function isNonEmptyArray<T>(arr: Array<T>): arr is NonEmptyArray<T>;
266export function isNonEmptyArray<T>(
267 arr: ReadonlyArray<T>,
268): arr is Readonly<NonEmptyArray<T>>;
269export function isNonEmptyArray<T>(
270 arr: Readonly<Array<T>>,
271): arr is Readonly<NonEmptyArray<T>> {
272 return arr.length > 0;
273}
274
275/* eslint-disable @typescript-eslint/no-explicit-any */
276export type UnionToIntersection<T> = (
277 T extends any ? (x: T) => any : never
278) extends (x: infer R) => any
279 ? R
280 : never;
281/* eslint-enable @typescript-eslint/no-explicit-any */
282
283export type RenameEach<
284 Union,
285 Renames extends { [K in string]: keyof Union },
286> = Union extends object ? { [K in keyof Renames]: Union[Renames[K]] } : never;
287
288/**
289 * This type is exactly like Pick, except that it takes a union type as its
290 * first argument, and returns a union of the picked types. Concretely:
291 *
292 * ```
293 * Pick<
294 * { id: string, x: string, y: any } | { id: number, x: number, y: any },
295 * 'id' | 'x'
296 * >
297 * ```
298 *
299 * returns
300 *
301 * ```
302 * { id: string | number, x: string | number }
303 * ```
304 *
305 * Whereas `PickEach` preserves the original structure, giving
306 *
307 * { id: string, x: string } | { id: number, x: number }
308 *
309 * The difference is that the second type, correctly, rejects an object like
310 * `{ id: 'string', x: 4 }`.
311 *
312 * This works by exploiting that fact that conditional types distribute over
313 * unions in TS.
314 */
315export type PickEach<Union, Keys extends keyof Union> = Union extends unknown
316 ? Pick<Union, Keys>
317 : never;
318
319/**
320 * This type is exactly like Omit, except that it takes a union type as its
321 * first argument, and returns a union of the omitted types. See {@link PickEach}
322 * docblock for a concrete example of this behavior.
323 */
324export type OmitEach<Union, Keys extends keyof Union> = Union extends unknown
325 ? Omit<Union, Keys>
326 : never;
327/**
328 * CollapseCases takes a type T that's a union of object types, and returns a
329 * single object type where the type of each key is the union of the types for
330 * that key across all the cases of T.
331 *
332 * For example, if you have:
333 *
334 * type A = { id: 'A' }
335 * type B = { id: 'B' }
336 * type C = { id: 'C' }
337 *
338 * type Item =
339 * | { id: string; type: A; }
340 * | { id: string; type: B; }
341 * | { id: string; type: C; }
342 *
343 * Then, `CollapseCases<Item>` will be: `{ id: string, type: A | B | C }`.
344 *
345 * This can be useful because Typescript checks assignability case by case,
346 * so a type like `{ id: string, type: A | B | C }` would not be assignable
347 * to Item, because TS can only see that it's not known to be assignable to
348 * any of Item's individual cases.
349 *
350 * The fix, assuming `x` has type `{ id: string, type: A | B | C }`,
351 * would be something like `x satisfies CollapseCases<Item> as Item`.
352 *
353 * Note: in cases where a key only appears in some of the cases, the
354 * result type will have that key as optional, and typed as `unknown`.
355 * E.g., `CollapseCases<{ a: string } | { b: string }>` will be
356 * `{ a?: unknown, b?: unknown }`. This is the correct type because the first
357 * type in the union can be satisfied with any value in its key for `b`, and
358 * vice-versa. I.e., `{ a: 'hello', b: true }` and `{ a: 42, b: 'hello' }` are
359 * both legal assignments to `{ a: string } | { b: string }` (ignoring
360 * excess property checking heuristics that only sometimes apply), as they
361 * satisfy the first and second case respectively.
362 */
363export type CollapseCases<T extends object> = Simplify<
364 Pick<T, keyof T> & Partial<Pick<T, AllKeys<T>>>
365>;
366
367/**
368 * Returns all the keys from a union of object types.
369 *
370 * I.e., with a `type T = { a: number, b: string } | { a: boolean }`,
371 *
372 * I.e., `keyof T` will normally only return `'a'`, because `keyof` with an
373 * object type union returns the keys that exist in _every_ constituent of the
374 * union. However, AllKeys<T> will return `'a' | 'b'`.
375 */
376export type AllKeys<T extends object> = T extends object ? keyof T : never;
377
378/**
379 * Returns the first type parameter, while giving a type error if the first
380 * parameter's type isn't assignable to the second parameter's type.
381 *
382 * This is like a type-level equivalent of the `satisfies` operator: in the same
383 * way that you'd write `exprOfTypeT satisfies U` to get the `exprOfTypeT` typed
384 * as `T` while ensuring that the type is a `U`, you can write `Satisfies<T, U>`
385 * to get a type `T` while ensuring that it's assignable to `U`.
386 */
387export type Satisfies<T extends U, U> = T;
388
389/**
390 * Returns the first type parameter, while enforcing that it's a supertype of
391 * the second parameter.
392 */
393export type SatisfiedBy<T, _U extends T> = T;
394
395/**
396 * This type allows you to make sure that every case in one tagged union has a
397 * corresponding case in another tagged union. For example, imagine you have:
398 *
399 * type Job =
400 * | { type: 'USER_INITIATED', startedAt: Date };
401 * | { type: 'CRON', startedAt: Date, schedule: string };
402 *
403 * Now, imagine somewhere else you want to define (say) the shape of db rows
404 * that'll store a job. In this type, the keys are cased differently, and you
405 * might attach some extra metadata, like so:
406 *
407 * type JobRow =
408 * | { type: 'USER_INITIATED', started_at: Date, initiated_by: UserId }
409 * | { type: 'CRON', started_at: Date, schedule: string };
410 *
411 * Still, you want every `type` of Job to have a corresponding `type` in JobRow;
412 * otherwise, some jobs may not be able to be stored correctly. But, the
413 * question is: how can you make TS enforce that?
414 *
415 * The answer is to use this type, like so:
416 *
417 * type JobRow2 = TaggedUnionFromCases<
418 * { type: Job['type'] },
419 * {
420 * USER_INITIATED: { started_at: Date, initiated_by: UserId },
421 * CRON: { started_at: Date, schedule: string }
422 * }
423 * >
424 *
425 * The JobRow2 type will be exactly equal to the original JobRow type, but TS
426 * will give an error if the object type passed as the second type parameter
427 * doesn't have a key for every `type` value in Job.
428 *
429 * To unpack the above, the first type parameter holds an object type whose
430 * single key will be used as the discriminator key in the final type, and whose
431 * value is the union of all the required values of the discriminator key.
432 *
433 * Then, the second type parameter is an object type whose keys are the possible
434 * values of the discriminator key, and whose values are the corresponding
435 * fields that should be present in that case.
436 *
437 * This, honestly, is not the most intuitive API, but it's the best I could come
438 * up with.
439 */
440export type TaggedUnionFromCases<
441 TagWithValues extends object,
442 Map extends { [K in TagValues]: unknown },
443 TagKey extends keyof TagWithValues = keyof TagWithValues,
444 TagValues extends TagWithValues[TagKey] &
445 (string | number | symbol) = TagWithValues[TagKey] &
446 (string | number | symbol),
447> = { [K in TagValues]: Simplify<{ [K2 in TagKey]: K } & Map[K]> }[TagValues];
448
449/**
450 * Same as CollapseCases, but does it recursively on object types. Useful when
451 * the type with the discriminated union is nested inside another object.
452 *
453 * For example with:
454 *
455 * type T =
456 * | { name: 'A', data: { value: 'C' | 'D' } }
457 * | { name: 'B', data: { value: 'E' | 'F' } }
458 *
459 * `CollapseCasesDeep<T>` will be:
460 *
461 * { name: 'A' | 'B', data: { value: 'C' | 'D' | 'E' | 'F' } }
462 *
463 * Whereas `CollapseCases<T>` would only be:
464 *
465 * { name: 'A' | 'B', data: { value: 'C' | 'D' } | { value: 'E' | 'F' } }
466 */
467export type CollapseCasesDeep<T extends object> = Simplify<
468 {
469 // For all the keys in T that are in _any_ case (if T is a union) but not
470 // in _every_ case (which is what `keyof T` returns), make them optional.
471 [K in Exclude<AllKeys<T>, keyof T>]?: T[K] extends object
472 ? CollapseCasesDeep<T[K]>
473 : T[K];
474 } & {
475 // Meanwhile, the keys from every case are required in the final object
476 // type. `AllKeys<T> & keyof T` here might seem redundant, and it is, in the
477 // sense that it's always equal to just `keyof T`. However, it's necessary
478 // to prevent a TS rule from kicking in whereby, if T is a union, TS will
479 // silently distribute in the { [K in keyof T]: ... } type, which we don't
480 // want.
481 [K in AllKeys<T> & keyof T]: T[K] extends object
482 ? CollapseCasesDeep<T[K]>
483 : T[K];
484 }
485>;
486
487/**
488 * This type allows you to replace a type within a complex type. It works
489 * recursively, so it can handle nested types as well.
490 *
491 * @template Type The original type.
492 * @template Search The type to be replaced. Any time it -- or a subtype of it
493 * -- is found (deeply) within @see {Type}, it will be replaced.
494 * @template Replacement The type to replace with.
495 *
496 * @example
497 * ReplaceDeep<{ a: "x" | "y", b: { c: number } }, string, number> will return
498 * { a: number, b: { c: number } }. The type of `a` is replaced b/c "x" | "y"
499 * is a subtype of `string`, even though it's not exactly `string`.
500 *
501 * @example
502 * ReplaceDeep<{ a: string, b: { c: number } }, "x", number> will return
503 * the first type parameter without doing any replacements. The type of `a` is
504 * _not_ replaced, b/c string is not assignable to (i.e., isn't necesssarily)
505 * the "x" that's being searched for.
506 */
507export type ReplaceDeep<
508 Type,
509 Search,
510 Replacement,
511 IncludeFunctions extends boolean = false,
512> = Type extends Search
513 ? Replacement
514 : Type extends Opaque<unknown, infer Tag>
515 ? Opaque<ReplaceDeep<UnwrapOpaque<Type>, Search, Replacement>, Tag>
516 : Type extends Primitive | Date | RegExp
517 ? Type
518 : // Treat Promises and AsyncIterables specially because, while it's possible
519 // to do replacement totally structurally, that's likely undesirable and might
520 // push up against TS limits deep in the replacement
521 Type extends Promise<infer T>
522 ? Promise<ReplaceDeep<T, Search, Replacement, IncludeFunctions>>
523 : Type extends AsyncIterable<infer T>
524 ? Simplify<
525 AsyncIterable<ReplaceDeep<T, Search, Replacement, IncludeFunctions>> &
526 Omit<Type, typeof Symbol.asyncIterator>
527 >
528 : IncludeFunctions extends true
529 ? Type extends (...args: infer Args) => infer R
530 ? (
531 ...args: ReplaceDeep<Args, Search, Replacement, IncludeFunctions> &
532 unknown[]
533 ) => ReplaceDeep<R, Search, Replacement, IncludeFunctions>
534 : ReplaceObjectDeep<Type, Search, Replacement, IncludeFunctions>
535 : ReplaceObjectDeep<Type, Search, Replacement, IncludeFunctions>;
536
537type ReplaceObjectDeep<
538 Type,
539 Search,
540 Replacement,
541 IncludeFunctions extends boolean = false,
542> = Type extends object
543 ? {
544 [Key in keyof Type]: ReplaceDeep<
545 Type[Key],
546 Search,
547 Replacement,
548 IncludeFunctions
549 >;
550 }
551 : never;