JSPM

  • Created
  • Published
  • Downloads 839
  • Score
    100M100P100Q93168F
  • License MIT

Assertions and typeguards

Package Exports

  • @httpx/assert
  • @httpx/assert/package.json

Readme

@httpx/assert

Assertions and typeguards as primitives

npm changelog codecov bundles node browserslist size maintainability downloads license

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.
  • 🖖  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.

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')
);
//...

Alternatively it's possible to provide either a message or function returning an Error. For example:

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

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

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

Usage

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

isPlainObject

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

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

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

ArrayNonEmpty

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

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

StringNotEmpty

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
isUuidV1 string UuidV1
isUuidV3 string UuidV3
isUuidV4 string UuidV4
isUuidV5 string UuidV5
assertUuid string UuidV1 | UuidV3 | UuidV4 | UuidV5
assertUuidV1 string UuidV5
assertUuidV3 string UuidV3
assertUuidV4 string UuidV4
assertUuidV5 string UuidV5
getUuidVersion 1 | 3 | 4 | 5
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
ES2021 Dist files checked with es-check
Node16
Node18 Ensured on CI
Node20 Ensured on CI
Node21 Ensured on CI
Edge Ensured on CI with @vercel/edge-runtime
Browsers > 95% on 12/2023. Minimums to Chrome 96+, Firefox 90+, Edge 19+, iOS 12+, Safari 12+, Opera 77+
Typescript TS 4.7+ / Dual packaging is ensured with are-the-type-wrong on the 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.