@bscotch/schema-builder
Version:
Tools for creating and managing JSON Schemas.
240 lines (239 loc) • 9.24 kB
TypeScript
/**
* @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;
}