/** * Schemable presents a unified algebra for parsing, decoding * guarding, or otherwise building typed js structures in * TypeScript. * * @module Schemable * @since 2.0.0 */ import "./_dnt.polyfills.js"; import type { $, Hold, Kind, Spread } from "./kind.js"; import type { NonEmptyArray } from "./array.js"; import type { ReadonlyRecord } from "./record.js"; /** * These are the super-types that a Literal schema must extent. * They are used to constrain the inputs for LiteralSchemable. * * @since 2.0.0 */ export type Literal = string | number | boolean | null | undefined; /** * Wraps an unknown type in Schemable. This is the best escape * hatch when a Schema isn't known ahead of time. * * @since 2.0.0 */ export interface UnknownSchemable extends Hold { readonly unknown: () => $; } /** * Wraps a string type in Schemable. * * @since 2.0.0 */ export interface StringSchemable extends Hold { readonly string: () => $; } /** * Wraps a number type in Schemable. * * @since 2.0.0 */ export interface NumberSchemable extends Hold { readonly number: () => $; } /** * Wraps a boolean type in Schemable. * * @since 2.0.0 */ export interface BooleanSchemable extends Hold { readonly boolean: () => $; } /** * Wraps a union of literals in Schemable. * * @since 2.0.0 */ export interface LiteralSchemable extends Hold { readonly literal: , B, C, D, E>(...s: A) => $; } /** * Takes a Schemable and returns a Schemable; * * @since 2.0.0 */ export interface NullableSchemable extends Hold { readonly nullable: (or: $) => $; } /** * Takes a Schemable and returns a Schemable; * * @since 2.0.0 */ export interface UndefinableSchemable extends Hold { readonly undefinable: (or: $) => $; } /** * Takes a Schemable and returns a Schemable> * * @since 2.0.0 */ export interface RecordSchemable extends Hold { readonly record: (codomain: $) => $, B, C], [D], [E]>; } /** * Takes a Schemable and returns a Schemable> * * @since 2.0.0 */ export interface ArraySchemable extends Hold { readonly array: (item: $) => $, B, C], [D], [E]>; } /** * Takes a tuple of Schemables and returns a Schemable * for a tuple that matches, index for index, the input * schemables. * * ie. [StringSchemable, NumberSchemable] becomes * Schemable<[string, number]> * * @since 2.0.0 */ export interface TupleSchemable extends Hold { readonly tuple: (...items: { readonly [K in keyof A]: $; }) => $; } /** * Takes a struct of Schemables and returns a Schemable * for a struct that matches, key for key, the input * schemables. * * ie. { str: StringSchemable, num: NumberSchemable } becomes * Schemable<{ str: string, num: number }> * * @since 2.0.0 */ export interface StructSchemable extends Hold { readonly struct: (items: { readonly [K in keyof A]: $; }) => $; } /** * Takes a struct of Schemables and returns a Schemable * for a struct that matches, key for key, the input * schemables but the values can also be partial. * * ie. { str: StringSchemable, num: NumberSchemable } becomes * Schemable<{ str?: string, num?: number }> * * @since 2.0.0 */ export interface PartialSchemable extends Hold { readonly partial: (items: { readonly [K in keyof A]: $; }) => $; } /** * Takes two schemables, left and right, and returns * the intersection of them. This means that any value * for must match both schemables. * * @since 2.0.0 */ export interface IntersectSchemable extends Hold { readonly intersect: (right: $) => (left: $) => $, B, C], [D], [E]>; } /** * Takes two schemables, left and right, and returns * the union of them. This means that any value * for must match either schemable. * * @since 2.0.0 */ export interface UnionSchemable extends Hold { readonly union: (right: $) => (left: $) => $; } /** * Takes an id and a thunk returning a schemable and * returns a schemable that matches the return value * of the thunk. This schemable is necessary for * handling recursive or corecursive schemables. * * @since 2.0.0 */ export interface LazySchemable extends Hold { readonly lazy: (id: string, f: () => $) => $; } /** * A Schemable is the union of all schemable methods. * This allows one to build an arbitrary Schema using * the Schemable interface, then pass a concrete * Schemable implementation to the Schema. Thus, one * can build a single model and produce decoders, * guards, or jsonschema from that model. * * @since 2.0.0 */ export interface Schemable extends UnknownSchemable, StringSchemable, NumberSchemable, BooleanSchemable, LiteralSchemable, NullableSchemable, UndefinableSchemable, RecordSchemable, ArraySchemable, TupleSchemable, StructSchemable, PartialSchemable, IntersectSchemable, UnionSchemable, LazySchemable, Hold { } /** * A Schema is the a function that takes a generic schemable and builds * a specific model from it. * * @since 2.0.0 */ export type Schema = (S: Schemable) => $; /** * Extracts the inner type of a Schema * * @since 2.0.0 */ export type TypeOf = T extends Schema ? A : unknown; type InferSchema = (S: Schemable) => $; /** * A helper function to build a generic Schema that can be used * with any Schemable. * * @example * ```ts * import { schema, TypeOf } from "./schemable.ts"; * import { SchemableDecoder } from "./decoder.ts"; * import { SchemableRefinement } from "./refinement.ts"; * import { SchemableJsonBuilder, print } from "./json_schema.ts"; * import { pipe } from "./fn.ts"; * * const mySchema = schema(s => pipe( * s.struct({ * name: s.string(), * age: s.number(), * }), * s.intersect(s.partial({ * interests: s.array(s.string()), * })) * )); * * // Derive the type from the schema * type MySchema = TypeOf; * * const decode = mySchema(SchemableDecoder); * const refine = mySchema(SchemableRefinement); * const jsonSchema = mySchema(SchemableJsonBuilder); * * const unknown1 = { * name: "Batman", * age: 45, * interests: ["crime fighting", "cake", "bats"], * }; * const unknown2 = { * name: "Cthulhu", * interests: ["madness"], * }; * * const decoded1 = decode(unknown1); // Success! * const decoded2 = decode(unknown2); // Failure with info * * const refine1 = refine(unknown1); // true * const refine2 = refine(unknown2); // false * * const jsonSchemaString = pipe( * jsonSchema, * print, * json => JSON.stringify(json, null, 2), * ); // Turns the jsonSchema into a prettified string * * ``` * * @since 2.0.0 */ export declare function schema(s: InferSchema): Schema; export {};