@aurbi/ts-binding
Version:
bidirectionally bind serialized & simplified objects to full-featured runtime objects. kinda like a subset of zod, but it goes both ways.
216 lines (215 loc) • 9.11 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.nil = exports.boolean = exports.number = exports.string = void 0;
exports.any = any;
exports.validated = validated;
exports.optional = optional;
exports.extendObject = extendObject;
exports.literal = literal;
exports.record = record;
exports.array = array;
exports.object = object;
exports.union = union;
exports.document = document;
const errors_1 = require("./errors");
const serialization_1 = require("./serialization");
__exportStar(require("./serialization"), exports);
/** represents any type. use with care. */
function any() {
return {
transform: (object) => object,
restore: (json) => json
};
}
/** validate a value with given functions upon transformation/restoration */
function validated(validator, reason = () => 'Failed validation') {
return {
transform: (object, s) => {
if (!validator(object)) {
throw new errors_1.TransformationError(reason(object), s, object);
}
return object;
},
restore: (json, s) => {
if (!validator(json)) {
throw new errors_1.TransformationError(reason(object), s, object);
}
return json;
}
};
}
/** Make a provided type expression optional */
function optional(schema) {
return {
transform: (object, s) => {
return object === undefined ? undefined : schema.transform(object, s);
},
restore: (json, s) => {
return json === undefined ? undefined : schema.restore(json, s);
},
attributes: {
optional: true
}
};
}
/** Extend an existing object schema with another */
function extendObject(baseSchema, withSchema) {
return {
transform: (object, s) => {
return {
...baseSchema.transform(object, s),
...withSchema.transform(object, s)
};
},
restore: (json, s) => {
return {
...baseSchema.restore(json, s),
...withSchema.restore(json, s)
};
}
};
}
/** expresses a literal value of any type */
function literal(value) {
return (0, errors_1.stackwrap)(validated((v) => v === value), 'literal');
}
/** expresses a string value */
const string = () => (0, errors_1.stackwrap)(validated((v) => typeof v === 'string', v => `expected 'string', received '${typeof v}'`), 'string');
exports.string = string;
/** expresses a numeric value */
const number = () => (0, errors_1.stackwrap)(validated((v) => typeof v === 'number', v => `expected 'number', received '${typeof v}'`), 'number');
exports.number = number;
/** expresses a boolean value */
const boolean = () => (0, errors_1.stackwrap)(validated((v) => typeof v === 'boolean', v => `expected 'boolean', received '${typeof v}'`), 'boolean');
exports.boolean = boolean;
/** expresses a null value (called nil, because null is a reserved keyword) */
const nil = () => (0, errors_1.stackwrap)(validated((v) => v === null, v => `expected 'null', received '${typeof v}'`), 'nil');
exports.nil = nil;
/**
* Express an object with unknown key values and associated values
* @param keySchema expression of key type
* @param valueSchema expression of value type
*/
function record(keySchema, valueSchema) {
return {
transform: (object, s = (0, errors_1.stack)()) => {
return Object.fromEntries(Object.entries(object).map(([key, value]) => [
keySchema.transform(key, s.with(`record:transform['key of ${key}']`)),
valueSchema.transform(value, s.with(`record:transform[value of '${key}']`))
]));
},
restore: (json, s = (0, errors_1.stack)()) => {
return Object.fromEntries(Object.entries(json).map(([key, value]) => [
keySchema.restore(key, s.with(`record:restore['key of ${key}']`)),
valueSchema.restore(value, s.with(`record:restore[value of '${key}']`))
]));
}
};
}
/**
* Expresses an array of items all typed-alike.
* For arrays containing multiple types, you'll want to use `union` within this.
* @param itemSchema expression of array element
*/
function array(itemSchema) {
return {
transform: (array, s = (0, errors_1.stack)()) => {
return array.map((e, i) => itemSchema.transform(e, s.with(`array:transform[${i}]`)));
},
restore: (array, s = (0, errors_1.stack)()) => {
if (!Array.isArray(array)) {
throw new errors_1.TransformationError('Not an array', s.with('array:restore'));
}
return array.map((e, i) => itemSchema.restore(e, s.with(`array:restore[${i}]`)));
}
};
}
/**
* Expresses an object with a known structure.
* If you're looking for an object with unknown keys, use `record`.
* @param schemaObject Object prototype expressing the structure and expressed types therewithin
*/
function object(schemaObject) {
return {
transform: (object, s = (0, errors_1.stack)()) => {
return Object.fromEntries(Object.entries(schemaObject).map(([key, value]) => [key, value.transform(object[key], s.with(`object:transform['${key}']`))]));
},
restore: (json, s = (0, errors_1.stack)()) => {
var _a, _b;
if (typeof json !== 'object' || Array.isArray(json) || json === null) {
const t = Array.isArray(json) ? 'array' : typeof json;
throw new errors_1.TransformationError(`Not an object. Expected 'object', got '${t}'`, s.with('object:transform'));
}
for (const key of Object.keys(schemaObject)) {
if (!(key in json) && ((_b = (_a = schemaObject[key]) === null || _a === void 0 ? void 0 : _a.attributes) === null || _b === void 0 ? void 0 : _b.optional) !== true) {
throw new errors_1.TransformationError(`Missing required object key '${key}'`, s.with('object:transform'));
}
}
return Object.fromEntries(Object.entries(schemaObject).map(([key, value]) => [key, value.restore(json[key], s.with(`object:restore['${key}']`))]));
}
};
}
/**
* Expresses a discriminated union.
* @param discriminators validation functions that either return the selected type expression or false
*/
function union(...discriminators) {
return {
transform: (object, s = (0, errors_1.stack)()) => {
for (let i = 0; i < discriminators.length; i++) {
const discriminator = discriminators[i];
const discriminated = discriminator(object);
if (discriminated === false) {
continue;
}
return discriminated.transform(object, s.with(`union:transform[${i}]`));
}
throw new errors_1.TransformationError('No matching union discriminator', s.with('union:transform'));
},
restore: (json, s = (0, errors_1.stack)()) => {
for (let i = 0; i < discriminators.length; i++) {
const discriminator = discriminators[i];
const discriminated = discriminator(json);
if (discriminated === false) {
continue;
}
return discriminated.restore(json, s.with(`union:restore[${i}]`));
}
throw new errors_1.TransformationError('No matching union discriminator', s.with('union:restore'));
}
};
}
/**
* Express a serialized string that matches a specific schema.
* Unpacked into an object of matching type during transformation,
* and re-packed into a serialized string during restoration.
* @param schema expression of serialized type
*/
function document(schema, { serializer, deserializer } = serialization_1.DefaultSerializationConfig) {
return {
transform: (object, s = (0, errors_1.stack)()) => {
const literal = schema.transform(object, s.with('document:transform'));
const transformed = serializer(literal);
return transformed;
},
restore: (str, s = (0, errors_1.stack)()) => {
const literal = deserializer(str);
const restored = schema.restore(literal, s.with('document:restore'));
return restored;
}
};
}
;