UNPKG

type-assurance

Version:

Lightweight type guards and assertions

126 lines 3.92 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.is = is; exports.diff = diff; exports.typeGuard = typeGuard; exports.union = union; exports.optional = optional; exports.unknown = unknown; exports.assert = assert; exports.record = record; /** * Marker for optional properties. */ const OPTIONAL = "type-assurance:optional"; /** * Type guard to check if a value is a constructor function. * * NOTE: There is no way to tell wether a constructable function is intended * to be called as constructor. This guard therefore checks if the function * name starts with an uppercase letter. */ function isConstructor(fn) { return (typeof fn === "function" && fn.prototype?.constructor === fn && fn.name.charAt(0) === fn.name.charAt(0).toUpperCase()); } /** * Type guard to check if a value is compatible with a given schema. */ function is(value, schema) { return diff(value, schema).length === 0; } /** * Compares a value to a schema and returns all property paths * where the data does not match the specified type. */ function diff(value, schema, path = "value") { const t = (v) => (v ? [] : [path]); if (schema === String) return t(typeof value === "string"); if (schema === Number) return t(typeof value === "number"); if (schema === Boolean) return t(typeof value === "boolean"); if (Array.isArray(schema)) { if (!Array.isArray(value)) return t(false); if (!schema.length) return t(true); if (schema.length > 1) { // tuple const mismatch = value.flatMap((v, i) => diff(v, schema[i], `${path}[${i}]`)); if (mismatch.length) return mismatch; return t(value.length === schema.length); } else { return value.flatMap((v, i) => diff(v, schema[0], `${path}[${i}]`)); } } if (typeof schema === "object" && schema) { if (!value || typeof value !== "object") return t(false); return Object.keys(schema).flatMap((k) => //@ts-ignore diff(value[k], schema[k], `${path}.${k}`)); } if (typeof schema === "function") { if (isConstructor(schema)) { return t(value instanceof schema); } else { return t(schema(value)); } } return t(value === schema); } /** * Creates a type guard that checks if a value matches the given schema. */ function typeGuard(schema) { return (value) => is(value, schema); } /** * Creates a type guard that checks if a value matches any of the given schemas. */ function union(...schemas) { return (v) => schemas.some((schema) => is(v, schema)); } /** * Creates a type guard that checks if a value either matches the given schema or is undefined. * The returned function is marked with the `type-assurance:optional` marker – when used as value inside an * object, the property will become optional. */ function optional(schema) { const guard = union(schema, undefined); guard.optional = true; return guard; } /** * Type guard that always returns `true`. Can be used to create schemas * where the type of a property does not matter. */ function unknown(v) { return true; } /** * Asserts that a value matches a given schema. * @throws TypeError if the value does not match the schema. */ function assert(value, schema) { const mismatch = diff(value, schema); if (mismatch.length) { throw new TypeError(`${mismatch[0]} does not match the schema.`); } } /** * Creates a type guard that checks if a value matches a given Record<K, V>. */ function record(key, value) { return (v) => typeof v === "object" && v !== null && Object.values(v).every((y) => is(y, value)) && Object.keys(v).every((y) => is(y, key)); } //# sourceMappingURL=index.js.map