UNPKG

typizator

Version:

Runtime types and metadata schemas for Typescript

311 lines (310 loc) 12.2 kB
import { InferSourceForDictionary, InferSourceFromSchema, InferTargetForDictionary, InferTargetFromSchema, SchemaSource, SchemaTarget } from "./type-conversions"; export type DefaultBehaviour = { allowNull: boolean; optional: boolean; }; interface NotNull extends DefaultBehaviour { allowNull: false; } interface Optional extends DefaultBehaviour { optional: true; allowNull: true; } type AllowNull<T, B extends DefaultBehaviour> = B extends NotNull ? T : B extends Optional ? T | null | undefined : T | null; /** * Metadata for the schema type */ export interface TypedMetadata { /** * Data type */ dataType: string; /** * True if null is accepted by the object */ notNull: boolean; /** * True if the object is optional, i.e. the field can be absent or the object undefined */ optional: boolean; /** * True if the object has a default/validation rule defined */ hasDefaultRule?: boolean; } /** * Facade exposing a map child schemas of the schema object */ export type FieldsMap = { /** * Get the field's schema by field name * @param fieldName Field's name * @returns Schema object for the named field or undefined if the field does not exist */ get: (fieldName: string) => Schema | undefined; /** * Executes for each key of the underlying object * @param func Function to execute for each field of the object */ forEach: (func: (fieldName: string, schema: Schema) => void) => void; /** * Executes for each key of the underlying object and returns the value for each iteration * @param func Function to execute for each field of the object * @returns Array of objects returned from each consecutive field */ map: <T>(func: (fieldName: string, schema: Schema) => T) => T[]; /** * Returns only the fields matching the condition implemented by the function * Function returning true * @param func Function checking each field in the object. If true, the schema is added to the resulting array */ filter: (func: (fieldName: string, schema: Schema) => boolean) => { key: string; schema: Schema; }[]; /** * Returns the number of fields (and thus field schemas) returned by the object */ get size(): number; }; /** * Metadata for the object schema */ export interface ObjectMetadata extends TypedMetadata { dataType: "object"; /** * Map of schemas for every field of the object */ fields: FieldsMap; } /** * Metadata for the array schema */ export interface ArrayMetadata extends TypedMetadata { dataType: "array"; /** * Schema for every element of the array. Only one schema because all the array elements are of the same type */ elements: Schema; } /** * Metadata for the dictionary schema */ export interface DictionaryMetadata extends TypedMetadata { dataType: "dictionary"; /** * Schema for every value of the dictionary. Only one schema because all the dictionary elements are of the same type */ values: Schema; } /** * Setting that define the way the object described by the schema is unboxed */ export type UnboxingProperties = { /** * True if the "null" string is interpreted as a string. Otherwise it is unboxed as null */ keepNullString?: boolean; /** * True if the "undefined" string is interpreted as a string. Otherwise it is unboxed as undefined */ keepUndefinedString?: boolean; }; export type MetadataForSchema<T> = T extends ObjectS<any> ? ObjectMetadata : T extends ArrayS<any> ? ArrayMetadata : T extends DictionaryS<any> ? DictionaryMetadata : TypedMetadata; /** * Base for all schemas defining their common behaviour */ export interface Schema<Target = any, Sources = any, B extends DefaultBehaviour = DefaultBehaviour, Original extends Schema = any> { /** * Runtime information about the schema object */ metadata: MetadataForSchema<Original>; /** * Converts the loosely-typed source to the exact type defined by the schema * @param source Source that can be of a type that can be converted to one defined by the schema * @param props Unboxing options. By default, "null" string is unboxed as null, but "undefined" string as an "undefined" string * @returns Value converted to the type managed by the schema */ unbox: (source: AllowNull<Sources, B>, props?: UnboxingProperties) => AllowNull<Target, B>; } /** * Schema for complex types */ export interface ExtendedSchema<Target = any, Sources = any, B extends DefaultBehaviour = DefaultBehaviour> extends Schema<Target, Sources, B> { /** * Indicates that the unboxed value cannot be null. Forbids null source values if true */ notNull: NotNullFacade<Target, Sources, B, this>; /** * Indicates that the unboxed value can be undefined or omitted if it is an object's field */ optional: OptionalFacade<Target, Sources, B, this>; /** * Indicates the default unboxing behaviour of the schema depending on the source unboxing value * @param target Either a default value to set or a function returning that default value, or an error to throw, in which case this is acting as a validator * @param condition Function defining the condition when the default value is applied (or the error is thrown). By default, applied when the source is null */ byDefault: (target: Target | Error | ((s: Sources) => Target), condition?: (source: Sources) => boolean) => ByDefaultFacade<Target, Sources, B, this>; } /** * Facade schema enforcing the not null restriction on any underlying unboxed object */ export interface NotNullFacade<Target, Sources, B extends DefaultBehaviour, Original extends Schema<Target, Sources, B>> extends Schema<Target, Sources, NotNull, Original> { /** * This flag can be used for discriminators */ readonly notNullFlag: true; /** * Extends the object schema with additional fields * @param definition Object containing fields with type definitions * @returns Extended object schema */ extend: Original extends ObjectS<infer X> ? <R extends SchemaDefinition>(definition: R) => ObjectS<X & R> : never; } /** * Facade schema allowing unboxed values to be undefined and making related object fields optional */ export interface OptionalFacade<Target, Sources, B extends DefaultBehaviour, Original extends Schema<Target, Sources, B>> extends Schema<Target, Sources, Optional, Original> { /** * This flag can be used for discriminators */ readonly optionalFlag: true; /** * Extends the object schema with additional fields * @param definition Object containing fields with type definitions * @returns Extended object schema */ extend: Original extends ObjectS<infer X> ? <R extends SchemaDefinition>(definition: R) => ObjectS<X & R> : never; } export interface ByDefaultFacade<Target, Sources, B extends DefaultBehaviour, Original extends Schema<Target, Sources, B>> extends Schema<Target, Sources, B, Original> { optional: OptionalFacade<Target, Sources, B, Original>; } /** * Abstract schema type, parent class of all the base schema definitions */ export declare abstract class TypeSchema<Target = any, Sources = any, B extends DefaultBehaviour = { allowNull: true; optional: false; }> implements ExtendedSchema<Target, Sources, B> { #private; abstract get metadata(): TypedMetadata; protected abstract convert: (source: Sources, props?: UnboxingProperties) => Target; /** {@inheritdoc Schema.unbox} */ unbox: (source: AllowNull<Sources, B>, props?: UnboxingProperties) => AllowNull<Target, B>; /** {@inheritdoc ExtendedSchema.unbox} */ get notNull(): NotNullFacade<Target, Sources, B, this>; /** {@inheritdoc ExtendedSchema.unbox} */ get optional(): OptionalFacade<Target, Sources, B, this>; /** {@inheritdoc ExtendedSchema.unbox} */ byDefault: (target: Error | Target | ((s: Sources) => Target), condition?: (source: Sources) => boolean) => ByDefaultFacade<Target, Sources, B, this>; } /** * Defines how a complex object's schema must be structured */ export interface SchemaDefinition { [K: string]: Schema; } /** * Utility type to get the type hidden behind a .notNull or .optional facade extension */ export type ExtractFromFacade<T> = T extends NotNullFacade<any, any, any, infer S> ? S : T extends OptionalFacade<any, any, any, infer S> ? S : T; /** * Gets the schema's signature as a string * @param schema Schema to obtain the string signature for * @returns Signature detailing the schema's structure */ export declare const getSchemaSignature: <T extends SchemaDefinition, B extends DefaultBehaviour>(schema: Schema<SchemaTarget<T>, SchemaSource<T>, B> | T) => string; export type RecursiveS = { isRecursive: true; } & Schema; export declare const recursiveS: RecursiveS; /** * Object schema representing a Typescript/Javascript object */ export interface ObjectS<T extends SchemaDefinition> extends ExtendedSchema<SchemaTarget<T>, SchemaSource<T>> { /** * Extended metadata containing names and schemas of object's fields */ get metadata(): ObjectMetadata; /** * Extends the object schema with additional fields * @param definition Object containing fields with type definitions * @returns Extended object schema */ extend: <R extends SchemaDefinition>(definition: R) => ObjectS<T & R>; } /** * Returns an object schema representing a Typescript/Javascript object with typed fields * @param definition Object containing fields with type definitions * @returns Object schema with typed fields schemas * @example The object schema defined like this: * ```ts * objectS({ * id: intS.notNull, * name: stringS, * valid: boolS.optional * }) * ``` * ...represents the Typescript object defined as * ```ts * { * id: number, * name: string | null, * valid?: boolean | null * } * ``` */ export declare const objectS: <T extends SchemaDefinition>(definition: T) => ObjectS<T>; /** * Schema representing an array */ export interface ArrayS<S extends Schema> extends ExtendedSchema<InferTargetFromSchema<S>[], InferSourceFromSchema<S>[] | string> { /** * Extended metadata containing the information about the array elements' types */ get metadata(): ArrayMetadata; } /** * Returns a schema object representing the array of elements of the same type * @param elements Schema type for each array element * @returns Object schema representing an array of containing type * @example A schema defined like this: * ```ts * arrayS(stringS.notNull) * ``` * ...represents an object defined as * ```ts * string[] | null * ``` */ export declare const arrayS: <S extends Schema<any, any, DefaultBehaviour, any>>(elements: S) => ArrayS<S>; /** * Utility type returning an object schema even if the underlying object is `objectS(...).notNull` or `objectS(...).optional` */ export type ObjectOrFacadeS<T extends SchemaDefinition> = ObjectS<T> | NotNullFacade<SchemaTarget<T>, SchemaSource<T>, DefaultBehaviour, ObjectS<T>>; /** * Schema representing a string-to-object dictionary */ export interface DictionaryS<V extends Schema> extends ExtendedSchema<InferTargetForDictionary<V>, InferSourceForDictionary<V> | string> { /** * Extended metadata containing the information about the dictionary values' types */ get metadata(): DictionaryMetadata; } /** * Returns a schema object representing the string-to-object dictionary of elements of the same type * @param elements Schema type for each dictionary value * @returns Object schema representing an dictionary of values of containing type * @example A schema defined like this: * ```ts * dictionaryS(intS.notNull) * ``` * ...represents an object defined as * ```ts * { * [K:string]: number * } | null * ``` */ export declare const dictionaryS: <S extends Schema<any, any, DefaultBehaviour, any>>(values: S) => DictionaryS<S>; export {};