@jsonjoy.com/json-type
Version:
High-performance JSON Pointer implementation
419 lines (418 loc) • 12.9 kB
TypeScript
import type { Observable } from 'rxjs';
import type { Mutable } from '@jsonjoy.com/util/lib/types';
import type { Display, Identifiable } from './common';
import type { Expr } from '@jsonjoy.com/json-expression';
export interface TType<Value = unknown> extends Display, Partial<Identifiable> {
/**
* The type of the JSON Type node.
*/
kind: string;
/**
* Custom metadata that can be attached to the type. This is useful for
* documentation generation, and for custom code generators. The `meta` field
* is not used by the JSON Type system itself.
*/
meta?: Record<string, unknown>;
/**
* Default value for this type. This may be used when the value is not provided
* during validation or serialization. The default value should match the
* type of this schema node.
*/
default?: Value;
/**
* List of example usages of this type.
*/
examples?: TExample<Value>[];
/**
* A flag that indicates that this type is deprecated. When a type is
* deprecated, it should not be used in new code, and existing code should be
* updated to use a non-deprecated type.
*/
deprecated?: {
/**
* A message that explains why the type is deprecated, and what to use
* instead.
*/
description?: string;
};
}
/**
* An example of how a value of a given type could look like.
*/
export interface TExample<Value = unknown> extends Display {
value: Value;
}
/**
* A type for which an explicit validation function can be applied.
*/
export interface WithValidator {
/** Name of the validation to apply. */
validator?: string | string[];
}
/**
* Represents something of which type is not known.
*
* Example:
*
* ```json
* {
* "kind": "any",
* "metadata": {
* "description": "Any type"
* }
* }
* ```
*/
export interface AnySchema extends TType<unknown>, WithValidator {
kind: 'any';
/**
* Custom metadata that can be attached to the type. This is useful for
* documentation generation, and for custom code generators.
*/
metadata?: Record<string, unknown>;
}
/**
* Represents a JSON boolean.
*
* Example:
*
* ```json
* {
* "kind": "bool",
* "meta": {
* "description": "A boolean value"
* }
* }
* ```
*/
export interface BooleanSchema extends TType<boolean>, WithValidator {
kind: 'bool';
}
/**
* Represents a JSON number.
*
* Example:
*
* ```json
* {
* "kind": "num",
* "format": "i32",
* "gte": 0,
* "lte": 100
* }
* ```
*/
export interface NumberSchema extends TType<number>, WithValidator {
kind: 'num';
/**
* A more specific format of the number. When this is set, faster compiled
* serialization functions can generate. "i" stands for signed integer, "u"
* for unsigned integer, and "f" for float.
*
* - "i" is signed integer.
* - "i8" is 8-bit signed integer.
* - "i16" is 16-bit signed integer.
* - "i32" is 32-bit signed integer.
* - "i64" is 64-bit signed integer.
* - "u" is unsigned integer.
* - "u8" is 8-bit unsigned integer.
* - "u16" is 16-bit unsigned integer.
* - "u32" is 32-bit unsigned integer.
* - "u64" is 64-bit unsigned integer.
* - "f" is float.
* - "f32" is 32-bit float.
* - "f64" is 64-bit float.
*/
format?: 'i' | 'u' | 'f' | 'i8' | 'i16' | 'i32' | 'i64' | 'u8' | 'u16' | 'u32' | 'u64' | 'f32' | 'f64';
/** Minimum value. */
gt?: number;
/** Minimum value, inclusive. */
gte?: number;
/** Maximum value. */
lt?: number;
/** Maximum value, inclusive. */
lte?: number;
}
/**
* Represents a JSON string.
*
* Example:
*
* ```json
* {
* "kind": "str",
* "format": "utf8",
* "min": 1,
* "max": 255
* }
* ```
*/
export interface StringSchema extends TType<string>, WithValidator {
kind: 'str';
/**
* String format specification. When set, the string value will be validated
* according to the specified format for maximum performance.
*
* - "ascii" - Only ASCII characters (0-127) are allowed
* - "utf8" - Valid UTF-8 encoded strings are allowed
*/
format?: 'ascii' | 'utf8';
/**
* When set to true, means that the string can contain only ASCII characters.
* This enables a range of optimizations, such as using a faster JSON
* serialization, faster binary serialization.
*
* @deprecated Use `format: 'ascii'` instead.
*/
ascii?: boolean;
/**
* When set to `true`, a faster JSON serialization function can be
* generated, which does not escape special JSON string characters.
* See: https://www.json.org/json-en.html
*/
noJsonEscape?: boolean;
/** Minimum number of characters. */
min?: number;
/** Maximum number of characters. */
max?: number;
}
/**
* Represents a binary type.
*
* Example:
*
* ```json
* {
* "kind": "bin",
* "type": {
* "kind": "str"
* },
* "format": "json",
* "min": 10,
* "max": 1024
* }
* ```
*/
export interface BinarySchema<T extends TType = any> extends TType, WithValidator {
kind: 'bin';
/** Type of value encoded in the binary data. */
type: T;
/** Codec used for encoding the binary data. */
format?: 'json' | 'cbor' | 'msgpack' | 'resp3' | 'ion' | 'bson' | 'ubjson' | 'bencode';
/** Minimum size in octets. */
min?: number;
/** Maximum size in octets. */
max?: number;
}
/**
* Represents a JSON array.
*
* Example:
*
* ```json
* {
* "kind": "arr",
* "type": {
* "kind": "num"
* },
* "min": 1,
* "max": 10
* }
* ```
*/
export interface ArraySchema<T extends TType = any> extends TType<Array<unknown>>, WithValidator {
kind: 'arr';
/** One or more "one-of" types that array contains. */
type: T;
/** Minimum number of elements. */
min?: number;
/** Maximum number of elements. */
max?: number;
}
/**
* Represents a constant value.
* Example:
* ```json
* {
* "kind": "con",
* "value": 42
* }
* ```
*/
export interface ConstSchema<V = any> extends TType, WithValidator {
kind: 'con';
/** The value. */
value: V;
}
/**
* Represents a JSON array.
*/
export interface TupleSchema<T extends TType[] = any> extends TType, WithValidator {
kind: 'tup';
types: T;
}
/**
* Represents a JSON object type, the "object" type excluding "null" in JavaScript,
* the "object" type in JSON Schema, and the "obj" type in MessagePack.
* Example:
* ```json
* {
* "kind": "obj",
* "fields": [
* {
* "kind": "field",
* "key": "name",
* "type": {
* "kind": "str"
* },
* "optional": false
* },
* {
* "kind": "field",
* "key": "age",
* "type": {
* "kind": "num",
* "gte": 0
* },
* "optional": true
* }
* ],
* "unknownFields": false
* }
* ```
*/
export interface ObjectSchema<Fields extends ObjectFieldSchema<string, TType>[] | readonly ObjectFieldSchema<string, TType>[] = any> extends TType<object>, WithValidator {
kind: 'obj';
/**
* Sorted list of fields this object contains. Although object fields in JSON
* are not guaranteed to be in any particular order, this list is sorted so
* that the order of fields is consistent when generating documentation or code.
*/
fields: Fields;
/**
* Whether the object may have fields that are not explicitly defined in the
* "fields" list. This setting is similar to "additionalProperties" in JSON
* Schema. Defaults to false.
*
* To define an object with of unknown shape use the following annotation:
*
* ```json
* {
* "kind": "obj",
* "fields": [],
* "unknownFields": true
* }
* ```
*
* @deprecated
* @todo Rename ot `decodeUnknownFields`.
*/
unknownFields?: boolean;
encodeUnknownFields?: boolean;
}
/**
* Represents a single field of an object.
*/
export interface ObjectFieldSchema<K extends string = string, V extends TType = TType> extends TType<[K, V]>, Display {
kind: 'field';
/** Key name of the field. */
key: K;
/**
* Type of the field value.
*/
value: V;
optional?: boolean;
}
export interface ObjectOptionalFieldSchema<K extends string = string, V extends TType = TType> extends ObjectFieldSchema<K, V> {
optional: true;
}
/**
* Represents an object, which is treated as a map. All keys are strings and all
* values are of the same type.
*/
export interface MapSchema<V extends TType = any, K extends TType = any> extends TType<Record<string, unknown>>, WithValidator {
kind: 'map';
/**
* Type of all keys in the map. Defaults to string type.
*/
key?: K;
/**
* Type of all values in the map.
*/
value: V;
}
/**
* Reference to another type.
*/
export interface RefSchema<T extends TType = TType> extends TType {
kind: 'ref';
/** ID of the type it references. */
ref: string & T;
}
/**
* Represents a type that is one of a set of types.
*/
export interface OrSchema<T extends TType[] = TType[]> extends TType {
kind: 'or';
/** One or more "one-of" types. */
types: T;
discriminator: Expr;
}
export type FunctionValue<Req, Res, Ctx = unknown> = (req: Req, ctx?: Ctx) => Res | Promise<Res>;
export interface FunctionSchema<Req extends TType = TType, Res extends TType = TType, Ctx = unknown> extends TType {
kind: 'fn';
req: Req;
res: Res;
__ctx_brand?: Ctx;
}
export type FunctionStreamingValue<Req, Res, Ctx = unknown> = (req: Observable<Req>, ctx?: Ctx) => Observable<Res>;
export interface FunctionStreamingSchema<Req extends TType = TType, Res extends TType = TType, Ctx = unknown> extends TType {
/** @todo Rename to `fn`. Make it a property on the schema instead. */
kind: 'fn$';
req: Req;
res: Res;
__ctx_brand?: Ctx;
}
export interface TypeSystemSchema {
types: {};
}
/**
* Any valid JSON type.
*/
export type JsonSchema = BooleanSchema | NumberSchema | StringSchema | BinarySchema | ArraySchema | ConstSchema | TupleSchema | ObjectSchema | ObjectFieldSchema | ObjectOptionalFieldSchema | MapSchema;
export type Schema = JsonSchema | RefSchema | OrSchema | AnySchema | FunctionSchema | FunctionStreamingSchema;
export type NoT<T extends TType> = Omit<T, 'kind'>;
export type TypeOf<T> = T extends OrSchema<any> ? TypeOfValue<T['types'][number]> : T extends RefSchema<infer U> ? TypeOf<U> : T extends AnySchema ? unknown : TypeOfValue<T>;
export type TypeOfValue<T> = T extends BooleanSchema ? boolean : T extends NumberSchema ? number : T extends StringSchema ? string : T extends ArraySchema<infer U> ? TypeOf<U>[] : T extends ConstSchema<infer U> ? U : T extends TupleSchema<infer U> ? {
[K in keyof U]: TypeOf<U[K]>;
} : T extends ObjectSchema<infer F> ? NoEmptyInterface<TypeFields<Mutable<F>>> : T extends MapSchema<infer U> ? Record<string, TypeOf<U>> : T extends BinarySchema ? Uint8Array : T extends FunctionSchema<infer Req, infer Res, infer Ctx> ? (req: TypeOf<Req>, ctx: Ctx) => UndefToVoid<TypeOf<Res>> | Promise<UndefToVoid<TypeOf<Res>>> : T extends FunctionStreamingSchema<infer Req, infer Res, infer Ctx> ? (req$: Observable<TypeOf<Req>>, ctx: Ctx) => Observable<UndefToVoid<TypeOf<Res>>> : never;
export type TypeOfMap<M extends Record<string, Schema>> = {
[K in keyof M]: TypeOf<M[K]>;
};
type TypeFields<F> = TypeOfFieldMap<FieldsAdjustedForOptional<ToObject<{
[K in keyof F]: ObjectFieldToTuple<F[K]>;
}>>>;
type ToObject<T> = T extends [string, unknown][] ? {
[K in T[number] as K[0]]: K[1];
} : never;
type ObjectFieldToTuple<F> = F extends ObjectFieldSchema<infer K, infer V> ? [K, F] : never;
type NoEmptyInterface<I> = keyof I extends never ? Record<string, never> : I;
type OptionalFields<T> = {
[K in keyof T]-?: T[K] extends ObjectOptionalFieldSchema ? K : never;
}[keyof T];
type RequiredFields<T> = Exclude<keyof T, OptionalFields<T>>;
type FieldsAdjustedForOptional<T> = Pick<T, RequiredFields<T>> & Partial<Pick<T, OptionalFields<T>>>;
type TypeOfFieldMap<T> = {
[K in keyof T]: TypeOf<FieldValue<T[K]>>;
};
type FieldValue<F> = F extends ObjectFieldSchema<any, infer V> ? V : never;
type UndefToVoid<T> = T extends undefined ? void : T;
export type OptionalProps<T extends object> = Exclude<{
[K in keyof T]: T extends Record<K, T[K]> ? never : K;
}[keyof T], undefined>;
export type Optional<T extends object> = Pick<T, OptionalProps<T>>;
export type Required<T extends object> = Omit<T, OptionalProps<T>>;
export type Narrow<T> = (T extends infer U ? U : never) | Extract<T, number | string | boolean | bigint | symbol | null | undefined | []> | ([T] extends [[]] ? [] : {
[K in keyof T]: Narrow<T[K]>;
});
export {};