UNPKG

@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
"use strict"; 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; } }; }