UNPKG

decoders

Version:

Elegant and battle-tested validation library for type-safe input data for TypeScript

668 lines (649 loc) 24.9 kB
interface ObjectAnnotation { readonly type: 'object'; readonly fields: ReadonlyMap<string, Annotation>; readonly text?: string; } interface ArrayAnnotation { readonly type: 'array'; readonly items: readonly Annotation[]; readonly text?: string; } interface ScalarAnnotation { readonly type: 'scalar'; readonly value: unknown; readonly text?: string; } interface OpaqueAnnotation { readonly type: 'opaque'; readonly value: string; readonly text?: string; } type Annotation = ObjectAnnotation | ArrayAnnotation | ScalarAnnotation | OpaqueAnnotation; /** * Result <value> <error> * = Ok <value> * | Err <error> */ type Ok<T> = { readonly ok: true; readonly value: T; readonly error?: never; }; type Err<E> = { readonly ok: false; readonly value?: never; readonly error: E; }; type Result<T, E> = Ok<T> | Err<E>; /** * Create a new Result instance representing a successful computation. */ declare function ok<T>(value: T): Ok<T>; /** * Create a new Result instance representing a failed computation. */ declare function err<E>(error: E): Err<E>; /** The Standard Schema interface. */ interface StandardSchemaV1<Input = unknown, Output = Input> { /** The Standard Schema properties. */ readonly '~standard': StandardSchemaV1.Props<Input, Output>; } declare namespace StandardSchemaV1 { /** The Standard Schema properties interface. */ interface Props<Input = unknown, Output = Input> { /** The version number of the standard. */ readonly version: 1; /** The vendor name of the schema library. */ readonly vendor: string; /** Validates unknown input values. */ readonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>; /** Inferred types associated with the schema. */ readonly types?: Types<Input, Output> | undefined; } /** The result interface of the validate function. */ type Result<Output> = SuccessResult<Output> | FailureResult; /** The result interface if validation succeeds. */ interface SuccessResult<Output> { /** The typed output value. */ readonly value: Output; /** The non-existent issues. */ readonly issues?: undefined; } /** The result interface if validation fails. */ interface FailureResult { /** The issues of failed validation. */ readonly issues: ReadonlyArray<Issue>; } /** The issue interface of the failure output. */ interface Issue { /** The error message of the issue. */ readonly message: string; /** The path of the issue, if any. */ readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined; } /** The path segment interface of the issue. */ interface PathSegment { /** The key representing a path segment. */ readonly key: PropertyKey; } /** The Standard Schema types interface. */ interface Types<Input = unknown, Output = Input> { /** The input type of the schema. */ readonly input: Input; /** The output type of the schema. */ readonly output: Output; } /** Infers the input type of a Standard Schema. */ type InferInput<Schema extends StandardSchemaV1> = NonNullable<Schema['~standard']['types']>['input']; /** Infers the output type of a Standard Schema. */ type InferOutput<Schema extends StandardSchemaV1> = NonNullable<Schema['~standard']['types']>['output']; } type DecodeResult<T> = Result<T, Annotation>; /** * A function taking a untrusted input, and returning a DecodeResult<T>. The * `ok()` and `err()` constructor functions are provided as the 2nd and 3rd * param. One of these should be called and its value returned. */ type AcceptanceFn<O, I = unknown> = (blob: I, ok: (value: O) => DecodeResult<O>, err: (msg: string | Annotation) => DecodeResult<O>) => DecodeResult<O>; type Next<O, I = unknown> = Decoder<O> | ((blob: I, ok: (value: O) => DecodeResult<O>, err: (msg: string | Annotation) => DecodeResult<O>) => DecodeResult<O> | Decoder<O>); interface Decoder<T> { /** * Verifies untrusted input. Either returns a value, or throws a decoding * error. */ verify(blob: unknown, formatterFn?: (ann: Annotation) => string | Error): T; /** * Verifies untrusted input. Either returns a value, or returns undefined. */ value(blob: unknown): T | undefined; /** * Verifies untrusted input. Always returns a DecodeResult, which is either * an "ok" value or an "error" annotation. */ decode(blob: unknown): DecodeResult<T>; /** * Build a new decoder from the the current one, with an extra acceptance * criterium. */ refine<N extends T>(predicate: (value: T) => value is N, msg: string): Decoder<N>; refine(predicate: (value: T) => boolean, msg: string): Decoder<T>; /** * Cast the return type of this read-only decoder to a narrower type. This is * useful to return "branded" types. This method has no runtime effect. */ refineType<SubT extends T>(): Decoder<SubT>; /** * Build a new decoder from the current one, with an extra rejection * criterium. */ reject(rejectFn: (value: T) => string | Annotation | null): Decoder<T>; /** * Build a new decoder from the current one, modifying its outputted value. */ transform<V>(transformFn: (value: T) => V): Decoder<V>; /** * Build a new decoder from the current one, with a mutated error message * in case of a rejection. */ describe(message: string): Decoder<T>; /** * Send the output of the current decoder into another decoder or acceptance * function. The given acceptance function will receive the output of the * current decoder as its input. * * > _**NOTE:** This is an advanced, low-level, API. It's not recommended * > to reach for this construct unless there is no other way. Most cases can * > be covered more elegantly by `.transform()`, `.refine()`, or `.pipe()` * > instead._ */ then<V>(next: Next<V, T>): Decoder<V>; /** * Send the output of this decoder as input to another decoder. * * This can be useful to validate the results of a transform, i.e.: * * string * .transform((s) => s.split(',')) * .pipe(array(nonEmptyString)) * * You can also conditionally pipe: * * string.pipe((s) => s.startsWith('@') ? username : email) */ pipe<V, D extends Decoder<V>>(next: D | ((blob: T) => D)): Decoder<DecoderType<D>>; /** * The Standard Schema interface for this decoder. */ '~standard': StandardSchemaV1.Props<unknown, T>; } /** * Helper type to return the output type of a Decoder. * It’s the inverse of Decoder<T>. * * You can use it at the type level: * * DecoderType<Decoder<string>> // string * DecoderType<Decoder<number[]>> // number[] * * Or on decoder instances, by using the `typeof` keyword: * * DecoderType<typeof string> // string * DecoderType<typeof truthy> // boolean */ type DecoderType<D extends Decoder<any>> = D extends Decoder<infer T> ? T : never; /** * Defines a new `Decoder<T>`, by implementing a custom acceptance function. * The function receives three arguments: * * 1. `blob` - the raw/unknown input (aka your external data) * 2. `ok` - Call `ok(value)` to accept the input and return ``value`` * 3. `err` - Call `err(message)` to reject the input with error ``message`` * * The expected return value should be a `DecodeResult<T>`, which can be * obtained by returning the result of calling the provided `ok` or `err` * helper functions. Please note that `ok()` and `err()` don't perform side * effects! You'll need to _return_ those values. */ declare function define<T>(fn: AcceptanceFn<T>): Decoder<T>; type Formatter = (err: Annotation) => string | Error; declare function formatInline(ann: Annotation): string; declare function formatShort(ann: Annotation): string; /** * Accepts any array, but doesn't validate its items further. * * "poja" means "plain old JavaScript array", a play on `pojo()`. */ declare const poja: Decoder<unknown[]>; /** * Accepts arrays of whatever the given decoder accepts. */ declare function array<T>(decoder: Decoder<T>): Decoder<T[]>; /** * Like `array()`, but will reject arrays with 0 elements. */ declare function nonEmptyArray<T>(decoder: Decoder<T>): Decoder<[T, ...T[]]>; type TupleDecoderType<Ds extends readonly Decoder<unknown>[]> = { [K in keyof Ds]: DecoderType<Ds[K]>; }; /** * Accepts a tuple (an array with exactly _n_ items) of values accepted by the * _n_ given decoders. */ declare function tuple<Ds extends readonly [first: Decoder<unknown>, ...rest: readonly Decoder<unknown>[]]>(...decoders: Ds): Decoder<TupleDecoderType<Ds>>; type Scalar = string | number | boolean | symbol | undefined | null; /** * Accepts and returns only the literal `null` value. */ declare const null_: Decoder<null>; /** * Accepts and returns only the literal `undefined` value. */ declare const undefined_: Decoder<undefined>; /** * Accepts whatever the given decoder accepts, or `undefined`. * * If a default value is explicitly provided, return that instead in the * `undefined` case. */ declare function optional<T>(decoder: Decoder<T>): Decoder<T | undefined>; declare function optional<T, C extends Scalar>(decoder: Decoder<T>, defaultValue: (() => C) | C): Decoder<NonNullable<T> | C>; declare function optional<T, V>(decoder: Decoder<T>, defaultValue: (() => V) | V): Decoder<NonNullable<T> | V>; /** * Accepts whatever the given decoder accepts, or `null`. * * If a default value is explicitly provided, return that instead in the `null` * case. */ declare function nullable<T>(decoder: Decoder<T>): Decoder<T | null>; declare function nullable<T, C extends Scalar>(decoder: Decoder<T>, defaultValue: (() => C) | C): Decoder<NonNullable<T> | C>; declare function nullable<T, V>(decoder: Decoder<T>, defaultValue: (() => V) | V): Decoder<NonNullable<T> | V>; /** * @deprecated Will get removed in a future version. * * Alias of `nullish()`. */ declare const maybe: typeof nullish; /** * Accepts whatever the given decoder accepts, or `null`, or `undefined`. * * If a default value is explicitly provided, return that instead in the * `null`/`undefined` case. */ declare function nullish<T>(decoder: Decoder<T>): Decoder<T | null | undefined>; declare function nullish<T, C extends Scalar>(decoder: Decoder<T>, defaultValue: (() => C) | C): Decoder<NonNullable<T> | C>; declare function nullish<T, V>(decoder: Decoder<T>, defaultValue: (() => V) | V): Decoder<NonNullable<T> | V>; /** * Accepts only the given constant value. */ declare function constant<C extends Scalar>(value: C): Decoder<C>; /** * Accepts anything, completely ignores it, and always returns the provided * value instead. * * This is useful to manually add extra fields to object decoders. */ declare function always<C extends Scalar>(value: C): Decoder<C>; declare function always<T>(value: (() => T) | T): Decoder<T>; /** * Rejects all inputs, and always fails with the given error message. May be * useful for explicitly disallowing keys, or for testing purposes. */ declare function never(msg: string): Decoder<never>; /** * Alias of `never()`. */ declare const fail: typeof never; /** * Alias of `always()`. * * @deprecated Will get removed in a future version. */ declare const hardcoded: typeof always; /** * Accepts anything and returns it unchanged. * * Useful for situation in which you don't know or expect a specific type. Of * course, the downside is that you won't know the type of the value statically * and you'll have to further refine it yourself. */ declare const unknown: Decoder<unknown>; /** * Alias of `unknown`. * * @deprecated Will get removed in a future version. */ declare const mixed: Decoder<unknown>; /** * Accepts and returns booleans. */ declare const boolean: Decoder<boolean>; /** * Accepts anything and will return its "truth" value. Will never reject. */ declare const truthy: Decoder<boolean>; /** * Accepts objects where all values match the given decoder, and returns the * result as a `Record<string, V>`. */ declare function record<V>(valueDecoder: Decoder<V>): Decoder<Record<string, V>>; /** * Accepts objects where all keys and values match the given decoders, and * returns the result as a `Record<K, V>`. The given key decoder must return * strings. */ declare function record<K extends string, V>(keyDecoder: Decoder<K>, valueDecoder: Decoder<V>): Decoder<Record<K, V>>; /** * @deprecated Will get removed in a future version. * * Alias of `record()`. */ declare const dict: typeof record; /** * Similar to `array()`, but returns the result as an [ES6 * Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set). */ declare function setFromArray<T>(decoder: Decoder<T>): Decoder<Set<T>>; /** * Renamed to `setFromArray` to make room for a future `set()` decoder that * works differently. * * @deprecated This decoder will change behavior in a future version. */ declare const set: typeof setFromArray; /** * Similar to `record()`, but returns the result as a `Map<string, T>` (an [ES6 * Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)) * instead. */ declare function mapping<T>(decoder: Decoder<T>): Decoder<Map<string, T>>; /** * Accepts and returns `Date` instances. */ declare const date: Decoder<Date>; /** * Accepts [ISO8601](https://en.wikipedia.org/wiki/ISO_8601)-formatted strings, * returns them as `Date` instances. * * This is very useful for working with dates in APIs: serialize them as * `.toISOString()` when sending, decode them with `iso8601` when receiving. */ declare const iso8601: Decoder<Date>; /** * Accepts either a Date, or an ISO date string, returns a Date instance. * This is commonly useful to build decoders that can be reused to validate * object with Date instances as well as objects coming from JSON payloads. */ declare const datelike: Decoder<Date>; type JSONValue = null | string | number | boolean | JSONObject | JSONArray; type JSONObject = { [key: string]: JSONValue | undefined; }; type JSONArray = JSONValue[]; /** * Accepts objects that contain only valid JSON values. */ declare const jsonObject: Decoder<JSONObject>; /** * Accepts arrays that contain only valid JSON values. */ declare const jsonArray: Decoder<JSONArray>; /** * Accepts any value that's a valid JSON value. * * In other words: any value returned by `JSON.parse()` should decode without * failure. * * ```typescript * type JSONValue = * | null * | string * | number * | boolean * | { [string]: JSONValue } * | JSONValue[] * ``` */ declare const json: Decoder<JSONValue>; type SizeOptions = { min?: number; max?: number; size?: number; }; interface Klass<T> extends Function { new (...args: readonly any[]): T; } type Instance<K> = K extends Klass<infer T> ? T : never; /** * Accepts any value that is an ``instanceof`` the given class. */ declare function instanceOf<K extends Klass<any>>(klass: K): Decoder<Instance<K>>; /** * Lazily evaluate the given decoder. This is useful to build self-referential * types for recursive data structures. */ declare function lazy<T>(decoderFn: () => Decoder<T>): Decoder<T>; /** * Pre-process the data input before passing it into the decoder. This gives * you the ability to arbitrarily customize the input on the fly before passing * it to the decoder. Of course, the input value at that point is still of * ``unknown`` type, so you will have to deal with that accordingly. */ declare function prep<T>(mapperFn: (blob: unknown) => unknown, decoder: Decoder<T>): Decoder<T>; /** * Accepts any valid ``number`` value. * * This also accepts special values like `NaN` and `Infinity`. Unless you * want to deliberately accept those, you'll likely want to use the * `number` decoder instead. */ declare const anyNumber: Decoder<number>; /** * Accepts finite numbers (can be integer or float values). Values `NaN`, * or positive and negative `Infinity` will get rejected. */ declare const number: Decoder<number>; /** * Accepts only finite whole numbers. */ declare const integer: Decoder<number>; /** * Accepts only non-negative (zero or positive) finite numbers. */ declare const positiveNumber: Decoder<number>; /** * Accepts only non-negative (zero or positive) finite whole numbers. */ declare const positiveInteger: Decoder<number>; /** * Accepts any valid ``bigint`` value. */ declare const bigint: Decoder<bigint>; type RequiredKeys<T extends object> = { [K in keyof T]: undefined extends T[K] ? never : K; }[keyof T]; type Resolve<T> = T extends (...args: readonly unknown[]) => unknown ? T : { [K in keyof T]: T[K]; }; /** * Transforms an object type, by marking all fields that contain "undefined" * with a question mark, i.e. allowing implicit-undefineds when * explicit-undefined are also allowed. * * For example, if: * * type User = { * name: string; * age: number | null | undefined; * } * * Then UndefinedToOptional<User> will become equivalent to: * * { * name: string; * age?: number | null | undefined; * ^ * Note the question mark * } */ type UndefinedToOptional<T extends object> = Resolve<Pick<Required<T>, RequiredKeys<T>> & Partial<T>>; type ObjectDecoderType<Ds extends Record<string, Decoder<unknown>>> = UndefinedToOptional<{ [K in keyof Ds]: DecoderType<Ds[K]>; }>; /** * Accepts any "plain old JavaScript object", but doesn't validate its keys or * values further. */ declare const pojo: Decoder<Record<string, unknown>>; /** * Accepts objects with fields matching the given decoders. Extra fields that * exist on the input object are ignored and will not be returned. */ declare function object(decoders: Record<any, never>): Decoder<Record<string, never>>; declare function object<Ds extends Record<string, Decoder<unknown>>>(decoders: Ds): Decoder<ObjectDecoderType<Ds>>; /** * Like `object()`, but will reject inputs that contain extra fields that are * not specified explicitly. */ declare function exact(decoders: Record<any, never>): Decoder<Record<string, never>>; declare function exact<Ds extends Record<string, Decoder<unknown>>>(decoders: Ds): Decoder<ObjectDecoderType<Ds>>; /** * Like `object()`, but will pass through any extra fields on the input object * unvalidated that will thus be of `unknown` type statically. */ declare function inexact(decoders: Record<any, never>): Decoder<Record<string, unknown>>; declare function inexact<Ds extends Record<string, Decoder<unknown>>>(decoders: Ds): Decoder<ObjectDecoderType<Ds> & Record<string, unknown>>; /** * Accepts and returns strings. */ declare const string: Decoder<string>; /** * Like `string`, but will reject the empty string or strings containing only whitespace. */ declare const nonEmptyString: Decoder<string>; /** * Accepts and returns strings that match the given regular expression. */ declare function regex(regex: RegExp, msg: string): Decoder<string>; /** * Accepts and returns strings that start with the given prefix. */ declare function startsWith<P extends string>(prefix: P): Decoder<`${P}${string}`>; /** * Accepts and returns strings that end with the given suffix. */ declare function endsWith<S extends string>(suffix: S): Decoder<`${string}${S}`>; /** * Accepts and returns strings that are syntactically valid email addresses. * (This will not mean that the email address actually exist.) */ declare const email: Decoder<string>; /** * Accepts strings that are valid URLs, returns the value as a URL instance. */ declare const url: Decoder<URL>; /** * Accepts strings that are valid URLs, but only HTTPS ones. Returns the value * as a URL instance. */ declare const httpsUrl: Decoder<URL>; /** * Accepts and returns strings that are valid identifiers in most programming * languages. */ declare const identifier: Decoder<string>; /** * Accepts and returns [nanoid](https://zelark.github.io/nano-id-cc) string * values. It assumes the default nanoid alphabet. If you're using a custom * alphabet, use `regex()` instead. */ declare function nanoid(options?: SizeOptions): Decoder<string>; /** * Accepts strings that are valid * [UUIDs](https://en.wikipedia.org/wiki/universally_unique_identifier) * (universally unique identifier). */ declare const uuid: Decoder<string>; /** * Like `uuid`, but only accepts * [UUIDv1](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_1_%28date-time_and_MAC_address%29) * strings. */ declare const uuidv1: Decoder<string>; /** * Like `uuid`, but only accepts * [UUIDv4](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_%28random%29) * strings. */ declare const uuidv4: Decoder<string>; /** * Accepts and returns strings with decimal digits only (base-10). * To convert these to numbers, use the `numeric` decoder. */ declare const decimal: Decoder<string>; /** * Accepts and returns strings with hexadecimal digits only (base-16). */ declare const hexadecimal: Decoder<string>; /** * Accepts valid numerical strings (in base-10) and returns them as a number. * To only accept numerical strings and keep them as string values, use the * `decimal` decoder. */ declare const numeric: Decoder<number>; /** * Accepts values accepted by any of the given decoders. * * The decoders are tried on the input one by one, in the given order. The * first one that accepts the input "wins". If all decoders reject the input, * the input gets rejected. */ declare function either<TDecoders extends readonly Decoder<unknown>[]>(...decoders: TDecoders): Decoder<DecoderType<TDecoders[number]>>; /** * Accepts any value that is strictly-equal (using `===`) to one of the * specified values. */ declare function oneOf<C extends Scalar>(constants: readonly C[]): Decoder<C>; /** * Accepts and return an enum value. */ declare function enum_<TEnum extends Record<string, string | number>>(enumObj: TEnum): Decoder<TEnum[keyof TEnum]>; /** * If you are decoding tagged unions you may want to use the `taggedUnion()` * decoder instead of the general purpose `either()` decoder to get better * error messages and better performance. * * This decoder is optimized for [tagged * unions](https://en.wikipedia.org/wiki/Tagged_union), i.e. a union of * objects where one field is used as the discriminator. * * ```ts * const A = object({ tag: constant('A'), foo: string }); * const B = object({ tag: constant('B'), bar: number }); * * const AorB = taggedUnion('tag', { A, B }); * // ^^^ * ``` * * Decoding now works in two steps: * * 1. Look at the `'tag'` field in the incoming object (this is the field * that decides which decoder will be used) * 2. If the value is `'A'`, then decoder `A` will be used. If it's `'B'`, then * decoder `B` will be used. Otherwise, this will fail. * * This is effectively equivalent to `either(A, B)`, but will provide better * error messages and is more performant at runtime because it doesn't have to * try all decoders one by one. */ declare function taggedUnion<O extends Record<string, Decoder<unknown>>>(field: string, mapping: O): Decoder<DecoderType<O[keyof O]>>; /** * Briefly peek at a runtime input using a "scout" decoder first, then decide * which decoder to run on the (original) input, based on the information that * the "scout" extracted. * * It serves a similar purpose as `taggedUnion()`, but is a generalization that * works even if there isn't a single discriminator, or the discriminator isn't * a string. */ declare function select<T, D extends Decoder<unknown>>(scout: Decoder<T>, selectFn: (result: T) => D): Decoder<DecoderType<D>>; export { type DecodeResult, type Decoder, type DecoderType, type Err, type Formatter, type JSONArray, type JSONObject, type JSONValue, type Ok, type Result, type Scalar, type SizeOptions, always, anyNumber, array, bigint, boolean, constant, date, datelike, decimal, define, dict, either, email, endsWith, enum_, err, exact, fail, formatInline, formatShort, hardcoded, hexadecimal, httpsUrl, identifier, inexact, instanceOf, integer, iso8601, json, jsonArray, jsonObject, lazy, mapping, maybe, mixed, nanoid, never, nonEmptyArray, nonEmptyString, null_, nullable, nullish, number, numeric, object, ok, oneOf, optional, poja, pojo, positiveInteger, positiveNumber, prep, record, regex, select, set, setFromArray, startsWith, string, taggedUnion, truthy, tuple, undefined_, unknown, url, uuid, uuidv1, uuidv4 };