npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@httpx/assert

v0.15.1

Published

Assertions and typeguards

Downloads

485

Readme

@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')

const lang = null;
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<T?> | PlainObject | | | assertPlainObject<T?> | PlainObject | |

Based on @httpx/plain-object


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

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<CustomType>(value)) {
  // 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<CustomType>(value);

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 { assertParsableSafeInt, isParsableSafeInt } from '@httpx/assert';

isParsableSafeInt(2); // 👉 false
isParsableSafeInt(`${Number.MAX_SAFE_INTEGER}`); // 👉 true
assertParsableSafeInt(`${Number.MAX_SAFE_INTEGER}1`); // 👉 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 | | | assertParsableStrictIsoDateZ | string | ParsableStrictIsoDateZ | |

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

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 | | | isUuidV7 | string | UuidV7 | | | 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
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

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

Network

isNetWorkPort

Check whether the value is a valid tcp/udp network port (0-65535)

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

Http

isHttpMethod

Check whether the value is a specific http method (case-insensitive).

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

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

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

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

Compatibility

| Level | CI | Description | |--------------|----|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Node | ✅ | CI for 18.x, 20.x & 22.x. | | Browser | ✅ | Tested with latest chrome (vitest/playwright) | | Browserslist | ✅ | > 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. | | 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...)

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

License

MIT © belgattitude and contributors.