@studnicky/predicates
Type guards, numeric checks, string bounds, array constraints, and coercion helpers aligned to JSON Schema draft 2020-12.
Install
bash
pnpm add @studnicky/predicatesRequires @studnicky:registry=https://npm.pkg.github.com in .npmrc.
Usage
Type checks
ts
import { Predicates } from '../src/index.js';
// inferValueType maps JS values to JSON Schema type names
const nullType = Predicates.inferValueType(null);
const arrayType = Predicates.inferValueType([]);
const objectType = Predicates.inferValueType({});
const stringType = Predicates.inferValueType('hello');
const numberType = Predicates.inferValueType(42);
const booleanType = Predicates.inferValueType(true);
console.log('inferValueType(null):', nullType); // 'null'
console.log('inferValueType([]):', arrayType); // 'array'
console.log('inferValueType({}):', objectType); // 'object'
console.log('inferValueType(hello):', stringType); // 'string'
console.log('inferValueType(42):', numberType); // 'number'
console.log('inferValueType(true):', booleanType); // 'boolean'
// isFiniteNumber and isIntegerValue narrow numeric primitives
console.log('isFiniteNumber(42):', Predicates.isFiniteNumber(42)); // true
console.log('isFiniteNumber(Infinity):', Predicates.isFiniteNumber(Infinity)); // false
console.log('isIntegerValue(3):', Predicates.isIntegerValue(3)); // true
console.log('isIntegerValue(3.14):', Predicates.isIntegerValue(3.14)); // false
// matchesType dispatches to per-type matchers
console.log('matchesType integer 5:', Predicates.matchesType('integer', 5)); // true
console.log('matchesType number 5.5:', Predicates.matchesType('number', 5.5)); // true
console.log('matchesType object []:', Predicates.matchesType('object', [])); // false
// matchesAnyType is a union check: true if any type in the list matches
console.log('matchesAnyType string|null null:', Predicates.matchesAnyType(['string', 'null'], null)); // true
console.log('matchesAnyType string|null 42:', Predicates.matchesAnyType(['string', 'null'], 42)); // falseString validation
ts
// codePointLength counts Unicode scalar values, not UTF-16 code units
console.log('codePointLength hello:', Predicates.codePointLength('hello')); // 5
console.log('codePointLength 👋:', Predicates.codePointLength('👋')); // 1
console.log('codePointLength 👋👋:', Predicates.codePointLength('👋👋')); // 2
// satisfiesMinLength / satisfiesMaxLength operate on code-point counts
console.log('satisfiesMinLength hello 5:', Predicates.satisfiesMinLength('hello', 5)); // true
console.log('satisfiesMinLength hi 5:', Predicates.satisfiesMinLength('hi', 5)); // false
console.log('satisfiesMaxLength hello 5:', Predicates.satisfiesMaxLength('hello', 5)); // true
console.log('satisfiesMaxLength hello 4:', Predicates.satisfiesMaxLength('hello', 4)); // false
// satisfiesPattern accepts a pre-compiled RegExp
console.log('satisfiesPattern abc123:', Predicates.satisfiesPattern('abc123', WORD_PATTERN)); // true
console.log('satisfiesPattern abc 123:', Predicates.satisfiesPattern('abc 123', WORD_PATTERN)); // falseNumeric validation
ts
// checkMinimum / checkMaximum with inclusive and exclusive modes
console.log('checkMinimum 5 5 false:', Predicates.checkMinimum(5, 5, false)); // true (inclusive)
console.log('checkMinimum 5 5 true:', Predicates.checkMinimum(5, 5, true)); // false (exclusive)
console.log('checkMaximum 10 10 false:', Predicates.checkMaximum(10, 10, false)); // true
console.log('checkMaximum 10 10 true:', Predicates.checkMaximum(10, 10, true)); // false
// checkMultipleOf is epsilon-tolerant for floating-point rounding
console.log('checkMultipleOf 0.3 0.1:', Predicates.checkMultipleOf(0.3, 0.1)); // true
console.log('checkMultipleOf 9 3:', Predicates.checkMultipleOf(9, 3)); // true
console.log('checkMultipleOf 10 3:', Predicates.checkMultipleOf(10, 3)); // falseArray validation
ts
// satisfiesMinItems / satisfiesMaxItems test array length
console.log('satisfiesMinItems [1,2,3] 3:', Predicates.satisfiesMinItems([1, 2, 3], 3)); // true
console.log('satisfiesMinItems [1,2] 3:', Predicates.satisfiesMinItems([1, 2], 3)); // false
console.log('satisfiesMaxItems [1,2] 3:', Predicates.satisfiesMaxItems([1, 2], 3)); // true
console.log('satisfiesMaxItems [1,2,3,4] 3:', Predicates.satisfiesMaxItems([1, 2, 3, 4], 3)); // false
// satisfiesUniqueItems uses deep equality for each pair
console.log('satisfiesUniqueItems [1,2,3]:', Predicates.satisfiesUniqueItems([1, 2, 3])); // true
console.log('satisfiesUniqueItems [1,2,1]:', Predicates.satisfiesUniqueItems([1, 2, 1])); // false
console.log('satisfiesUniqueItems [{a:1},{a:1}]:', Predicates.satisfiesUniqueItems([{ 'a': 1 }, { 'a': 1 }])); // false
// satisfiesContains validates minContains/maxContains bounds against a pre-counted match total
console.log('satisfiesContains 2 1 3:', Predicates.satisfiesContains(2, 1, 3)); // true
console.log('satisfiesContains 0 1 3:', Predicates.satisfiesContains(0, 1, 3)); // false
console.log('satisfiesContains 1 undef undef:', Predicates.satisfiesContains(1, undefined, undefined)); // trueObject validation
ts
// hasAllRequiredProperties verifies all listed keys exist in the object
console.log('hasAllRequiredProperties {a,b} [a,b]:', Predicates.hasAllRequiredProperties({ 'a': 1, 'b': 2 }, ['a', 'b'])); // true
console.log('hasAllRequiredProperties {a} [a,b]:', Predicates.hasAllRequiredProperties({ 'a': 1 }, ['a', 'b'])); // false
// hasNoAdditionalProperties checks that every key belongs to the allowed set
console.log('hasNoAdditionalProperties {a} Set[a,b]:', Predicates.hasNoAdditionalProperties({ 'a': 1 }, new Set(['a', 'b']))); // true
console.log('hasNoAdditionalProperties {a,c} Set[a,b]:', Predicates.hasNoAdditionalProperties({ 'a': 1, 'c': 3 }, new Set(['a', 'b']))); // false
// satisfiesMinProperties / satisfiesMaxProperties count own enumerable keys
console.log('satisfiesMinProperties {a,b} 2:', Predicates.satisfiesMinProperties({ 'a': 1, 'b': 2 }, 2)); // true
console.log('satisfiesMaxProperties {a,b,c} 2:', Predicates.satisfiesMaxProperties({ 'a': 1, 'b': 2, 'c': 3 }, 2)); // falseEnum and const
ts
// satisfiesEnum uses deep equality against each candidate
console.log('satisfiesEnum red:', Predicates.satisfiesEnum('red', ['red', 'green', 'blue'])); // true
console.log('satisfiesEnum yellow:', Predicates.satisfiesEnum('yellow', ['red', 'green', 'blue'])); // false
console.log('satisfiesEnum {x:1}:', Predicates.satisfiesEnum({ 'x': 1 }, [{ 'x': 1 }, { 'x': 2 }])); // true
// satisfiesConst uses deep equality for a single fixed value
console.log('satisfiesConst {x:1} {x:1}:', Predicates.satisfiesConst({ 'x': 1 }, { 'x': 1 })); // true
console.log('satisfiesConst {x:1} {x:2}:', Predicates.satisfiesConst({ 'x': 1 }, { 'x': 2 })); // falseCoercion
ts
// coerceToBoolean: recognised literals only; undefined for anything else
console.log('coerceToBoolean true:', Predicates.coerceToBoolean('true')); // true
console.log('coerceToBoolean 1:', Predicates.coerceToBoolean('1')); // true
console.log('coerceToBoolean maybe:', Predicates.coerceToBoolean('maybe')); // undefined
// coerceToNumber: finite numbers only; undefined for NaN / Infinity / non-numeric
console.log('coerceToNumber 3.14:', Predicates.coerceToNumber('3.14')); // 3.14
console.log('coerceToNumber NaN:', Predicates.coerceToNumber('NaN')); // undefined
console.log('coerceToNumber Infinity:', Predicates.coerceToNumber('Infinity')); // undefined
// coerceValue tries each schema type in order, returns first successful coercion
const coercedInt = Predicates.coerceValue(['integer', 'string'], '42');
console.log('coerceValue integer|string 42:', coercedInt); // 42Content encoding and media type
ts
// satisfiesContentEncoding validates base64 and base64url; unknown encodings pass
console.log('satisfiesContentEncoding base64:', Predicates.satisfiesContentEncoding('aGVsbG8=', 'base64')); // true
console.log('satisfiesContentEncoding bad base64:', Predicates.satisfiesContentEncoding('not-base64!!!', 'base64')); // false
console.log('satisfiesContentEncoding base64url:', Predicates.satisfiesContentEncoding('aGVsbG8', 'base64url')); // true
console.log('satisfiesContentEncoding unknown:', Predicates.satisfiesContentEncoding('anything', 'custom-encoding')); // true
// satisfiesContentMediaType validates application/json; unknown media types pass
console.log('satisfiesContentMediaType json:', Predicates.satisfiesContentMediaType('{"x":1}', 'application/json')); // true
console.log('satisfiesContentMediaType bad json:', Predicates.satisfiesContentMediaType('{bad json}', 'application/json')); // false
console.log('satisfiesContentMediaType text/plain:', Predicates.satisfiesContentMediaType('anything', 'text/plain')); // trueTry it
JSON Schema type inference and numeric guards
/** typeInference — inferValueType, isFiniteNumber, isIntegerValue, matchesType, matchesAnyType. Run: npx tsx examples/typeInference.ts */
import assert from 'node:assert/strict';
// #region usage
import { Predicates } from '../src/index.js';
// inferValueType maps JS values to JSON Schema type names
const nullType = Predicates.inferValueType(null);
const arrayType = Predicates.inferValueType([]);
const objectType = Predicates.inferValueType({});
const stringType = Predicates.inferValueType('hello');
const numberType = Predicates.inferValueType(42);
const booleanType = Predicates.inferValueType(true);
console.log('inferValueType(null):', nullType); // 'null'
console.log('inferValueType([]):', arrayType); // 'array'
console.log('inferValueType({}):', objectType); // 'object'
console.log('inferValueType(hello):', stringType); // 'string'
console.log('inferValueType(42):', numberType); // 'number'
console.log('inferValueType(true):', booleanType); // 'boolean'
// isFiniteNumber and isIntegerValue narrow numeric primitives
console.log('isFiniteNumber(42):', Predicates.isFiniteNumber(42)); // true
console.log('isFiniteNumber(Infinity):', Predicates.isFiniteNumber(Infinity)); // false
console.log('isIntegerValue(3):', Predicates.isIntegerValue(3)); // true
console.log('isIntegerValue(3.14):', Predicates.isIntegerValue(3.14)); // false
// matchesType dispatches to per-type matchers
console.log('matchesType integer 5:', Predicates.matchesType('integer', 5)); // true
console.log('matchesType number 5.5:', Predicates.matchesType('number', 5.5)); // true
console.log('matchesType object []:', Predicates.matchesType('object', [])); // false
// matchesAnyType is a union check: true if any type in the list matches
console.log('matchesAnyType string|null null:', Predicates.matchesAnyType(['string', 'null'], null)); // true
console.log('matchesAnyType string|null 42:', Predicates.matchesAnyType(['string', 'null'], 42)); // false
// #endregion usage
assert.equal(nullType, 'null');
assert.equal(arrayType, 'array');
assert.equal(objectType, 'object');
assert.equal(stringType, 'string');
assert.equal(numberType, 'number');
assert.equal(booleanType, 'boolean');
assert.equal(Predicates.isFiniteNumber(42), true);
assert.equal(Predicates.isFiniteNumber(Infinity), false);
assert.equal(Predicates.isFiniteNumber(NaN), false);
assert.equal(Predicates.isFiniteNumber('42'), false);
assert.equal(Predicates.isIntegerValue(3), true);
assert.equal(Predicates.isIntegerValue(3.14), false);
assert.equal(Predicates.matchesType('null', null), true);
assert.equal(Predicates.matchesType('array', [1, 2]), true);
assert.equal(Predicates.matchesType('integer', 5), true);
assert.equal(Predicates.matchesType('integer', 5.5), false);
assert.equal(Predicates.matchesType('number', 5.5), true);
assert.equal(Predicates.matchesType('object', {}), true);
assert.equal(Predicates.matchesType('object', []), false);
assert.equal(Predicates.matchesType('string', 'x'), true);
assert.equal(Predicates.matchesType('boolean', false), true);
assert.equal(Predicates.matchesAnyType(['string', 'null'], null), true);
assert.equal(Predicates.matchesAnyType(['string', 'null'], 'hi'), true);
assert.equal(Predicates.matchesAnyType(['string', 'null'], 42), false);
assert.equal(Predicates.matchesAnyType(['integer', 'boolean'], true), true);
console.log('typeInference: all assertions passed');
Output
Press Execute to run this example against the real library.The output shows inferValueType mapping JS values to JSON Schema type names, isFiniteNumber/isIntegerValue narrowing numerics, and matchesType/matchesAnyType performing union-aware dispatch.
API
| Export | Type | Description |
|---|---|---|
Predicates | class | Static-only predicate and coercion library |
CoerceToBooleanResultType | type | boolean | undefined |
CoerceToNumberResultType | type | number | undefined |
Selected Predicates static methods
| Method | Description |
|---|---|
isFiniteNumber(value) | True for finite number values |
isIntegerValue(value) | True for integer number values |
inferValueType(value) | Returns JSON Schema type name ('null', 'array', 'object', etc.) |
matchesType(schemaType, value) | True if value satisfies the named JSON Schema type |
matchesAnyType(schemaTypes, value) | True if value satisfies any type in the list |
coerceToBoolean(value) | Coerces string to boolean; returns undefined for unrecognised literals |
coerceToNumber(value) | Coerces string to finite number; returns undefined for non-numeric input |
coerceValue(schemaTypes, value) | Tries each coercer in order; returns first successful result |
codePointLength(str) | Counts Unicode code points without allocating |
satisfiesMinLength(str, min) | Fast-path code-point length check |
satisfiesMaxLength(str, max) | Fast-path code-point length check |
checkMultipleOf(value, divisor) | Epsilon-tolerant floating-point multiple check |
satisfiesUniqueItems(arr) | Deep-equal uniqueness check |
satisfiesContains(matchCount, min, max) | Validates minContains/maxContains bounds |
satisfiesContentEncoding(value, encoding) | Validates base64/base64url encoding |
satisfiesContentMediaType(value, mediaType, encoding?) | Validates application/json content |