@pvorona/assert
v1.0.4
Published
ESM-only TypeScript assertions, restrictive guards, and narrowing helpers.
Maintainers
Readme
@pvorona/assert
Use @pvorona/assert for runtime assertions, nullish narrowing, object and property checks, and a few small array and number helpers in TypeScript code.
Install and import
npm i @pvorona/assert- Import runtime helpers with standard ESM syntax, for example
import { assert } from '@pvorona/assert' - Import public types with
import type, for exampleimport type { AssertionFailure } from '@pvorona/assert' - The published package is ESM-only
- The published package requires Node
>=20 - This repo currently verifies the package with TypeScript
5.9+
When this package fits
- Use it to throw on impossible states with
assert(...)orensure*helpers. - Use it to narrow existing unions such as
string | number,T | undefined, orT | null | undefined. - Use it when you want small reusable checks like
hasOwnPropertyValue(...),isPromiseLike(...), orensureArray(...).
Most helpers are mainly for narrowing values that already include the member you want to keep. They are not meant to act like loose unknown -> whatever casts. Some public guards are intentionally boundary-friendly for values typed as unknown or any, including isArray(...), isError(...), isFunction(...), isNumber(...), isObject(...), isString(...), isNull(...), isUndefined(...), isNullOrUndefined(...), and isSymbol(...). For these helpers, unknown narrows on success and any remains any.
defined means not undefined. It does not mean not nullish, so use ensureNotNull(...) or ensureNotNullOrUndefined(...) when those match the actual input shape better.
Quick start
Throw on impossible state with assert(...)
assert(condition, failure?, functionToSkipStackFrames?) takes a boolean condition. If the condition is false, it throws either AssertionError or the caller-provided Error. When failure is a string or () => string, the thrown AssertionError.message matches the provided string exactly. If functionToSkipStackFrames is omitted, assert omits its own frame from the captured stack trace by default.
failure supports these shapes:
string() => stringError() => Error
The function form is lazy and only runs on failure. Return the message or error from the callback instead of throwing it directly if you want functionToSkipStackFrames to apply consistently.
import { assert } from '@pvorona/assert';
export function parsePort(input: string): number {
const port = Number(input);
assert(Number.isInteger(port) && port > 0 && port < 65536, 'Invalid port');
return port;
}Throw a custom error with assert(...)
Prefer the function form for custom errors when construction is non-trivial or should stay off the success path.
import { assert } from '@pvorona/assert';
assert(user != null, () => new MissingUserError('User is required'));Passing Error directly or via a callback preserves the caller-provided error instance.
This unified failure contract applies to assert(...) only. The ensure* helpers keep their existing contracts.
Remove null and undefined
import { ensureNotNullOrUndefined } from '@pvorona/assert';
const envPort: null | string | undefined = process.env['PORT'];
const port = ensureNotNullOrUndefined(envPort);
// port: stringUse ensureDefined(...) for T | undefined and ensureNotNull(...) for T | null.
Check an object property on unknown
import { hasOwnPropertyValue } from '@pvorona/assert';
const result: unknown = { status: 'success', value: 42 };
if (hasOwnPropertyValue(result, 'status', 'success')) {
console.log(result['status']);
}Narrow an existing union
import { isString } from '@pvorona/assert';
function format(value: string | number): string {
if (!isString(value)) return String(value);
return value.toUpperCase();
}This is the intended style of use:
- Good fit:
isString(value)whenvalueisstring | number - Not a good fit: treating
isString(...)like a loose parser for arbitraryunknown
Narrow caught errors and boundary inputs
import { isError } from '@pvorona/assert';
function formatProblem(problem: Error | string): string {
if (!isError(problem)) return problem.toUpperCase();
return problem.message;
}
function messageFromUnknown(value: unknown): string {
if (!isError(value)) return 'Unknown failure';
return value.message;
}isError(...) accepts unknown and any as boundary inputs. unknown narrows to Error; any is allowed but remains any.
API reference
Core assertion helpers
assert(condition, failure?, functionToSkipStackFrames?): asserts a boolean condition; string-based failures preserve the exact message, caller-providedErrorvalues pass through, and omittingfunctionToSkipStackFramesomitsassertfrom captured stack traces by defaultAssertionFailure: publicassert(...)input contract,undefined | string | Error | (() => string | Error)AssertionError: error class used by failedassert(...)callsensureNever(value, silent?, message?): throws plainErrorfor exhaustive-check failures unlesssilentistrue
Nullish helpers
isDefined(...): boolean type guard that narrowsT | undefinedtoTensureDefined(...): narrowsT | undefinedand throws onundefinedisNull(...): boolean type guard for unions that includenullensureNotNull(...): narrowsT | nulland throws onnullisUndefined(...): boolean type guard for unions that includeundefinedisNullOrUndefined(...): boolean type guard for unions that include bothnullandundefinedensureNotNullOrUndefined(...): narrowsT | null | undefinedand throws onnullorundefined
String and number helpers
isString(...): boolean type guard for unions that includestringensureString(...): narrows tostringand throws on failureisNumber(...): boolean type guard for unions that includenumberensureNumber(...): narrows tonumberand throws on failure
Object and property helpers
isObject(...): boolean type guard for non-null indexable objectsensureObject(...): narrows to a non-null object and throws on failurehasOwnKey(...): boolean guard for own properties on objects or functionshasOwnPropertyValue(...): boolean guard for own data properties matching an exact valueisError(...): boolean guard for same-realmErrorinstances, includingunknownboundary inputs and unions that includeErroror error subtypesisFunction(...): boolean guard for unions that already include a function memberisSymbol(...): boolean guard for unions that includesymbol
Array helpers
isArray(...): boolean type guard for unions that include mutable or readonly arraysensureArray(...): narrows to arrays and preserves readonlyness when the input is readonly
Async helper
isPromiseLike(...): boolean guard for values that expose a callable.then
Public types
AssertionFailure: public union for the supportedassert(...)failure inputs
Behavior notes
isNumber(...)andensureNumber(...)accept any JavaScript number value, includingNaN,Infinity, and-Infinity.ensureNumber(...)is an exception to the stricter compile-time pattern used by helpers likeisNumber(...): plainnumberinputs are allowed.isObject(...)andensureObject(...)accept arrays but reject functions.hasOwnKey(...)andhasOwnPropertyValue(...)work with both objects and functions that have own properties.hasOwnPropertyValue(...)only matches own data properties. Inherited properties and getters returnfalse.isPromiseLike(...)accepts object or function thenables and returnsfalseif reading.thenthrows.isError(...)uses same-realmvalue instanceof globalThis.Errorat runtime. Plain objects withnameandmessagefields returnfalse, and errors created in another realm also returnfalse.isError(...)acceptsunknownandanyas boundary inputs.unknownnarrows toError;anyremainsany.isError(...)still follows the restrictive compile-time style for typed inputs: plainError, error subtypes, and unions made only of error subtypes are rejected.isNumber(...),isString(...),isNull(...),isUndefined(...),isNullOrUndefined(...), andisSymbol(...)now follow the same boundary-friendly style as helpers likeisArray(...),isError(...),isFunction(...), andisObject(...): they acceptunknownandany,unknownnarrows to the checked member on success, andanyremainsany. For other typed inputs they keep the existing restrictive compile-time contract.ensureNever(...)is for exhaustive checks. It throws plainError, notAssertionError, andsilent = trueskips throwing.assert(...)preserves string messages exactly for bothstringand() => stringfailures.- Omitting
functionToSkipStackFramesmakesassert(...)omit its own frame from the captured stack trace by default. - The unified
AssertionFailureinput and caller-provided custom-error support apply toassert(...), not theensure*helpers. isFunction(...)is most useful when the union already contains a function member.isSymbol(...)expects the broadsymboltype in the input union.isNullOrUndefined(...)andensureNotNullOrUndefined(...)expect unions that include bothnullandundefined.
Array helpers
isArray(...) and ensureArray(...) accept mutable and readonly arrays. ensureArray(...) preserves readonlyness when the input is readonly.
import { ensureArray } from '@pvorona/assert';
const values = ['1', '2'] as readonly string[] | string;
const ensured = ensureArray(values);
// ensured: readonly string[]Restrictive helper contracts
import { ensureDefined, ensureNotNullOrUndefined } from '@pvorona/assert';
const port: null | string | undefined = process.env['PORT'];
const definedPort = ensureNotNullOrUndefined(port);
const fallbackPort: string | undefined = process.env['FALLBACK_PORT'];
const requiredFallbackPort = ensureDefined(fallbackPort);
// Not the same contract:
// ensureNotNullOrUndefined(fallbackPort)
// Use ensureDefined(...) for T | undefined and ensureNotNull(...) for T | null.Migration notes
The root package no longer re-exports some advanced helpers from older internal surfaces.
resolveValueOrGettermoved to@pvorona/resolve-value-or-getterand is no longer part of the@pvorona/assertAPIMutablemoved to a private workspace package and is no longer part of the published API- External consumers should define local equivalents if they still need either helper
- Removed root exports include internal-looking helpers such as
Override,InferErrorMessage,NotOnly*,Includes*,AtLeastOneValid, andInferArrayType - There is no replacement public subpath for the removed advanced types; if you still need them, define local equivalents in your own codebase
