typizator
Version:
Runtime types and metadata schemas for Typescript
311 lines (310 loc) • 12.2 kB
TypeScript
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 {};