Skip to content

@studnicky/types

Runtime type-guard, predicate, and object helpers for @studnicky/substrate.

Install

bash
pnpm add @studnicky/types

Usage

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.

ts
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

Loading example…

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.

typescript
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.

typescript
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.

ts
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)

Loading example…

The output shows direct configuration with required defaults and an optional clock field that is present only when defined.

Exports

SymbolPurposeImport path
PredicatesType guards, atomic comparators, JSON Schema draft 2020-12 predicates, and value equality/coercion helpers, unified on one static class.@studnicky/types
PredicateTyped runtime predicate composition for boolean algebra and record, array, and map structure.@studnicky/types
PredicateFunctionInterfaceContract for a runtime predicate that narrows unknown to its value type.@studnicky/types
EmptyProduces fresh empty collection instances.@studnicky/types
JsonObjectNarrows values at the plain-object JSON boundary.@studnicky/types
JsonValueValidates and coerces recursive JSON values.@studnicky/types
PickDefinedOmits undefined-valued properties from an object.@studnicky/types
TIME_ONLY_PATTERNRecognizes a time-only string before a consumer applies its own domain semantics.@studnicky/types

Selected Predicates static methods

MethodDescription
isString/isNumber/isBoolean/isFunction/isNullishGeneric-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/isPlainObjectProgressively narrower object-shape guards; see each method's doc comment for the exact exclusion each adds.
isMap/isSet/isDate/isArray/isRegExp/isURL/isErrorType guards for the common non-primitive built-ins.
isEmptyString/isEmptyPlainObject/isEmptyArray/isEmptyMap/isEmptySetEmptiness checks — pair with Empty's producers of the same five shapes.
areArraysEqual/areMapsEqual/areSetsEqual/areObjectsEqualStructural 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.

Source on GitHub