@rimbu/deep
Version:
Tools to use handle plain JS objects as immutable objects
339 lines (237 loc) • 11.3 kB
Markdown
<p align="center">
<img src="https://github.com/rimbu-org/rimbu/raw/main/assets/rimbu_logo.svg" height="96" alt="Rimbu Logo" />
</p>
<div align="center">
[](https://www.npmjs.com/package/@rimbu/deep)





</div>
# `@rimbu/deep`
**Immutable, type-safe utilities for deeply patching, matching, and selecting from plain JavaScript objects.**
`@rimbu/deep` gives you a set of composable tools – `Patch`, `Match`, `Path`, `Selector`, `Protected`, and `Tuple` –
to treat plain objects as if they were immutable, deeply typed data structures. You can:
- **Apply immutable updates** to nested structures using a flexible `Patch` notation.
- **Express rich match conditions** over objects and arrays using `Match`.
- **Access nested properties safely** using type‑checked string `Path`s.
- **Build typed projections** from objects using `Selector` shapes.
- **Protect values at compile time** from accidental mutation using `Protected`.
- **Work ergonomically with tuples** and fixed‑length arrays via `Tuple`.
Use it whenever you want the convenience of plain objects, but with **deep type safety**, **immutable semantics**, and
**refactor‑friendly string paths**.
## Table of Contents
1. [Why `@rimbu/deep`?](#why-rimbu-deep)
2. [Feature Highlights](#feature-highlights)
3. [Quick Start](#quick-start)
4. [Core Concepts & Types](#core-concepts--types)
5. [Deep API Helpers](#deep-api-helpers)
6. [Installation](#installation)
7. [Ecosystem & Further Reading](#ecosystem--further-reading)
8. [Contributing](#contributing)
9. [License](#license)
## Why `@rimbu/deep`?
Plain objects are great, but they quickly become painful when:
- You need to **update nested fields immutably** (e.g. in Redux‑style state).
- You want **type‑safe string paths** like `'a.b.c[0]?.d'` instead of ad‑hoc helpers.
- You’d like to **pattern‑match** complex structures without a forest of `if`/`switch` checks.
- You want to **project and reshape data** (e.g. API responses) into well‑typed views.
`@rimbu/deep` focuses on:
- **Type‑driven paths and selectors** – `Path` and `Selector` are derived from your data types.
- **Immutable patching** – `Patch` lets you describe updates declaratively and apply them in one go.
- **Expressive matching** – `Match` supports nested objects, arrays, tuples, and compound predicates.
- **Compile‑time protection** – `Protected<T>` makes entire object graphs appear readonly to TypeScript.
If you find yourself writing a lot of manual cloning, deep property access, or matcher utilities, `@rimbu/deep` is a
drop‑in improvement.
## Feature Highlights
- **Deep immutable patching** with `patch` and `patchAt`:
describe updates using nested objects/arrays and functions instead of manual cloning.
- **Typed string paths** with `Path.Get` and `Path.Set`:
only valid paths for your data type compile.
- **Structured matching** with `match`:
supports nested objects, tuple/array traversal, and compound matchers (`every`, `some`, `none`, `single`).
- **Selection & projection** with `select` and `Selector`:
derive new shapes from existing data using path strings, functions, or nested selector objects.
- **Compile‑time protection** with `Protected` and `protect`:
make values deeply readonly at the type level while still using the underlying runtime value.
- **Tuple helpers** with `Tuple`:
ergonomics around fixed‑length tuples (construction, indexing, updates, etc.).
## Quick Start
```ts
import { Deep } from '@rimbu/deep';
const input = { a: 1, b: { c: true, d: 'a' } } as const;
// Immutable deep patch
const updated = Deep.patch(input, [{ b: [{ c: (v) => !v }] }]);
// => { a: 1, b: { c: false, d: 'a' } }
// Type-safe nested get
const cValue = Deep.getAt(input, 'b.c'); // boolean
// Pattern matching
if (Deep.match(input, { b: { c: true } })) {
// ...
}
// Selection / projection
const projected = Deep.select(input, { flag: 'b.c', label: 'b.d' });
// projected: { flag: boolean; label: string }
```
Try Rimbu (including `@rimbu/deep`) live in the browser using the
[Rimbu Sandbox on CodeSandbox](https://codesandbox.io/s/github/vitoke/rimbu-sandbox/tree/main?previewwindow=console&view=split&editorsize=65&moduleview=1&module=/src/index.ts).
## Core Concepts & Types
### Exported Types & Namespaces
From `@rimbu/deep`’s main entrypoint you have access to:
| Name | Description |
| ------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `Patch<T, C = T>` | Type describing allowed patch shapes for a value of type `T`. |
| `Match<T, C = Partial<T>>` | Type describing allowed matchers for values of type `T`. |
| `Path` | Namespace containing `Path.Get<T>`, `Path.Set<T>`, and `Path.Result<T, P>` utilities for string paths. |
| `Selector<T>` | Type describing allowed selector shapes for values of type `T`. |
| `Protected<T>` | Deeply readonly/“protected” view of `T` for compile‑time mutation safety. |
| `Tuple<T extends Tuple.Source>` | Tuple wrapper with helper types and functions under the `Tuple` namespace. |
| `Deep` | Convenience namespace exposing the main deep utilities (`patch`, `match`, `getAt`, `select`, etc.). |
See the [Deep overview docs](https://rimbu.org/docs/deep/overview) and
[API reference](https://rimbu.org/api/rimbu/deep) for the full surface.
### Patching with `patch` and `Patch`
```ts
import { Deep, type Patch } from '@rimbu/deep';
type State = {
count: number;
user?: { name: string; active: boolean };
};
const state: State = { count: 1, user: { name: 'Ada', active: true } };
const patchItem: Patch<State> = [
{ count: (v) => v + 1 },
{ user: [{ active: false }] },
];
const next = Deep.patch(state, patchItem);
// => { count: 2, user: { name: 'Ada', active: false } }
```
Patches can be:
- Direct replacement values (`T`).
- Functions `(current, parent, root) => newValue`.
- Nested objects / arrays describing which fields or tuple indices to update.
### Matching with `match` and `Match`
```ts
import { Deep, type Match } from '@rimbu/deep';
type Item = { id: number; tags: string[] };
const items: Item[] = [
{ id: 1, tags: ['a', 'b'] },
{ id: 2, tags: ['b'] },
];
const matcher: Match<Item> = {
tags: { someItem: (tag) => tag === 'a' },
};
const result = items.filter((item) => Deep.match(item, matcher));
// => only items containing tag 'a'
```
`Match` supports:
- Plain object matchers (`{ a: 1, b: { c: true } }`).
- Function matchers `(value, parent, root) => boolean | matcher`.
- Array/tuple matchers and traversal helpers such as `someItem`, `everyItem`, `noneItem`, `singleItem`.
- Compound matchers like `['every', matcher1, matcher2]`.
### Paths with `Path.Get`, `Path.Set` and `getAt` / `patchAt`
```ts
import { Deep, type Path } from '@rimbu/deep';
type Model = { a: { b: { c: number }[] } };
const value: Model = { a: { b: { c: [5, 6] } } as any };
// Typed paths
const path: Path.Get<Model> = 'a.b.c[1]?.d'; // compile-time checked
// Reading
const result = Deep.getAt(value, 'a.b.c[0]'); // number | undefined
// Patching at a path
const updated = Deep.patchAt(value, 'a.b.c', (arr) => [...arr, 7]);
```
`Path.Result<T, P>` gives you the resulting type at a given path `P` in `T`.
### Selection with `Selector` and `select`
```ts
import { Deep, type Selector } from '@rimbu/deep';
type Source = {
a: { b: number; c: string };
meta: { createdAt: string };
};
const source: Source = {
a: { b: 1, c: 'x' },
meta: { createdAt: '2024-01-01' },
};
const selector: Selector<Source> = {
value: 'a.b',
label: 'a.c',
created: 'meta.createdAt',
};
const view = Deep.select(source, selector);
// view: { value: number; label: string; created: string }
```
Selectors can be:
- String paths.
- Functions `(value: Protected<T>) => any`.
- Arrays or objects composed of other selectors.
### Protection with `Protected` and `protect`
```ts
import { Deep, type Protected } from '@rimbu/deep';
type Data = { a: { b: number[] } };
const data: Data = { a: { b: [1, 2] } };
const protectedData: Protected<Data> = Deep.protect(data);
// protectedData.a.b.push(3); // TypeScript error – `b` is readonly
```
`Protected<T>` is a **type‑level** construct: it does not freeze the value at runtime, but helps prevent
accidental mutations in your code.
## Deep API Helpers
All top‑level utilities are also available through the `Deep` namespace:
```ts
import { Deep } from '@rimbu/deep';
// Functional helpers
const incCount = Deep.patchWith<{ count: number }>([{ count: (v) => v + 1 }]);
const onlyActive = Deep.matchWith({ active: true });
const getName = Deep.getAtWith<{ user: { name: string } }>('user.name');
// Typed, curried API with withType
const s = { a: 1, b: { c: 'a', d: true } };
const api = Deep.withType<typeof s>();
const next = api.patchWith([{ b: [{ d: (v) => !v }] }])(s);
// => { a: 1, b: { c: 'a', d: false } }
```
The `Deep` namespace mirrors the main exports:
- `Deep.patch`, `Deep.patchAt`, `Deep.patchWith`, `Deep.patchAtWith`
- `Deep.match`, `Deep.matchAt`, `Deep.matchWith`, `Deep.matchAtWith`
- `Deep.getAt`, `Deep.getAtWith`
- `Deep.select`, `Deep.selectAt`, `Deep.selectWith`, `Deep.selectAtWith`
- `Deep.withType<T>()` for creating a typed, curried API.
## Installation
### Node / Bun / npm / Yarn
```sh
npm install @rimbu/deep
# or
yarn add @rimbu/deep
# or
bun add @rimbu/deep
# or
deno add npm:@rimbu/deep
```
Then:
```ts
import { Deep } from '@rimbu/deep/mod.ts';
```
### Browser / ESM
`@rimbu/deep` ships both **ESM** and **CJS** builds. Use it with any modern bundler
(Vite, Webpack, esbuild, Bun, etc.) or directly in Node ESM projects.
## Ecosystem & Further Reading
- Part of the broader **Rimbu** ecosystem – interoperates with `@rimbu/core`, `@rimbu/collection-types`,
and other collection packages.
- Ideal for modelling immutable application state, selectors, and matchers in complex domains.
- Learn more in the [Deep overview docs](https://rimbu.org/docs/deep/overview) and the
[Deep API reference](https://rimbu.org/api/rimbu/deep).
## Contributing
We welcome contributions! See the
[Contributing guide](https://github.com/rimbu-org/rimbu/blob/main/CONTRIBUTING.md) for details.
<img src="https://contrib.rocks/image?repo=rimbu-org/rimbu" alt="Contributors" />
_Made with [contributors-img](https://contrib.rocks)._
## License
MIT © Rimbu contributors. See [LICENSE](./LICENSE) for details.