UNPKG

@bscotch/schema-builder

Version:
240 lines (239 loc) 9.24 kB
/** * @file Extensions to TypeBox for convenience features * and specific use-cases. */ import { Defined } from '@bscotch/utility'; import { ErrorObject as AjvErrorObject, Options as AjvOptions, ValidateFunction } from 'ajv/dist/2019'; import { CustomOptions, SchemaDefs, TLiteralUnion, TRef, TSchema, TValue, TypeBuilder } from './typebox'; export * from './typebox'; /** * The {@link SchemaBuilder}'s root schema (what defines the * shape of the data it can validate), as a Typescript interface. * * @example * ```ts * const mySchema = new SchemaBuilder(); * mySchema.addDefinition('myDefinition', mySchema.String()); * mySchema.setRoot('myDefinition'); * * type MySchema = Static<typeof mySchema>; * //-> { myDefinition: string } * ``` */ export declare type Static<T extends SchemaBuilder<any, any> | TSchema | undefined> = T extends undefined ? never : T extends SchemaBuilder<any, infer Root> ? Root extends undefined ? never : Defined<Root>['$static'] : T extends TSchema ? T['$static'] : never; /** * The {@link SchemaBuilder}'s definitions (re-usable, * referenceable subschemas) as Typescript interfaces. * * @example * ```ts * const mySchema = new SchemaBuilder(); * mySchema.addDefinition('myDefinition', mySchema.String()); * * type MySchemaDefs = StaticDefs<typeof mySchema>; * // { myDefinition: string } * ``` */ export declare type StaticDefs<T extends SchemaBuilder<any, any>> = T extends SchemaBuilder<infer U, any> ? { [K in keyof U]: U[K] extends TSchema ? Static<U[K]> : never; } : never; /** * An ajv validator function built from the root schema of * a {@link SchemaBuilder} instance. */ export declare type SchemaValidator<Root extends TSchema | undefined> = Root extends undefined ? undefined : ValidateFunction<Static<Defined<Root>>>; export interface SchemaBuilderOptions<Defs extends SchemaDefs> { /** * Optionally initialize the SchemaBuilder using * an already-existing collection of schemas, * either from another SchemaBuilder or from a * plain `{[defName]: schema}` object. * * (This is no different from calling * {@link SchemaBuilder.addDefinitions} later.) */ lib?: Defs | SchemaBuilder<Defs, any>; /** * Optionally specify validator options for ajv. */ validatorOptions?: AjvOptions; } /** * A `SchemaBuilder` instance provides storage for schema * definitions (for use in `$ref`s) and for creating a Schema * that uses them, all with full Typescript support. * * The API is designed to enable * * (Powered by TypeBox) */ export declare class SchemaBuilder<Defs extends SchemaDefs = {}, Root extends TRef<Defs[keyof Defs]> | undefined = undefined> extends TypeBuilder { readonly $defs: Defs; protected _root: Root; /** * AJV Validator cache */ protected _validator?: SchemaValidator<Root>; validatorOptions: AjvOptions; constructor(options?: SchemaBuilderOptions<Defs>); /** * The root Schema, used for validation, if set by * {@link SchemaBuilder.setRoot}. */ get root(): Root; /** * Get the validator cached from the last call * to {@link SchemaBuilder.compileValidator}. * * If no validator has been cached, one will be * automatically created and cached with default options. */ get validate(): Root extends undefined ? never : SchemaValidator<Root>; /** * Create an AJV validator for the root schema, which will * include any defs on this SchemaBuilder and all of * the `ajv-formats`. Additional formats, keywords, * and other AJV options can be provided via the optional * `options` and `extensions` arguments. */ compileValidator(options?: AjvOptions): Root extends undefined ? never : SchemaValidator<Root>; /** * Test data against the root schema, returning * an array of errors or `undefined` if the data is valid. */ hasErrors<T>(data: T): T extends Static<Root> ? undefined : AjvErrorObject[]; /** * Test data against the root schema, throwing an error * if the check fails. If the data is valid, it is returned. */ assertIsValid<T>(data: T): T extends Static<Root> ? T : never; /** * Test data against the root schema, returning `true` * if it is valid, and `false` if it is not. */ isValid(data: any): data is Static<Root>; /** * Set one of this SchemaBuilder's definitions as the * "root" definition, which will cause a `$ref` schema * pointing to it to be used as the default output schema. */ setRoot<N extends keyof Defs>(defName: N): SchemaBuilder<Defs, TRef<Defs[N]>>; /** * Create a `$ref` reference to a schema definition that * this `SchemaBuilder` knows about * (e.g. it was provided via `addDefinition`). * * If no `name` argument is provided, returns the root * schema (set by {@link setRoot}). */ DefRef<N extends keyof Defs>(name?: N): N extends undefined ? Root extends TSchema ? Root : never : TRef<Defs[N]>; /** * Create an enum schema from an array of literals, * resulting in a union type of those values. */ LiteralUnion<T extends TValue[]>(items: [...T], options?: CustomOptions): TLiteralUnion<T>; /** * Find a definition schema by name. If no such * definition found, returns `undefined`. * * To throw an error instead, use {@link tryFindDef}. */ findDef<N extends string>(name: N): N extends keyof Defs ? Defs[N] : undefined; /** * Find a definition schema by name. If no such * definition found, throw an error. * * To return `undefined` instead of throwing, use * {@link findDef}. */ private tryFindDef; /** * Add a new schema to the stored definitions, for use * in local references for a final schema. */ addDefinition<N extends string, T extends TSchema>(name: N, schema: T | ((this: SchemaBuilder<Defs, Root>) => T)): SchemaBuilder<Defs & Record<N, T>, Root>; /** * Add a new schema to the stored definitions, for use * in local references for a final schema. */ addDefinitions<NewDefs extends SchemaDefs>(newDefs: NewDefs | ((this: SchemaBuilder<Defs, Root>) => NewDefs) | SchemaBuilder<NewDefs, any>): SchemaBuilder<Defs & NewDefs, Root>; /** * Like JavaScript's `Function.prototype.call()`, except that * `this` is already provided as this `SchemaBuilder` instance. * * This is useful when you want to reference a schema definition * created earlier in the chain that Typescript doesn't know about, * or when you want to create Schemas within the build-chain when * you don't have a variable name to reference. * * @example * * ```ts * const lib = new SchemaBuilder().addDefinition('aString', function () { * return this.String(); * }); * * const mySchema = new SchemaBuilder({lib}) * .use(function () { * return this.addDefinition('nums', this.LiteralUnion([1, 2, 3])) * .addDefinition('moreNums', this.Array(this.Number())) * .addDefinition('deeper', function () { * return this.Object({ * deepArray: this.Array(this.DefRef('nums')), * libRef: this.Array(this.DefRef('aString')), * }); * }); * }); * ``` */ use<Out>(func: (this: SchemaBuilder<Defs, Root>) => Out): Out; /** * @alias SchemaBuilder.WithDefs * * Any external functions that attempt to call `toJSON` on * objects during serialization (such as `JSON.stringify`) * will end up with the serialized root schema, which will * include all definitions in a `$defs` field. */ toJSON(): Root extends undefined ? never : Root & { $defs: Defs; }; /** * Given a schema, or reference to a schema definition on this * instance, return that schema with this builder's definitions * added to it. * * This is typically what you would save to file or use with a * validator, if you are not otherwise using convenience functionality * provided by {@link SchemaBuilder}. */ WithDefs(): Root extends undefined ? never : Root & { $defs: Defs; }; WithDefs<T extends TSchema | undefined>(schema?: T): T extends undefined ? Root extends undefined ? never : Root & { $defs: Defs; } : T & { $defs: Defs; }; /** * Write this SchemaBuilder's schemas to file as a valid * JSON Schema document, with definitions listed in a `$defs` * field alongside the root schema (if set). */ writeSchema<T extends TSchema>(outPath: string, schema?: T): Promise<this>; /** * Synchronous version of {@link writeSchema}. */ writeSchemaSync<T extends TSchema>(outPath: string, schema?: T): this; /** * Load data from file, ensuring that it is valid according * to the root schema. */ readData(path: string): Promise<Static<Root>>; /** * Synchronous version of {@link readData}. */ readDataSync(path: string): Static<Root>; private _readDataFile; private _write; }