@httpx/assert
TypeScript icon, indicating that this package has built-in type declarations

0.8.1 • Public • Published

@httpx/assert

Assertions and typeguards as primitives

npm changelog codecov bundles node browserslist size downloads license

warning: pre-v1, use at your own risks

Install

$ npm install @httpx/assert
$ yarn add @httpx/assert
$ pnpm add @httpx/assert

Features

  • 👉  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, browser and edge support.

Documentation

👉 Official website, GitHub Readme or generated api doc


Introduction

Consistent style

Typeguards starts with isXXX and have an assertion counterpart named assertXXX.

isParsableXXX and assertParsableXXX denotes a string.

Weak opaque types

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:

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

Assertions error messages

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:

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:

import { assertEan13, assertStringNonEmpty } from '@httpx/assert';
import { HttpBadRequest } from '@httpx/exception';

assertEan13('123', 'Not a barcode'); // 👈 Will throw a TypeError('Not a barcode')

assertStringNonEmpty(lang, () => new HttpBadRequest('Missing language'));

Usage

Type related

assertNever

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

Object related

isPlainObject

Name Type Comment
isPlainObject PlainObject
assertPlainObject PlainObject
import { isPainObject, assertPlainObject } from '@httpx/assert';

isPlainObject({cool: true}); // 👈 true
isPlainObject(new Promise()); // 👈 false
assertPlainObject({});

Number related

isNumberSafeInt

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

Array related

ArrayNonEmpty

Name Type Opaque type Comment
isArrayNonEmpty unknown[] ArrayNonEmpty
assertArrayNonEmpty unknown[] ArrayNonEmpty
import { isArrayNonEmpty, assertArrayNonEmpty, type ArrayNonEmpty } from '@httpx/assert';

isArrayNonEmpty([]) // 👉 false
isArrayNonEmpty([0,1]) // 👉 true
isArrayNonEmpty([null]) // 👉 true
assertArrayNonEmpty([]) // 👉 throws

String related

StringNonEmpty

Name Type Opaque type Comment
isStringNonEmpty string StringNonEmpty Trims the value
assertStringNonEmpty string StringNonEmpty Trims the value
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
assertParsableSafeInt string ParsableSafeInt
import { assertStrParsableSafeInt, isStrParsableSafeInt } from '@httpx/assert';

isStrParsableSafeInt(2); // 👉 false
isStrParsableSafeInt(`${Number.MAX_SAFE_INTEGER}`); // 👉 true
assertStrParsableSafeInt(`${Number.MAX_SAFE_INTEGER}1`); // 👉 throws

isParsableStrictIsoDateZ

Ensure a string contains a strict iso datetime with microseconds and utc suffix (aka: zulu time). Date is checked for validity.

Name Type Opaque type Comment
isParsableStrictIsoDateZ string ParsableStrictIsoDateZ
assertParsableStrictIsoDateZ string ParsableStrictIsoDateZ
import { isParsableStrictIsoDateZ, assertParsableStrictIsoDateZ, type ParsableStrictIsoDateZ } from '@httpx/assert';

isParsableStrictIsoDateZ(new Date().toISOString()); // 👉 true
isParsableStrictIsoDateZ('2023-12-29T23:37:31.653z'); // 👉 true
isParsableStrictIsoDateZ('2023-02-29T23:37:31.653'); // 👉 false, cause no 29th february in 2023

assertParsableStrictIsoDateZ('2023-02-29T23:37:31.653z'); // 👉 throws cause no 29th february

Uuid

isUuid

Name Type Opaque type Comment
isUuid string UuidV1 | UuidV3 | UuidV4 | UuidV5 | UuidV7
isUuidV1 string UuidV1
isUuidV3 string UuidV3
isUuidV4 string UuidV4
isUuidV5 string UuidV5
assertUuid string UuidV1 | UuidV3 | UuidV4 | UuidV5 | UuidV7
assertUuidV1 string UuidV5
assertUuidV3 string UuidV3
assertUuidV4 string UuidV4
assertUuidV5 string UuidV5
assertUuidV7 string UuidV7
getUuidVersion 1 | 3 | 4 | 5 | 7
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
isUuid('90123e1c-7512-523e-bb28-76fab9f2f73d');
assertUuid('90123e1c-7512-523e-bb28-76fab9f2f73d');
assertUuidV5('90123e1c-7512-523e-bb28-76fab9f2f73d')
isUuidV4('d9428888-122b-11e1-b85c-61cd3cbb3210'); // 👈 or isUuidV1(''), isUuidV3(''), isUuidV5('')...;

// Utils
getUuidVersion('90123e1c-7512-523e-bb28-76fab9f2f73d'); // 5

Barcode

isEan13

Supported barcodes is currently limited to Ean13

import { isEan13 } from "@httpx/assert";
import { assertEan13 } from "@httpx/assert";

isEan13('1234567890128'); // 👈 will check digit too
assertEan13('1234567890128');

Bundle size

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 ~ 56b
Import isUuid ~ 175b
Import isEan13 ~ 117b
All typeguards, assertions and helpers ~ 900b

For CJS usage (not recommended) track the size on bundlephobia.

Compatibility

Level CI Description
Node CI for 18.x, 20.x & 21.x.
Browsers > 95% on 12/2023. Mins to Chrome 96+, Firefox 90+, Edge 19+, iOS 12+, Safari 12+, Opera 77+
Edge Ensured on CI with @vercel/edge-runtime.
Typescript TS 5.0+ / are-the-type-wrong checks on CI.
ES2021 Dist files checked with es-check
Node16 Node 16.x supported, not ensured on CI

For older browsers: most frontend frameworks can transpile the library (ie: nextjs...)

Acknowledgments

Special thanks for inspiration:

Contributors

Contributions are warmly appreciated. Have a look to the CONTRIBUTING document.

Sponsors

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! 🙏❤️

Special thanks to

Jetbrains logo Jetbrains logo
JetBrains Embie.be

License

MIT © belgattitude and contributors.

Package Sidebar

Install

npm i @httpx/assert

Weekly Downloads

147

Version

0.8.1

License

MIT

Unpacked Size

116 kB

Total Files

139

Last publish

Collaborators

  • s.vanvelthem