Skip to content

Commit

Permalink
feat(types): add Ok/Err/Result/ResultPromise types
Browse files Browse the repository at this point in the history
  • Loading branch information
aleclarson committed Aug 15, 2024
1 parent a8c759b commit 7599e18
Showing 1 changed file with 47 additions and 194 deletions.
241 changes: 47 additions & 194 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,214 +98,67 @@ export type Intersect<U> = (U extends any ? (k: U) => void : never) extends (
export type Simplify<T> = {} & { [P in keyof T]: T[P] }

/**
* Get all properties **not using** the `?:` type operator.
*/
export type RequiredKeys<T> = T extends any
? keyof T extends infer K
? K extends keyof T
? Omit<T, K> extends T
? never
: K
: never
: never
: never

/**
* Get all properties using the `?:` type operator.
*/
export type OptionalKeys<T> = T extends any
? keyof T extends infer K
? K extends keyof T
? Omit<T, K> extends T
? K
: never
: never
: never
: never

/**
* Resolves to `true` if `Left` and `Right` are exactly the same type.
* A result tuple where the error is `undefined`.
*
* Otherwise false.
* @example
* ```ts
* type GoodResult = Ok<string>
* // ^? [undefined, string]
* ```
*/
export type IsExactType<Left, Right> = [Left] extends [Any]
? [Right] extends [Any]
? true
: false
: (<U>() => U extends Left ? 1 : 0) extends <U>() => U extends Right ? 1 : 0
? true
: false

export type Primitive =
| number
| string
| boolean
| symbol
| bigint
| null
| undefined
| void
export type Ok<TResult> = [err: undefined, result: TResult]

/**
* Coerce a primitive type to its boxed equivalent.
* A result tuple where an error is included.
*
* Note that `TError` is non-nullable, which means that
* `Err<undefined>` and `Err<null>` are not valid.
*
* @example
* ```ts
* type A = BoxedPrimitive<string>
* // ^? String
* type B = BoxedPrimitive<number>
* // ^? Number
* type BadResult = Err
* // ^? [Error, undefined]
*
* type BadResult2 = Err<TypeError | MyCoolCustomError>
* // ^? [TypeError | MyCoolCustomError, undefined]
* ```
*/
export type BoxedPrimitive<T = any> = T extends string
? // biome-ignore lint:
String
: T extends number
? // biome-ignore lint:
Number
: T extends boolean
? // biome-ignore lint:
Boolean
: T extends bigint
? // biome-ignore lint:
BigInt
: T extends symbol
? // biome-ignore lint:
Symbol
: never

export type TypedArray =
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array
| BigInt64Array
| BigUint64Array
| DataView
| ArrayBuffer
| SharedArrayBuffer
export type Err<TError = Error> = [err: NonNullable<TError>, result: undefined]

/**
* Add your own classes to this regitsry by extending its interface
* with what's called “declaration merging” in TypeScript.
* A result tuple.
*
* All property types in this registry type may be treated specially
* by any of Radashi's complex types. For example, `assign` will avoid
* merging with types in this registry.
*/
// biome-ignore lint: Preserve `interface` type.
export interface CustomClassRegistry {}

/**
* This type represents any custom class that was "registered" through
* the `CustomClassRegistry` type.
* First index is the error, second index is the result.
*
* @example
* ```ts
* type MyResult = Result<string>
* // ^? Ok<string> | Err<Error>
*
* type MyResult2 = Result<string, TypeError>
* // ^? Ok<string> | Err<TypeError>
* ```
*/
export type CustomClass = CustomClassRegistry[keyof CustomClassRegistry]
export type Result<TResult, TError = Error> =
| Ok<TResult>
| Err<NonNullable<TError>>

/**
* These types are implemented natively.
* A promise that resolves to a result tuple.
*
* Note that boxed primitives like `Boolean` (different from
* `boolean`) are not included, because `boolean extends Boolean ? 1 :
* 0` resolves to 1.
* @example
* ```ts
* type MyResult = ResultPromise<string>
* // ^? Promise<Ok<string> | Err<Error>>
*
* type MyResult2 = ResultPromise<string, TypeError>
* // ^? Promise<Ok<string> | Err<TypeError>>
* ```
*/
export type BuiltInType =
| ES2021.BuiltInType
| WebAPI.BuiltInType
| NodeJS.BuiltInType

// Start at ES2020, since they are the typings used by Radashi.
declare namespace ES2020 {
// Note: Don't include subtypes of types already listed here.
type BuiltInType =
| Primitive
| Promise<any>
| Date
| RegExp
| Error
| readonly any[]
| ReadonlyMap<any, any>
| ReadonlySet<any>
| WeakMap<WeakKey, any>
| WeakSet<WeakKey>
| TypedArray
// biome-ignore lint: Support the Function type.
| Function
}

declare namespace ES2021 {
// Note: Don't include subtypes of types already listed here.
type BuiltInType =
| ES2020.BuiltInType
| GlobalObjectType<'FinalizationRegistry'>
| GlobalObjectType<'WeakRef'>
}

declare namespace NodeJS {
type BuiltInType = GlobalObjectType<'Buffer'>
}

declare namespace WebAPI {
// Note: Don't include subtypes of types already listed here.
type BuiltInType =
| GlobalObjectType<'AbortController'>
| GlobalObjectType<'AbortSignal'>
| GlobalObjectType<'Blob'>
| GlobalObjectType<'Body'>
| GlobalObjectType<'CompressionStream'>
| GlobalObjectType<'Crypto'>
| GlobalObjectType<'CustomEvent'>
| GlobalObjectType<'DecompressionStream'>
| GlobalObjectType<'Event'>
| GlobalObjectType<'EventTarget'> // <-- Watch out for subtypes of this.
| GlobalObjectType<'FormData'>
| GlobalObjectType<'Headers'>
| GlobalObjectType<'MessageChannel'>
| GlobalObjectType<'Navigator'>
| GlobalObjectType<'ReadableStream'>
| GlobalObjectType<'ReadableStreamBYOBReader'>
| GlobalObjectType<'ReadableStreamDefaultController'>
| GlobalObjectType<'ReadableStreamDefaultReader'>
| GlobalObjectType<'SubtleCrypto'>
| GlobalObjectType<'TextDecoder'>
| GlobalObjectType<'TextDecoderStream'>
| GlobalObjectType<'TextEncoder'>
| GlobalObjectType<'TextEncoderStream'>
| GlobalObjectType<'TransformStream'>
| GlobalObjectType<'TransformStreamDefaultController'>
| GlobalObjectType<'URL'>
| GlobalObjectType<'URLSearchParams'>
| GlobalObjectType<'WebSocket'>
| GlobalObjectType<'WritableStream'>
| GlobalObjectType<'WritableStreamDefaultController'>
| GlobalObjectType<'WritableStreamDefaultWriter'>
| WebDocumentAPI.BuiltInType
}

declare namespace WebDocumentAPI {
// Note: Don't include subtypes of types already listed here.
type BuiltInType =
| GlobalObjectType<'Node'>
| GlobalObjectType<'NodeList'>
| GlobalObjectType<'NodeIterator'>
| GlobalObjectType<'HTMLCollection'>
| GlobalObjectType<'CSSStyleDeclaration'>
| GlobalObjectType<'DOMStringList'>
| GlobalObjectType<'DOMTokenList'>
}

// Infer an object type from a global constructor that is
// environment-specific. This helps avoid including unsupported types
// (according to tsconfig "lib" property), which can break things.
type GlobalObjectType<Identifier extends string> = [Identifier] extends [Any]
? never
: keyof Identifier extends never
? never
: typeof globalThis extends { [P in Identifier]: any }
? InstanceType<(typeof globalThis)[Identifier]>
: never
export type ResultPromise<TResult, TError = Error> = Promise<
[NonNullable<TError>] extends [never]
? Ok<TResult>
: [TResult] extends [never]
? Err<NonNullable<TError>>
: Result<TResult, NonNullable<TError>>
>

0 comments on commit 7599e18

Please sign in to comment.