@studnicky/types
Runtime type-guard, predicate, and object helpers for
@studnicky/substrate.
Install
pnpm add @studnicky/typesUsage
Predicates is the package's single unified static class for type narrowing, value comparison, and JSON Schema-style validation. Predicate composes atomic type guards while preserving their narrowed types: use and, or, not, field, arrayItems, and mapEntries to parse an untrusted value once into a canonical structural shape. Empty produces fresh empty collection instances. JsonObject and JsonValue implement runtime JSON boundaries. PickDefined assembles objects without retaining undefined properties.
import { Empty, JsonValue, Predicates } from '../src/index.js';
import { PredicatesAccessorsFixtures } from './fixtures/PredicatesAccessorsFixtures.js';
// ── Predicates.isObject ──────────────────────────────────────────────────────
const plainObject = Predicates.isObject({ 'a': 1 });
const arrayIsRecord = Predicates.isObject([1, 2, 3]);
const nullIsRecord = Predicates.isObject(null);
console.log('Predicates.isObject({ a: 1 }):', plainObject);
console.log('Predicates.isObject([1,2,3]):', arrayIsRecord);
console.log('Predicates.isObject(null):', nullIsRecord);
// ── Predicates.asNumber / asStringOrNull ────────────────────────────────────
const numberResult = Predicates.asNumber(3.14);
const stringOrNull = Predicates.asStringOrNull(null);
console.log('Predicates.asNumber(3.14):', numberResult);
console.log('Predicates.asStringOrNull(null):', stringOrNull);
// ── Predicates.asRecordArray ────────────────────────────────────────────────
const records = Predicates.asRecordArray(PredicatesAccessorsFixtures.mixed);
console.log('Predicates.asRecordArray([{id:1},"skip",{id:2},null]):', records);
// ── Predicates type predicates ──────────────────────────────────────────────
console.log('Predicates.isString("hello"):', Predicates.isString('hello'));
console.log('Predicates.isNumber(3.14):', Predicates.isNumber(3.14));
console.log('Predicates.isNumber(NaN):', Predicates.isNumber(Number.NaN));
console.log('Predicates.isBoolean(true):', Predicates.isBoolean(true));
console.log('Predicates.isNonNegativeInteger(0):', Predicates.isNonNegativeInteger(0));
console.log('Predicates.isPositiveInteger(0):', Predicates.isPositiveInteger(0));
// ── Static-override subclass ────────────────────────────────────────────────
class StrictPredicates extends Predicates {
public static override isObject<T>(value: T): value is Record<string, unknown> & T {
if (super.isObject(value) && !Array.isArray(value)) {
return true;
}
return false;
}
}
const strictArray = StrictPredicates.asRecordArray([{ 'a': 1 }, 99, { 'b': 2 }]);
console.log('StrictPredicates.asRecordArray([{a:1},99,{b:2}]):', strictArray);
// ── Empty producers ─────────────────────────────────────────────────────────
const emptyString = Empty.string();
const emptyObject = Empty.object();
const emptyArray = Empty.array<number>();
const emptyMap = Empty.map<string, number>();
const emptySet = Empty.set<string>();
console.log('Empty.string():', JSON.stringify(emptyString));
console.log('Empty.object():', emptyObject);
console.log('Empty.array<number>():', emptyArray);
console.log('Empty.map<string,number>().size:', emptyMap.size);
console.log('Empty.set<string>().size:', emptySet.size);
// ── Predicates emptiness checks ─────────────────────────────────────────────
console.log('Predicates.isEmptyString(""):', Predicates.isEmptyString(''));
console.log('Predicates.isEmptyPlainObject({}):', Predicates.isEmptyPlainObject({}));
console.log('Predicates.isEmptyArray([]):', Predicates.isEmptyArray([]));
console.log('Predicates.isEmptyMap(new Map()):', Predicates.isEmptyMap(new Map()));
console.log('Predicates.isEmptySet(new Set()):', Predicates.isEmptySet(new Set()));
// ── JSON value boundary ─────────────────────────────────────────────────────
const value = PredicatesAccessorsFixtures.value;
console.log('value:', JSON.stringify(value));Try it
The output shows Predicates.isObject/asRecordArray narrowing, scalar guards, the StrictPredicates static-override subclass, Empty producers, and a JSON value boundary.
JSON runtime boundaries
JsonObject
JsonObject.is performs a shallow plain-object check and narrows unknown to Record<string, unknown>. It rejects arrays, Map, Set, class instances, and other non-plain objects.
import { JsonObject } from '@studnicky/types';
const parsed: unknown = JSON.parse(responseText);
if (JsonObject.is(parsed)) {
const id = parsed.id;
console.log(id);
}Use schema validation when object members also need structural guarantees.
JsonValue
JsonValue.is narrows unknown to the canonical JSONSchema7Type owned by json-schema. JsonValue.from recursively coerces unsupported values to null, producing a finite, acyclic JSONSchema7Type without a cast.
import type { JSONSchema7Type } from 'json-schema';
import { JsonValue } from '@studnicky/types';
const candidate: unknown = JSON.parse(responseText);
if (JsonValue.is(candidate)) {
const value: JSONSchema7Type = candidate;
console.log(value);
}
const safe: JSONSchema7Type = JsonValue.from({
nested: [1, undefined]
});Import JSONSchema7Type directly from json-schema when a public signature or local annotation needs the type. Its declarations come from the package's direct @types/json-schema dependency. @studnicky/types exports the runtime boundary, not a type alias for the dependency-owned JSON type.
Assembling options objects (PickDefined)
PickDefined.from strips undefined-valued keys from a record, narrowing each remaining value away from undefined. It assembles direct configuration objects from required and optional fields.
import { PickDefined } from '../src/index.js';
const withClock = PickDefined.from({
'burstSize': 15,
'clock': Date.now,
'deadlineMs': undefined,
'requestsPerSecond': 5
});
const withoutClock = PickDefined.from({
'burstSize': 20,
'clock': undefined,
'deadlineMs': undefined,
'requestsPerSecond': 10
});
console.log('withClock:', { ...withClock, 'clock': typeof withClock.clock });
console.log('withoutClock:', withoutClock);Try it (PickDefined)
The output shows direct configuration with required defaults and an optional clock field that is present only when defined.
Exports
| Symbol | Purpose | Import path |
|---|---|---|
Predicates | Type guards, atomic comparators, JSON Schema draft 2020-12 predicates, and value equality/coercion helpers, unified on one static class. | @studnicky/types |
Predicate | Typed runtime predicate composition for boolean algebra and record, array, and map structure. | @studnicky/types |
PredicateFunctionInterface | Contract for a runtime predicate that narrows unknown to its value type. | @studnicky/types |
Empty | Produces fresh empty collection instances. | @studnicky/types |
JsonObject | Narrows values at the plain-object JSON boundary. | @studnicky/types |
JsonValue | Validates and coerces recursive JSON values. | @studnicky/types |
PickDefined | Omits undefined-valued properties from an object. | @studnicky/types |
TIME_ONLY_PATTERN | Recognizes a time-only string before a consumer applies its own domain semantics. | @studnicky/types |
Selected Predicates static methods
| Method | Description |
|---|---|
isString/isNumber/isBoolean/isFunction/isNullish | Generic-preserving type guards (<T>(value: T): value is X & T) — narrow an already-typed value without discarding its declared shape. |
isNumberType(value) | typeof value === 'number', including NaN/Infinity — use over isNumber when the caller routes those values to a more specific downstream check. |
isObjectLike/isObject/isRecord/isPlainObject | Progressively narrower object-shape guards; see each method's doc comment for the exact exclusion each adds. |
isMap/isSet/isDate/isArray/isRegExp/isURL/isError | Type guards for the common non-primitive built-ins. |
isEmptyString/isEmptyPlainObject/isEmptyArray/isEmptyMap/isEmptySet | Emptiness checks — pair with Empty's producers of the same five shapes. |
areArraysEqual/areMapsEqual/areSetsEqual/areObjectsEqual | Structural equality per container shape. |
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. |
satisfiesUniqueItems(arr) | Deep-equal uniqueness check. |
satisfiesContentEncoding(value, encoding) | Validates base64/base64url encoding. |
satisfiesContentMediaType(value, mediaType, encoding?) | Validates application/json content. |
Extending
Predicates is a pure-static class. Extend it and override a static method — most commonly isObject, to customise record detection — and other methods that delegate through this.<method> (e.g. asRecordArray delegates through this.isObject) will propagate the override automatically.