# @voxpelli/typed-utils

My personal (type-enabled) utils / helpers

[![npm version](https://img.shields.io/npm/v/@voxpelli/typed-utils.svg?style=flat)](https://www.npmjs.com/package/@voxpelli/typed-utils)
[![npm downloads](https://img.shields.io/npm/dm/@voxpelli/typed-utils.svg?style=flat)](https://www.npmjs.com/package/@voxpelli/typed-utils)
[![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-7fffff?style=flat&labelColor=ff80ff)](https://github.com/neostandard/neostandard)
[![Types in JS](https://img.shields.io/badge/types_in_js-yes-brightgreen)](https://github.com/voxpelli/types-in-js)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/voxpelli/typed-utils)
[![Follow @voxpelli@mastodon.social](https://img.shields.io/mastodon/follow/109247025527949675?domain=https%3A%2F%2Fmastodon.social&style=social)](https://mastodon.social/@voxpelli)

## Requirements

> [!NOTE]
> Check the `"engines"` field in [`package.json`](./package.json) for the definitive version requirements, as they may be updated independently of this README.

- **Node.js**: ^20.11.0 or >=22.0.0
- **TypeScript**: >=5.8

## Usage

### Simple

```javascript
import { filter } from '@voxpelli/typed-utils';

/** @type {string[]} */
const noUndefined = filter(['foo', undefined]);
```

## Helpers

### Array

#### `filter(inputArray, [valueToRemove]) => filteredArray`

Takes an array as `inputArray` and a `valueToRemove` that is a string literal, `false`, `null` or `undefined`, defaulting to `undefined` if left out.

Creates a new array with all values from `inputArray` except the one that matches `valueToRemove`, then returns that array with a type where the`valueToRemove` type has also been removed from the possible values.

Can be useful in combination with eg. a `.map()` where some items in the array has resulted in `undefined` / `null` / `false` values that one wants to have removed before processing the result further.

#### `filterWithCallback(value, callback)`

Similar to `Array.prototype.filter()` but expects the `callback` to be a function like `(value: unknown) => value is any` where the `is` is the magic sauce.

#### `isArrayOfType(value, callback)`

Similar to `Array.isArray()` but also checks that the array only contains values of type verified by the `callback` function and sets the type to be an array of that type rather than simply `any[]`. The `callback` should be a function like `(value: unknown) => value is any` and needs to have an `is` in the return type for the types to work.

#### `isStringArray(value)`

Similar to `Array.isArray()` but also checks that the array only contains values of type `string` and sets the type to `string[]` rather than `any[]`.

#### `typesafeIsArray(value)`

Alias: ~~`isUnknownArray(value)`~~ (deprecated)

Does the exact same thing as `Array.isArray()` but derives the type `unknown[]` rather than `any[]`, which improves strictness in a positive type narrowing scenario (`if (typesafeIsArray(value))`) but doesn't work as well for negative type narrowing (prefer `if (!Array.isArray(value))` then).

#### `guardedArrayIncludes(collection, searchElement)`

Type-narrowing variant of `Array.prototype.includes` that works on arrays and sets. Returns `true` if `searchElement` is strictly equal to a member of `collection`. When `true`, narrows the type of `searchElement` to the element type of the iterable (`C extends Iterable<infer U> ? U : never`). Useful when you have an `unknown` (or union) value and want to both test membership and refine its type in one step.

Example:
```js
/** @type {readonly ("red"|"green"|"blue")[]} */
const COLORS = ["red", "green", "blue"];
let input /** @type {string | number} */ = Math.random() > 0.5 ? 'red' : 42;

if (guardedArrayIncludes(COLORS, input)) {
  // inside: input is now "red"|"green"|"blue"
}
```

#### `ensureArray(value)`

Converts a value to an array if it isn't already one. If `value` is already an array, returns it as-is. Otherwise, wraps `value` in a new single-element array. Useful for normalizing inputs that may be either a single item or an array of items.

Example:
```js
/** @type {string | string[]} */
const input = Math.random() > 0.5 ? 'single' : ['multiple', 'items'];

const normalized = ensureArray(input); // always string[]
```

### Assertions

#### `TypeHelpersAssertionError`

Custom error class thrown by all assertion helpers in this module. You can catch this error type to specifically handle assertion failures from these utilities.

#### `TypeHelpersAssertionEqualityError`

Custom error class thrown by equality assertions such as [`assertStrictEqual(actual, expected, [message])`](#assertstrictequalactual-expected-message).

#### `assert(condition, message)`

Throws a `TypeHelpersAssertionError` if `condition` is falsy. Used internally by other assertion helpers, but can also be used directly for custom runtime assertions.

#### `assertStrictEqual(actual, expected, [message])`

Asserts strict equality (`===`) between `actual` and `expected`.

If values differ, throws a `TypeHelpersAssertionEqualityError` with `actual` / `expected` metadata that many reporters can use for diff rendering.

#### `assertObjectWithKey(obj, key)`

Asserts that `obj` is an object and contains the property `key`. Throws an error if not.

#### `assertType(value, type, [message])`

Asserts that `value` is of the given `type` (string literal, eg. `'string'`, `'number'`, `'array'`, `'null'` – same as returned by [`explainVariable()`](#explainvariablevalue)). Throws an error if not. Optional custom error message.

Supports union types by passing an array of type names:

```javascript
assertType(value, ['string', 'number']); // narrows to string | number
assertType(value, ['string', 'boolean', 'null']); // narrows to string | boolean | null
```

#### `assertKeyWithType(obj, key, type)`

Asserts that `obj` is an object, contains the property `key`, and that `obj[key]` is of the given `type`.

#### `assertKeyWithValue(obj, key, value)`

Asserts that `obj` is an object, contains the property `key`, and that `obj[key]` is strictly equal to the given `value`.

#### `assertOptionalKeyWithType(obj, key, type)`

Asserts that `obj` is an object and either does not contain the property `key`, or if present, that `obj[key]` is `undefined` or of the given `type`.

#### `assertArrayOfLiteralType(value, type, [message])`

Asserts that `value` is an array where every element is of the given `type` (string literal, eg. `'string'`, `'number'`, `'array'`, `'null'`). Throws an error if any element fails the type check. Optional custom error message.

Supports union types by passing an array of type names:

```javascript
assertArrayOfLiteralType(value, ['string', 'number']); // narrows to Array<string | number>
```

#### `assertObjectValueType(obj, type)`

Asserts that `obj` is an object where all values are of the given `type` and all keys are strings. This is useful for validating objects used as dictionaries/maps with homogeneous value types.

Supports union types by passing an array of type names:

```javascript
assertObjectValueType(obj, 'string'); // narrows to Record<string, string>
assertObjectValueType(obj, ['string', 'number', 'boolean']); // narrows to Record<string, string | number | boolean>
```

### `is`-calls / Type Checks

#### `isObject(value)`

Returns `true` if `value` is a non-null, non-array object. Narrows the type to `Record<string, unknown>`, providing an index signature for property access patterns.

Use this when you need indexed property access after the check (e.g., `value['key']` or `'key' in value`). For exhaustiveness narrowing in type discrimination chains, use [`isType(value, 'object')`](#istypevalue-type) instead, which narrows to `object`.

```javascript
if (isObject(value)) {
  // value is Record<string, unknown> — can do value['key']
}
```

#### `isObjectWithKey(obj, key)`

Returns `true` if `obj` is an object and contains the property `key`.

#### `isType(value, type)`

Returns `true` if `value` is of the given `type` (string literal, eg. `'string'`, `'number'`, `'array'`, `'null'` – same as returned by [`explainVariable()`](#explainvariablevalue)).

Supports union types by passing an array of type names:

```javascript
if (isType(value, ['string', 'number'])) {
  // value is narrowed to string | number
}
```

#### `isKeyWithType(obj, key, type)`

Returns `true` if `obj` is an object, contains the property `key`, and `obj[key]` is of the given `type`.

#### `isKeyWithValue(obj, key, value)`

Returns `true` if `obj` is an object, contains the property `key`, and `obj[key]` is strictly equal to the given `value`.

#### `isOptionalKeyWithType(obj, key, type)`

Returns `true` if `obj` is an object and either does not contain the property `key`, or if present, `obj[key]` is of the given `type` or `undefined`.

#### `isArrayOfLiteralType(value, type)`

Returns `true` if `value` is an array where every element is of the given `type` (string literal, eg. `'string'`, `'number'`, `'array'`, `'null'`).

Supports union types by passing an array of type names:

```javascript
if (isArrayOfLiteralType(value, ['string', 'number'])) {
  // value is narrowed to Array<string | number>
}
```

#### `isObjectValueType(obj, type)`

Returns `true` if `obj` is an object where all values are of the given `type` and all keys are strings.

Supports union types by passing an array of type names:

```javascript
if (isObjectValueType(obj, 'string')) {
  // obj is narrowed to Record<string, string>
}
```

#### `isPropertyKey(value)`

Runtime guard that returns `true` when `value` is a valid `PropertyKey` (`string | number | symbol`). Used internally by `hasOwn()` helpers; exported for external guard composition.

### Getters

#### `getValueOfKeyWithType(obj, key, type)`

Returns `obj[key]` when `obj` is an object, contains the property `key`, and the value at that key is of the given `type`. Returns `undefined` otherwise.

Supports union types by passing an array of type names:

```javascript
const value = getValueOfKeyWithType({ count: 1 }, 'count', ['string', 'number']);
// value: string | number | undefined
```

Useful when you want to both validate and retrieve a typed property in one step, without first calling [`isKeyWithType()`](#iskeywithtypeobj-key-type).

### Miscellaneous

#### `explainVariable(value)`

Returns a `typeof` style explanation of a variable, with added support for eg. `null` and `array`

#### `looksLikeAnErrnoException(err)`

Returns `true` if the `err` looks like being of the `NodeJS.ErrnoException` type

### Object

#### `omit(obj, keys)`

The TypeScript utility type [`Omit<obj, keys>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys) with code that does the actual omit.

#### `pick(obj, keys)`

The TypeScript utility type [`Pick<obj, keys>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-keys) with code that does the actual pick.

#### `typedObjectKeys(obj)`

Like [`Object.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) but typed with `Array<keyof obj>` rather than `string[]`. When `obj` is a union this means the type will resolve to only the keys _shared_ between all objects in the union.

#### `typedObjectKeysAll(obj)`

Like [`typedObjectKeys(obj)`](#typedobjectkeysobj) but when `obj` is a union this type will resolve to _all possible keys_ within that union, not just the shared ones.

#### `hasOwn(obj, key)`

Safe wrapper + type guard around `Object.hasOwn(obj, key)` that first ensures `key` is a `PropertyKey`. Narrows `key` to the intersection of keys when `obj` is a union type (same semantics as `typedObjectKeys`). Unlike the raw `in` operator, prototype chain properties are excluded.

Example:
```js
const shape = Math.random() > 0.5 ? { kind: 'a', value: 1 } : { kind: 'b', label: 'hi' };
let k /** @type {string | number | symbol} */ = 'kind';
if (hasOwn(shape, k)) {
  // k narrowed to 'kind'
  console.log(shape[k]); // 'a' | 'b' (further narrowed by shape.kind checks)
}
```

#### `hasOwnAll(obj, key)`

Variant of `hasOwn()` whose key type narrows to the full union of all possible keys across union object members (like `typedObjectKeysAll`). Runtime behavior is identical to `hasOwn()`; the difference is purely at the type level. Indexed access still requires a further presence guard because not every union member has every key.

Example union refinement:
```js
/** @type {{ foo: number; bar: string } | { foo: number; baz: boolean }} */
const maybe = Math.random() ? { foo: 1, bar: 'x' } : { foo: 2, baz: true };
/** @type {string} */
const key = Math.random() ? 'bar' : 'baz';
if (hasOwnAll(maybe, key)) {
  // key: 'foo' | 'bar' | 'baz'
  switch (key) {
    case 'baz':
      // Safe indexed access after runtime membership confirmation
      if (key in maybe) console.log(maybe[key]);
      break;
    // ... more checks
  }
}
```

### Object Path

#### `getObjectValueByPath(obj, path, createIfMissing)`

Returns the object at the given path within `obj`, where `path` can be a string (dot-separated) or an array of strings. If `createIfMissing` is `true`, missing objects along the path are created. Returns `false` if a non-object is encountered, or `undefined` if the path does not exist.

#### `getStringValueByPath(obj, path)`

Returns the string value at the given path within `obj`, or `false` if the value is not a string, or `undefined` if the path does not exist. The path can be a string (dot-separated) or an array of strings.

#### `getValueByPath(obj, path)`

Returns an object `{ value }` where `value` is the value at the given path within `obj`, or `false` if a non-object is encountered, or `undefined` if the path does not exist. The path can be a string (dot-separated) or an array of strings.

### Type Utilities

#### `assertTypeIsNever(value, [message])`

Asserts that a value is of type `never`, used for exhaustive switch/conditional checks. This function ensures all cases in a discriminated union are handled. If called at runtime, it throws an error indicating an unhandled case was encountered.

Useful for compile-time exhaustiveness checking:
```js
/**
 * @param {'red' | 'green' | 'blue'} color
 */
function handleColor(color) {
  switch (color) {
    case 'red': return '#f00';
    case 'green': return '#0f0';
    case 'blue': return '#00f';
    default:
      // TypeScript error if a color case is missing
      assertTypeIsNever(color);
  }
}
```

#### `noopTypeIsAssignableToBase(base, superset)`

No-op function that validates at compile-time that `Superset` type is assignable to `Base` type. Does nothing at runtime. Useful for type tests and ensuring type relationships hold.

#### `noopTypeIsEmptyObject(base, shouldHaveKeys)`

No-op function that validates at compile-time that `Base` type is an empty object. Does nothing at runtime. Useful for type tests.

#### `noopTypeIsNever(value)`

No-op function that validates at compile-time that `value` is `never`. Does nothing at runtime. Useful for type tests and exhaustive switch/conditional checks where you want a non-throwing alternative to `assertTypeIsNever()`.

### Set

#### `FrozenSet`

An immutable variant of `Set`. All mutating methods (`add()`, `delete()`, `clear()`) throw a `TypeError`, making it safe to expose as a read-only collection without risking external mutation.

Useful when you want a `Set` API for membership tests (`has()`, iteration, `size`) but want to guarantee it cannot be modified after creation.

Example:

```js
import { FrozenSet } from '@voxpelli/typed-utils';

const COLORS = new FrozenSet(['red', 'green', 'blue']);

COLORS.has('red'); // true
COLORS.size;       // 3

COLORS.add('purple'); // throws TypeError: Cannot modify frozen set
```

To create one from an existing `Set`:

```js
const regular = new Set([1, 2, 3]);
const frozen = new FrozenSet(regular); // copies current contents; further changes to `regular` won't affect `frozen`
```

Notes:
* Still inherits all read-only / iteration behavior from `Set` (eg. `for...of`, spread, `keys()`, `values()`).
* Throws eagerly on mutation attempts—no silent failures.
* If you need deep immutability of nested values, freeze those separately; `FrozenSet` only prevents structural changes to the set itself.

## Migration

### From 3.x to 4.x

#### `LiteralTypes['object']` changed back from `Record<string, unknown>` to `object`

`isType(value, 'object')` and `assertType(value, 'object')` now narrow to `object` instead of `Record<string, unknown>`, reverting to the original behavior from `@voxpelli/type-helpers`. The `Record<string, unknown>` mapping was introduced for convenience (indexed property access), but it broke exhaustiveness narrowing in the false branch — TypeScript could not eliminate concrete object types like `{ kind: string }` from unions, making `assertTypeIsNever()` fail.

**If you relied on indexed property access after narrowing:**

| Old pattern | Migration |
|---|---|
| `isType(x, 'object')` then `x['key']` | Use `isObject(x)` (new), or `isObjectWithKey(x, 'key')` |
| `assertType(x, 'object')` then `x['key']` | Use `assertObject(x)` (unchanged, still `Record<string, unknown>`) |
| `isType(x, 'object') && 'key' in x` | Use `isObject(x) && 'key' in x`, or `isObjectWithKey(x, 'key')` |

**If you use exhaustiveness chains** — these now work correctly:

```javascript
function process(val: string | number | { key: string }): string {
  if (isType(val, 'string')) return val;
  if (isType(val, 'number')) return String(val);
  if (isType(val, 'object')) return val.key;
  assertTypeIsNever(val); // now works — val is never
}
```

#### `LiteralTypes['function']` changed from `() => unknown` to `(...args: any[]) => unknown`

`isType(value, 'function')` and `assertType(value, 'function')` now narrow to `(...args: any[]) => unknown` instead of `() => unknown`. This is strictly more permissive — all existing code continues to work, and functions with parameters are now correctly accepted.

#### `assertTypeIsNever` now returns `never`

`assertTypeIsNever()` now has a return type of `never` instead of `void`. TypeScript recognizes it as a terminal statement (a function that never returns), so you no longer need a return statement after it in exhaustive switch/if chains.

**`eslint-plugin-jsdoc` note:** The [`require-returns-check`](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-returns-check.md) rule does not perform cross-function control flow analysis — it cannot know that a call to `assertTypeIsNever()` will always throw and never return. If you write a function with a `@returns` JSDoc that relies on `assertTypeIsNever()` as the terminal statement instead of a `return`, the rule may report "`@returns` declaration present but return expression not available in function." Suppress with `// eslint-disable-next-line jsdoc/require-returns-check` above the function's JSDoc block, or add an explicit `return` before the call (e.g., `return assertTypeIsNever(val)`). See [gajus/eslint-plugin-jsdoc#817](https://github.com/gajus/eslint-plugin-jsdoc/issues/817) for background.

<!-- ## Used by

* [`example`](https://example.com/) – used by this one to do X and Y
 -->
## Similar modules

* [`type-helpers`](https://github.com/voxpelli/type-helpers) – my personal type helpers, contains no code, just types
<!--
## See also

* [Announcement blog post](#)
* [Announcement tweet](#) -->
