@jsonjoy.com/json-type
Version:
High-performance JSON Pointer implementation
265 lines (264 loc) • 9.09 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypeBuilder = void 0;
const tslib_1 = require("tslib");
const schema = tslib_1.__importStar(require("../schema"));
const classes = tslib_1.__importStar(require("./classes"));
const { s } = schema;
class TypeBuilder {
constructor(system) {
this.system = system;
// --------------------------------------------------------------- shorthands
this.undefined = () => this.undef;
this.null = () => this.nil;
this.boolean = () => this.bool;
this.number = () => this.num;
this.string = () => this.str;
this.binary = () => this.bin;
this.con = (value, options) => this.Const(value, options);
this.literal = this.con;
this.array = (type, options) => this.Array((type ?? this.any), options);
this.tuple = (...types) => this.Tuple(...types);
/**
* Creates an object type with the specified properties. This is a shorthand for
* `t.Object(t.prop(key, value), ...)`.
*
* Importantly, this method does not allow to specify object field order,
* so the order of properties in the resulting type is not guaranteed.
*
* Example:
*
* ```ts
* t.object({
* id: t.str,
* name: t.string(),
* age: t.num,
* verified: t.bool,
* });
* ```
*
* @param record A mapping of property names to types.
* @returns An object type.
*/
this.object = (record) => {
const fields = [];
for (const [key, value] of Object.entries(record))
fields.push(this.prop(key, value));
const obj = new classes.ObjType(fields);
obj.system = this.system;
return obj;
};
/**
* Creates a type that represents a value that may be present or absent. The
* value is `undefined` if absent. This is a shorthand for `t.Or(type, t.undef)`.
*/
this.maybe = (type) => this.Or(type, this.undef);
/**
* Creates a union type from a list of values. This is a shorthand for
* `t.Or(t.Const(value1), t.Const(value2), ...)`. For example, the below
* are equivalent:
*
* ```ts
* t.enum('red', 'green', 'blue');
* t.Or(t.Const('red'), t.Const('green'), t.Const('blue'));
* ```
*
* @param values The values to include in the union.
* @returns A union type representing the values.
*/
this.enum = (...values) => this.Or(...values.map((type) => this.Const(type)));
}
// -------------------------------------------------------------- empty types
get any() {
return this.Any();
}
get undef() {
return this.Const(undefined);
}
get nil() {
return this.Const(null);
}
get bool() {
return this.Boolean();
}
get num() {
return this.Number();
}
get str() {
return this.String();
}
get bin() {
return this.Binary(this.any);
}
get arr() {
return this.Array(this.any);
}
get obj() {
return this.Object();
}
get map() {
return this.Map(this.any);
}
get fn() {
return this.Function(this.undef, this.undef);
}
get fn$() {
return this.Function$(this.undef, this.undef);
}
// --------------------------------------------------- base node constructors
Any(options) {
const type = new classes.AnyType(s.Any(options));
type.system = this.system;
return type;
}
Const(value, options) {
const type = new classes.ConType(schema.s.Const(value, options));
type.system = this.system;
return type;
}
Boolean(options) {
const type = new classes.BoolType(s.Boolean(options));
type.system = this.system;
return type;
}
Number(options) {
const type = new classes.NumType(s.Number(options));
type.system = this.system;
return type;
}
String(options) {
const type = new classes.StrType(s.String(options));
type.system = this.system;
return type;
}
Binary(type, options = {}) {
const bin = new classes.BinType(type, options);
bin.system = this.system;
return bin;
}
Array(type, options) {
const arr = new classes.ArrType(type, options);
arr.system = this.system;
return arr;
}
Tuple(...types) {
const tup = new classes.TupType(types);
tup.system = this.system;
return tup;
}
Object(...fields) {
const obj = new classes.ObjType(fields);
obj.system = this.system;
return obj;
}
prop(key, value) {
const field = new classes.ObjectFieldType(key, value);
field.system = this.system;
return field;
}
propOpt(key, value) {
const field = new classes.ObjectOptionalFieldType(key, value);
field.system = this.system;
return field;
}
Map(val, key, options) {
const map = new classes.MapType(val, key, options);
map.system = this.system;
return map;
}
Or(...types) {
const or = new classes.OrType(types);
or.system = this.system;
return or;
}
Ref(ref) {
const type = new classes.RefType(ref);
type.system = this.system;
return type;
}
Function(req, res, options) {
const fn = new classes.FnType(req, res, options);
fn.system = this.system;
return fn;
}
Function$(req, res, options) {
const fn = new classes.FnRxType(req, res, options);
fn.system = this.system;
return fn;
}
import(node) {
switch (node.kind) {
case 'any':
return this.Any(node);
case 'bool':
return this.Boolean(node);
case 'num':
return this.Number(node);
case 'str':
return this.String(node);
case 'bin':
return this.Binary(this.import(node.type), node);
case 'arr':
return this.Array(this.import(node.type), node);
case 'tup':
return this.Tuple(...node.types.map((t) => this.import(t))).options(node);
case 'obj': {
return this.Object(...node.fields.map((f) => f.optional
? this.propOpt(f.key, this.import(f.value)).options(f)
: this.prop(f.key, this.import(f.value)).options(f))).options(node);
}
case 'map':
return this.Map(this.import(node.value), node.key ? this.import(node.key) : undefined, node);
case 'con':
return this.Const(node.value).options(node);
case 'or':
return this.Or(...node.types.map((t) => this.import(t))).options(node);
case 'ref':
return this.Ref(node.ref).options(node);
case 'fn':
return this.Function(this.import(node.req), this.import(node.res)).options(node);
case 'fn$':
return this.Function$(this.import(node.req), this.import(node.res)).options(node);
}
throw new Error(`UNKNOWN_NODE [${node.kind}]`);
}
from(value) {
switch (typeof value) {
case 'undefined':
return this.undef;
case 'boolean':
return this.bool;
case 'number':
return this.num;
case 'string':
return this.str;
case 'object':
if (value === null)
return this.nil;
if (Array.isArray(value)) {
if (value.length === 0)
return this.arr;
const getType = (v) => {
switch (typeof v) {
case 'object':
if (v === null)
return 'nil';
if (Array.isArray(v))
return 'arr';
return 'obj';
default:
return typeof v;
}
};
const allElementsOfTheSameType = value.every((v) => getType(v) === getType(value[0]));
return allElementsOfTheSameType
? this.Array(this.from(value[0]))
: this.Tuple(...value.map((v) => this.from(v)));
}
return this.Object(...Object.entries(value).map(([key, value]) => this.prop(key, this.from(value))));
default:
return this.any;
}
}
}
exports.TypeBuilder = TypeBuilder;