Assertions and typeguards
npm install @httpx/assertAssertions and typeguards as primitives









> warning: pre-v1, use at your own risks
``bash`
$ npm install @httpx/assert
$ yarn add @httpx/assert
$ pnpm add @httpx/assert
- π Typeguards and assertions with a consistent style.
- π Assertions with useful default error message.
- π Return weak opaque types for boolean, strings and numbers.
- π Optimized tree-shakability, starts at 56b.
- π Don't leak values in the default assertion error messages.
- π No deps. Node, bun, browser and edge support.
π Official website, GitHub Readme or generated api doc
---
- Introduction
* Consistent style
* Weak opaque types
* Assertions error messages
- Usage
* Type related
+ assertNever
* Object related
+ isPlainObject
* Number related
+ isNumberSafeInt
* Array related
+ ArrayNonEmpty
* String related
+ StringNonEmpty
+ ParsableSafeInt
+ isParsableStrictIsoDateZ
* Uuid
+ isUuid
* Barcode
+ isEan13
* Network
+ isNetWorkPort
* Http
+ isHttpMethod
+ isValidHttpMethod
- Bundle size
- Compatibility
- Acknowledgments
- Contributors
- Sponsors
Typeguards starts with isXXX and have an assertion counterpart named assertXXX.
isParsableXXX and assertParsableXXX denotes a string.
For string, number and boolean the returned type is tagged with a weak opaque type.
It can optionally be used to enforce that the value was checked.
For example:
`typescript
import { assertUuidV7, type UuidV7 } from '@httpx/assert';
import { HttpUnprocessableEntity } from '@httpx/exception';
const persistRecord = async (uuid: UuidV7) => {
// uuid is compatible with string.
return await db.raw(insert into tbl(uuid) values (${uuid}))
}
const v = 'xxx'; // unknown
assertUuidV7(v, () => new HttpUnprocessableEntity());
// π v is known to be string & WeakOpaqueContainer<'UuidV4'>`
await persistRecord(v); // will work
await persistRecord('a_string'); // won't
When an assertion fail, a native TypeError
is thrown by default with a message indicating the requirement and and information about the
tested value. As an example:
`typescript`
expect(() => assertUuid('123')).toThrow(
new TypeError('Value is expected to be an uuid, got: string(length:3)')
);
expect(() => assertUuid(false, undefined, { version: 1 })).toThrow(
new TypeError('Value is expected to be an uuid v1, got: boolean(false)')
);
expect(() => assertUuidV1(Number.NaN)).toThrow(
new TypeError('Value is expected to be an uuid v1, got: NaN')
);
expect(() => assertUuidV3(new Error())).toThrow(
new TypeError('Value is expected to be an uuid v3, got: Error')
);
expect(() => assertUuidV4(new Date())).toThrow(
new TypeError('Value is expected to be an uuid v4, got: Date')
);
expect(() => assertUuidV5(() => {})).toThrow(
new TypeError('Value is expected to be an uuid v5, got: function')
);
expect(() => assertUuidV7(() => {})).toThrow(
new TypeError('Value is expected to be an uuid v7, got: function')
);
//...
Alternatively it's possible to provide either a message or function returning
an Error. For example:
`typescript
import { assertEan13, assertStringNonEmpty } from '@httpx/assert';
import { HttpBadRequest } from '@httpx/exception';
assertEan13('123', 'Not a barcode'); // π Will throw a TypeError('Not a barcode')
const lang = null;
assertStringNonEmpty(lang, () => new HttpBadRequest('Missing language'));
`
#### assertNever
`typescript
import { assertNever } from '@httpx/assert';
type PromiseState = 'resolved' | 'rejected' | 'running'
const state: PromiseState = 'rejected';
switch(state) {
case 'resolved': return v;
case 'rejected': return new Error();
default:
assertNever(state); // π TS will complain about missing 'running' state
// βοΈ Will throw a TypeError in js.
}
`
> PS: you can use the assertNeverNoThrow with the same behaviour except that it
> doesn't throw and return the value instead (no runtime error).
#### isPlainObject
| Name | Type | Comment |
|-------------------------|------------------|---------|
| isPlainObject\ | |PlainObject
| assertPlainObject\ | |
> Based on @httpx/plain-object
`typescript
import { isPlainObject, assertPlainObject } from '@httpx/assert';
// β π True
isPlainObject({ key: 'value' }); // β
isPlainObject({ key: new Date() }); // β
isPlainObject(new Object()); // β
isPlainObject(Object.create(null)); // β
isPlainObject({ nested: { key: true} }); // β
isPlainObject(new Proxy({}, {})); // β
isPlainObject({ [Symbol('tag')]: 'A' }); // β
// β
π (node context, workers, ...)
const runInNewContext = await import('node:vm').then(
(mod) => mod.runInNewContext
);
isPlainObject(runInNewContext('({})')); // β
// βπ False
class Test { };
isPlainObject(new Test()) // β
isPlainObject(10); // β
isPlainObject(null); // β
isPlainObject('hello'); // β
isPlainObject([]); // β
isPlainObject(new Date()); // β
isPlainObject(Math); // β Static built-in classes
isPlainObject(Promise.resolve({})); // β
isPlainObject(Object.create({})); // β
assertPlainObject({}) // π β true
`
#### Usage with generic
`typescript
import { isPlainObject, assertPlainObject } from '@httpx/assert';
// With generic value (unchecked at runtime!)
type CustomType = {
name: string;
deep: {
yes: boolean | null;
};
};
const value = {
name: 'hello',
deep: {
yes: true,
},
} as unknown;
if (isPlainObject
// Notice it's a deep partial to allow autocompletion
value?.deep?.yes; // π yes will be unknown to reflect that no runtime check was done
}
assertPlainObject
`
#### isNumberSafeInt
`typescript
import { assertNumberSafeInt, isNumberSafeInt } from '@httpx/assert';
isNumberSafeInt(10n); // π false
isNumberSafeInt(BigInt(10)); // π false
isNumberSafeInt(Number.MAX_SAFE_INTEGER); // π true
assertNumberSafeInt(Number.MAX_SAFE_INTEGER + 1); // π throws
`
#### ArrayNonEmpty
| Name | Type | Opaque type | Comment |
|---------------------|-------------|-----------------|-----------------|
| isArrayNonEmpty | unknown[] | ArrayNonEmpty | |unknown[]
| assertArrayNonEmpty | | ArrayNonEmpty | |
`typescript
import { isArrayNonEmpty, assertArrayNonEmpty, type ArrayNonEmpty } from '@httpx/assert';
isArrayNonEmpty([]) // π false
isArrayNonEmpty([0,1]) // π true
isArrayNonEmpty([null]) // π true
assertArrayNonEmpty([]) // π throws
`
#### StringNonEmpty
| Name | Type | Opaque type | Comment |
|----------------------|-----------|------------------|-----------------|
| isStringNonEmpty | string | StringNonEmpty | Trims the value |string
| assertStringNonEmpty | | StringNonEmpty | Trims the value |
`typescript
import { assertStringNonEmpty, isStringNonEmpty, type StringNonEmpty } from '@httpx/assert';
isStringNonEmpty(''); // π false
isStringNonEmpty(' '); // π false: trim by default
assertStringNonEmpty(''); // π throws
`
#### ParsableSafeInt
| Name | Type | Opaque type | Comment |
|-----------------------|-----------|-------------------|-----------------|
| isParsableSafeInt | string | ParsableSafeInt | |string
| assertParsableSafeInt | | ParsableSafeInt | |
`typescript
import { assertParsableSafeInt, isParsableSafeInt } from '@httpx/assert';
isParsableSafeInt(2); // π false
isParsableSafeInt(${Number.MAX_SAFE_INTEGER}); // π true${Number.MAX_SAFE_INTEGER}1
assertParsableSafeInt(); // π throws`
#### isParsableStrictIsoDateZ
Check if a value is a string that contains an ISO-8601 date time in 'YYYY-MM-DDTHH:mm:ss.sssZ'
format (UTC+0 / time). This check allow the value to be safely passed to new Date()or Date.parse()
without parser or timezone mis-interpretations. 'T' and 'Z' checks are done in a case-insensitive way.
| Name | Type | Opaque type | Comment |
|------------------------------|-----------|--------------------------|-----------------|
| isParsableStrictIsoDateZ | string | ParsableStrictIsoDateZ | |string
| assertParsableStrictIsoDateZ | | ParsableStrictIsoDateZ | |
`typescript
import { isParsableStrictIsoDateZ, assertParsableStrictIsoDateZ, type ParsableStrictIsoDateZ } from '@httpx/assert';
isParsableStrictIsoDateZ(new Date().toISOString()); // β
true
isParsableStrictIsoDateZ('2023-12-28T23:37:31.653Z'); // β
true
isParsableStrictIsoDateZ('2023-12-29T23:37:31.653z'); // β
true (case-insensitive works)
isParsableStrictIsoDateZ('2023-12-28T23:37:31.653'); // β false (missing 'Z')
isParsableStrictIsoDateZ('2023-02-29T23:37:31.653Z'); // β false (No 29th february in 2023)
// assertion
const dateStr = '2023-12-29T23:37:31.653Z';
assertParsableStrictIsoDateZ(dateStr, Invalid date: ${dateStr});
// π assertion passed, safe to use -> ParsableStrictIsoDateZ
const date = new Date(dateStr);
const timestampNumber = Date.parse(dateStr);
assertParsableStrictIsoDateZ('2023-02-29T23:37:31.653Z'); // π₯ throws cause no 29th february
`
#### isUuid
| Name | Type | Opaque type | Comment |
|----------------|-------------------------|--------------------------------------------------|--------|
| isUuid | string | UuidV1 \| UuidV3 \| UuidV4 \| UuidV5 \| UuidV7 | |string
| isUuidV1 | | UuidV1 | |string
| isUuidV3 | | UuidV3 | |string
| isUuidV4 | | UuidV4 | |string
| isUuidV5 | | UuidV5 | |string
| isUuidV7 | | UuidV7 | |string
| assertUuid | | UuidV1 \| UuidV3 \| UuidV4 \| UuidV5 \| UuidV7 | |string
| assertUuidV1 | | UuidV5 | |string
| assertUuidV3 | | UuidV3 | |string
| assertUuidV4 | | UuidV4 | |string
| assertUuidV5 | | UuidV5 | |string
| assertUuidV7 | | UuidV7 | |1 \| 3 \| 4 \| 5 \| 7
| getUuidVersion | | | |
`typescript
import { isUuid, isUuidV1, isUuidV3, isUuidV4, isUuidV5 } from "@httpx/assert";
import { assertUuid, assertUuidV1, assertUuidV3, assertUuidV4, assertUuidV5 } from "@httpx/assert";
import { getUuidVersion } from '@httpx/assert';
// Without version
isUuid('90123e1c-7512-523e-bb28-76fab9f2f73d'); // π valid uuid v1, 3, 4 or 5
assertUuid('90123e1c-7512-523e-bb28-76fab9f2f73d');
// With version
assertUuid('90123e1c-7512-523e-bb28-76fab9f2f73d');
assertUuidV5('90123e1c-7512-523e-bb28-76fab9f2f73d')
isUuid('90123e1c-7512-523e-bb28-76fab9f2f73d');
isUuidV4('d9428888-122b-11e1-b85c-61cd3cbb3210'); // π or isUuidV1(''), isUuidV3(''), isUuidV5('')...;
// Utils
getUuidVersion('90123e1c-7512-523e-bb28-76fab9f2f73d'); // 5
`
#### isEan13
Supported barcodes is currently limited to Ean13
`typescript
import { isEan13 } from "@httpx/assert";
import { assertEan13 } from "@httpx/assert";
isEan13('1234567890128'); // π will check digit too
assertEan13('1234567890128');
`
#### isNetWorkPort
Check whether the value is a valid tcp/udp network port (0-65535)
`typescript
import { isNetworkPort } from "@httpx/assert";
import { assertNetworkPort } from "@httpx/assert";
import { type NetworkPort } from "@httpx/assert";
isNetworkPort(443); // π weak opaque type is NetworkPort
assertNetworkPort(443);
`
#### isHttpMethod
Check whether the value is a specific http method (case-insensitive).
`typescript
import { isHttpMethod } from "@httpx/assert";
import { assertHttpMethod } from "@httpx/assert";
import { type HttpMethod } from "@httpx/assert";
const value: unknown = 'GET';
isHttpMethod('GET', value); // π weak opaque type is HttpMethod
assertHttpMethod('GET', value);
`
#### isValidHttpMethod
Check whether the value is a valid http method (case-insensitive).
`typescript
import { isHttpValidMethod } from "@httpx/assert";
import { assertHttpValidMethod } from "@httpx/assert";
import { type HttpMethod } from "@httpx/assert";
const value: unknown = 'GET';
isHttpValidMethod(value); // π weak opaque type is HttpMethod
assertHttpValidMethod(value);
`
Code and bundler have been tuned to target a minimal compressed footprint
for the browser.
ESM individual imports are tracked by a
size-limit configuration.
| Scenario | Size (compressed) |
|----------------------------------------|------------------:|
| Import isPlainObject | ~ 100b |isUuid
| Import | ~ 175b |isEan13` | ~ 117b |
| Import
| All typeguards, assertions and helpers | ~ 1700b |
> For CJS usage (not recommended) track the size on bundlephobia.
| Level | CI | Description |
|--------------|----|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Node | β
| CI for 20.x, 22.x, 24.x & 25.x. |
| Browser | β
| Tested with latest chrome (vitest/playwright) |
| Browserslist | β
| > 95% on 01/2025. defaults, chrome >= 96, firefox >= 105, edge >= 113, safari >= 15, ios >= 15, opera >= 103, not dead |
| Bun | β
| Tested with latest (at time of writing >= 1.3.3) |
| Edge | β
| Ensured on CI with @vercel/edge-runtime. |
| Cloudflare | β
| Ensured with @cloudflare/vitest-pool-workers (see wrangler.toml |
| Typescript | β
| TS 5.0+ / are-the-type-wrong checks on CI. |
| ES2022 | β
| Dist files checked with es-check |
> For _older_ browsers: most frontend frameworks can transpile the library (ie: nextjs...)
Special thanks for inspiration:
- sindresorhus/is
- webmozarts/assert
Contributions are warmly appreciated. Have a look to the CONTRIBUTING document.
If my OSS work brightens your day, let's take it to new heights together!
Sponsor>), coffee>),
or star β any gesture of support fuels my passion to improve. Thanks for being awesome! πβ€οΈ
![]() | |
JetBrains | Embie.be |
MIT Β© belgattitude and contributors.