UNPKG

@jsonjoy.com/json-type

Version:

High-performance JSON Pointer implementation

430 lines (429 loc) 13.4 kB
import type { Expr } from '@jsonjoy.com/json-expression'; import type { Mutable } from '@jsonjoy.com/util/lib/types'; import type { Observable } from 'rxjs'; import type { Display } from './common'; export interface SchemaBase<Value = unknown> extends Display { /** * 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?: SchemaExample<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. */ info?: string; }; /** * Custom metadata that can be attached to the type. This is useful for * documentation generation, and for custom code generators. */ metadata?: Record<string, unknown>; } /** * An example of how a value of a given type could look like. */ export interface SchemaExample<Value = unknown> extends Display { value: Value; } /** * Represents something of which type is not known. * * Example: * * ```json * { * "kind": "any", * "metadata": { * "description": "Any type" * } * } * ``` */ export interface AnySchema extends SchemaBase<unknown> { kind: 'any'; } /** * Represents a constant value. * Example: * ```json * { * "kind": "con", * "value": 42 * } * ``` */ export interface ConSchema<V = any> extends SchemaBase { kind: 'con'; /** The value. */ value: V; } /** * Represents a JSON boolean. * * Example: * * ```json * { * "kind": "bool", * "meta": { * "description": "A boolean value" * } * } * ``` */ export interface BoolSchema extends SchemaBase<boolean> { kind: 'bool'; } /** * Represents a JSON number. * * Example: * * ```json * { * "kind": "num", * "format": "i32", * "gte": 0, * "lte": 100 * } * ``` */ export interface NumSchema extends SchemaBase<number> { 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 StrSchema extends SchemaBase<string> { 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 BinSchema<T extends SchemaBase = any> extends SchemaBase { 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 ArrSchema<T extends SchemaBase = any, Head extends SchemaBase[] = any, Tail extends SchemaBase[] = any> extends SchemaBase<Array<unknown>> { kind: 'arr'; /** One or more "one-of" types that array contains. */ type?: T; /** Head tuple types. */ head?: Head; /** Tail tuple types. */ tail?: Tail; /** Minimum number of elements. */ min?: number; /** Maximum number of elements. */ max?: number; } /** * 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", * "keys": [ * { * "kind": "key", * "key": "name", * "type": { * "kind": "str" * }, * "optional": false * }, * { * "kind": "key", * "key": "age", * "type": { * "kind": "num", * "gte": 0 * }, * "optional": true * } * ], * "decodeUnknownKeys": false * } * ``` */ export interface ObjSchema<Keys extends KeySchema<string, SchemaBase>[] | readonly KeySchema<string, SchemaBase>[] = any> extends SchemaBase<object> { kind: 'obj'; /** * Sorted list of keys this object contains. Although object keys in JSON * are not guaranteed to be in any particular order, this list is sorted so * that the order of keys is consistent when generating documentation or code. */ keys: Keys; /** * List of types this object extends. When this type is used as part of the * module, the `extends` field is used to determine which other type aliases * this type extends. The other type alias MUST be of `obj` kind. An object * can extend multiple other types. The fields of the extended types are * deeply copied into this type, in order specified by the `extends` array: * first type in the array is copied first, then the second, and so on. */ extends?: string[]; /** * Whether the object may have keys that are not explicitly defined in the * "keys" 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", * "keys": [], * "decodeUnknownKeys": true * } * ``` */ decodeUnknownKeys?: boolean; encodeUnknownKeys?: boolean; } /** * Represents a single field of an object. * * @todo Rename to `key`. */ export interface KeySchema<K extends string = string, V extends SchemaBase = SchemaBase> extends SchemaBase<[K, V]>, Display { kind: 'key'; /** Key name of the field. */ key: K; /** * Type of the field value. */ value: V; optional?: boolean; } export interface OptKeySchema<K extends string = string, V extends SchemaBase = SchemaBase> extends KeySchema<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 SchemaBase = any, K extends SchemaBase = any> extends SchemaBase<Record<string, unknown>> { 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 SchemaBase = SchemaBase> extends SchemaBase { 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 SchemaBase[] = SchemaBase[]> extends SchemaBase { 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 FnSchema<Req extends SchemaBase = SchemaBase, Res extends SchemaBase = SchemaBase, Ctx = unknown> extends SchemaBase { kind: 'fn'; req: Req; res: Res; __ctx_brand?: Ctx; } export type FnStreamingValue<Req, Res, Ctx = unknown> = (req: Observable<Req>, ctx?: Ctx) => Observable<Res>; export interface FnRxSchema<Req extends SchemaBase = SchemaBase, Res extends SchemaBase = SchemaBase, Ctx = unknown> extends SchemaBase { /** @todo Rename to `fn`. Make it a property on the schema instead. */ kind: 'fn$'; req: Req; res: Res; __ctx_brand?: Ctx; } export interface AliasSchema extends KeySchema { pub?: boolean; } export interface ModuleSchema<Aliases extends AliasSchema[] = AliasSchema[]> extends SchemaBase { kind: 'module'; keys: Aliases; } export type TypeMap = { [alias: string]: Schema; }; /** * Any valid JSON type. */ export type JsonSchema = BoolSchema | NumSchema | StrSchema | BinSchema | ArrSchema | ConSchema | ObjSchema | KeySchema | OptKeySchema | MapSchema; export type Schema = JsonSchema | RefSchema | OrSchema | AnySchema | FnSchema | FnRxSchema | AliasSchema | ModuleSchema | KeySchema | OptKeySchema; export type NoT<T extends SchemaBase> = 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 BoolSchema ? boolean : T extends NumSchema ? number : T extends StrSchema ? string : T extends ArrSchema<infer U, infer Head, infer Tail> ? [ ...{ [K in keyof Head]: TypeOf<Head[K]>; }, ...(Schema extends U ? [] : TypeOf<U>[]), ...(Tail extends JsonSchema[] ? { [K in keyof Tail]: TypeOf<Tail[K]>; } : []) ] : T extends ConSchema<infer U> ? U : T extends KeySchema<any, infer V> ? TypeOf<V> : T extends ObjSchema<infer F> ? NoEmptyInterface<TypeFields<Mutable<F>>> : T extends MapSchema<infer U> ? Record<string, TypeOf<U>> : T extends BinSchema ? Uint8Array : T extends FnSchema<infer Req, infer Res, infer Ctx> ? (req: TypeOf<Req>, ctx: Ctx) => UndefToVoid<TypeOf<Res>> | Promise<UndefToVoid<TypeOf<Res>>> : T extends FnRxSchema<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 KeySchema<infer K, any> ? [K, F] : never; type NoEmptyInterface<I> = keyof I extends never ? Record<string, never> : I; type OptionalFields<T> = { [K in keyof T]-?: T[K] extends OptKeySchema ? 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 KeySchema<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 {};