type-assurance
Version:
Lightweight type guards and assertions
126 lines • 3.92 kB
JavaScript
;
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