Mirror of https://github.com/roostorg/coop github.com/roostorg/coop
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 551 lines 18 kB view raw
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;