mutts 1.0.10 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/browser.cjs +244 -3199
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +2 -2
- package/dist/browser.dev.cjs +51 -50
- package/dist/browser.dev.cjs.map +1 -1
- package/dist/browser.dev.d.ts +2 -2
- package/dist/browser.dev.esm.js +2 -2
- package/dist/browser.esm.js +15 -13
- package/dist/browser.esm.js.map +1 -1
- package/dist/chunks/{index-CaaQQlPJ.esm.js → index-BUop6B2U.esm.js} +384 -981
- package/dist/chunks/index-BUop6B2U.esm.js.map +1 -0
- package/dist/chunks/index-yK0HVxHv.cjs +2612 -0
- package/dist/chunks/index-yK0HVxHv.cjs.map +1 -0
- package/dist/chunks/{node-nKJBk8iJ.esm.js → node-Bo7WU5S2.esm.js} +2 -2
- package/dist/chunks/{node-nKJBk8iJ.esm.js.map → node-Bo7WU5S2.esm.js.map} +1 -1
- package/dist/chunks/{async-node-3PrbVAbB.cjs → node-Dd0esp5F.cjs} +4 -4
- package/dist/chunks/node-Dd0esp5F.cjs.map +1 -0
- package/dist/chunks/{proxy-Dtg-bJ3T.cjs → proxy-BvM4yewA.cjs} +441 -342
- package/dist/chunks/proxy-BvM4yewA.cjs.map +1 -0
- package/dist/chunks/{proxy-r7lARftl.esm.js → proxy-D2C49sXH.esm.js} +401 -330
- package/dist/chunks/proxy-D2C49sXH.esm.js.map +1 -0
- package/dist/debug.cjs +17 -4
- package/dist/debug.cjs.map +1 -1
- package/dist/debug.d.ts +2 -2
- package/dist/debug.esm.js +17 -3
- package/dist/debug.esm.js.map +1 -1
- package/dist/index.d.ts +84 -209
- package/dist/mutts.umd.js +842 -1362
- package/dist/mutts.umd.js.map +1 -1
- package/dist/mutts.umd.min.js +1 -1
- package/dist/mutts.umd.min.js.map +1 -1
- package/dist/node.cjs +51 -49
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.dev.cjs +51 -49
- package/dist/node.dev.cjs.map +1 -1
- package/dist/node.dev.d.ts +2 -2
- package/dist/node.dev.esm.js +3 -3
- package/dist/node.esm.js +3 -3
- package/dist/{types-W5vD6m2n.d.ts → types-Bx2PhORg.d.ts} +38 -47
- package/docs/ai/api-reference.md +0 -14
- package/docs/ai/manual.md +2 -22
- package/docs/reactive/advanced.md +13 -14
- package/docs/reactive/attend.md +1 -2
- package/docs/reactive/collections.md +2 -149
- package/docs/reactive/core.md +218 -96
- package/docs/reactive/debugging.md +2 -2
- package/docs/reactive/resource.md +1 -1
- package/docs/reactive.md +1 -3
- package/docs/zone.md +1 -1
- package/package.json +18 -9
- package/dist/chunks/async-browser-BU_IfxYD.cjs +0 -216
- package/dist/chunks/async-browser-BU_IfxYD.cjs.map +0 -1
- package/dist/chunks/async-core-CRLKP3l-.cjs +0 -29
- package/dist/chunks/async-core-CRLKP3l-.cjs.map +0 -1
- package/dist/chunks/async-node-3PrbVAbB.cjs.map +0 -1
- package/dist/chunks/index-CaaQQlPJ.esm.js.map +0 -1
- package/dist/chunks/proxy-Dtg-bJ3T.cjs.map +0 -1
- package/dist/chunks/proxy-r7lARftl.esm.js.map +0 -1
- package/docs/reactive/scan.md +0 -324
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
The `atomic` function wraps a function to batch all reactive effects triggered within it, ensuring effects run only once after the function completes. It can be used both as a function wrapper and as a decorator.
|
|
6
6
|
|
|
7
7
|
```typescript
|
|
8
|
-
import { atomic, reactive, effect } from 'mutts
|
|
8
|
+
import { atomic, reactive, effect } from 'mutts'
|
|
9
9
|
|
|
10
10
|
const state = reactive({ a: 0, b: 0 })
|
|
11
11
|
|
|
@@ -48,7 +48,7 @@ The wrapped function preserves its signature (parameters and return value), and
|
|
|
48
48
|
While `atomic()` **wraps** a function for later calls, `atom()` **runs** a function immediately and atomically. It always executes right away, even inside a nested batch.
|
|
49
49
|
|
|
50
50
|
```typescript
|
|
51
|
-
import { atom, reactive, effect } from 'mutts
|
|
51
|
+
import { atom, reactive, effect } from 'mutts'
|
|
52
52
|
|
|
53
53
|
const state = reactive({ a: 0, b: 0 })
|
|
54
54
|
|
|
@@ -85,9 +85,9 @@ update(1, 2) // runs when called
|
|
|
85
85
|
When an effect needs to perform an action that would modify state the effect depends on, this can create a reactive cycle. The `addBatchCleanup` function (also exported as `defer` for semantic clarity) allows you to defer such work until after the current batch of effects completes.
|
|
86
86
|
|
|
87
87
|
```typescript
|
|
88
|
-
import { addBatchCleanup, effect, reactive } from 'mutts
|
|
88
|
+
import { addBatchCleanup, effect, reactive } from 'mutts'
|
|
89
89
|
// or use the semantic alias:
|
|
90
|
-
// import { defer } from 'mutts
|
|
90
|
+
// import { defer } from 'mutts'
|
|
91
91
|
|
|
92
92
|
const state = reactive({
|
|
93
93
|
items: [],
|
|
@@ -298,7 +298,7 @@ effect(() => {
|
|
|
298
298
|
Creates a bidirectional binding between a reactive value and a non-reactive external value (like DOM elements), automatically preventing infinite loops.
|
|
299
299
|
|
|
300
300
|
```typescript
|
|
301
|
-
import { biDi, reactive } from 'mutts
|
|
301
|
+
import { biDi, reactive } from 'mutts'
|
|
302
302
|
|
|
303
303
|
const model = reactive({ value: '' })
|
|
304
304
|
|
|
@@ -1142,15 +1142,14 @@ Mutts provides several ways to derive values from reactive state. They differ in
|
|
|
1142
1142
|
| `lift(() => ({...}))` | Eager | Reactive object proxy | Yes (per-prop) | **Yes** | `result[cleanup]()` | Derived objects, computed shapes |
|
|
1143
1143
|
| `morph(source, fn)` | **Lazy** | Reactive proxy | Yes (per-key) | **Yes** | `cleanedBy` | Lazy per-element map with identity tracking |
|
|
1144
1144
|
| `morph.pure(source, fn)` | **Lazy** | Reactive proxy | Yes (per-key) | **Yes** | `cleanedBy` | Same, but skips per-item dependency tracking |
|
|
1145
|
-
| `scan(source, fn, init)` | Eager | Reactive array | Yes (per-index) | **Yes** | `result[cleanup]()` | Running accumulations (prefix sums) |
|
|
1146
1145
|
| `when(() => cond)` | Eager | Promise\<T\> | N/A | N/A | Auto (on resolve/timeout) | Awaiting a reactive condition |
|
|
1147
1146
|
| `watch(source, cb)` | Eager | Callback (old/new) | N/A | N/A | Auto (parent/GC) + Returned cleanup | Observing specific changes |
|
|
1148
1147
|
|
|
1149
1148
|
### Key distinctions
|
|
1150
1149
|
|
|
1151
1150
|
- **Lazy vs Eager**: `memoize` only recomputes when the result is read. Everything else recomputes immediately when dependencies change — even if nobody is consuming the output.
|
|
1152
|
-
- **Trackable**: Can downstream effects depend on the result? `memoize`'s return value is trackable because calling it runs inside an effect context. `lift`/`morph
|
|
1153
|
-
- **Identity stable**: `lift
|
|
1151
|
+
- **Trackable**: Can downstream effects depend on the result? `memoize`'s return value is trackable because calling it runs inside an effect context. `lift`/`morph` return reactive proxies where each property/index is independently trackable.
|
|
1152
|
+
- **Identity stable**: `lift` and `morph` return the **same proxy** across recomputations — only changed slots are updated. This is critical for downstream transformations or DOM reconciliation that relies on reference identity.
|
|
1154
1153
|
|
|
1155
1154
|
### Common patterns
|
|
1156
1155
|
|
|
@@ -1229,7 +1228,7 @@ state.items = fetchedItems // deep touch diffs old vs new per-index — no lift
|
|
|
1229
1228
|
**Signature**
|
|
1230
1229
|
|
|
1231
1230
|
```typescript
|
|
1232
|
-
import { morph } from 'mutts
|
|
1231
|
+
import { morph } from 'mutts'
|
|
1233
1232
|
|
|
1234
1233
|
function morph<I, O>(
|
|
1235
1234
|
source: readonly I[] | (() => readonly I[]),
|
|
@@ -1254,7 +1253,7 @@ function morph<I, O>(
|
|
|
1254
1253
|
**Basic usage**
|
|
1255
1254
|
|
|
1256
1255
|
```typescript
|
|
1257
|
-
import { morph, reactive, effect } from 'mutts
|
|
1256
|
+
import { morph, reactive, effect } from 'mutts'
|
|
1258
1257
|
|
|
1259
1258
|
const items = reactive(['alice', 'bob', 'charlie'])
|
|
1260
1259
|
const upper = morph(items, name => name.toUpperCase())
|
|
@@ -1355,7 +1354,7 @@ pure[0] // 10 — stale, no per-item effect to invalidate
|
|
|
1355
1354
|
**Signature**
|
|
1356
1355
|
|
|
1357
1356
|
```typescript
|
|
1358
|
-
import { memoize } from 'mutts
|
|
1357
|
+
import { memoize } from 'mutts'
|
|
1359
1358
|
|
|
1360
1359
|
type Memoizable = object | any[] | symbol
|
|
1361
1360
|
|
|
@@ -1380,7 +1379,7 @@ function memoize<Result>(
|
|
|
1380
1379
|
**Basic usage**
|
|
1381
1380
|
|
|
1382
1381
|
```typescript
|
|
1383
|
-
import { effect, memoize, reactive } from 'mutts
|
|
1382
|
+
import { effect, memoize, reactive } from 'mutts'
|
|
1384
1383
|
|
|
1385
1384
|
const source = reactive({ value: 1 })
|
|
1386
1385
|
const args = { node: source }
|
|
@@ -1426,7 +1425,7 @@ Use `maxArgs` when the memoized function should only consider the first _n_ argu
|
|
|
1426
1425
|
A flavored variant that gracefully handles non-WeakKey arguments (primitives, `null`, `undefined`). Instead of throwing, it falls back to recomputing the function without caching.
|
|
1427
1426
|
|
|
1428
1427
|
```typescript
|
|
1429
|
-
import { memoize } from 'mutts
|
|
1428
|
+
import { memoize } from 'mutts'
|
|
1430
1429
|
|
|
1431
1430
|
const process = memoize.lenient((value: string | { data: string }) => {
|
|
1432
1431
|
return typeof value === 'string' ? value.toUpperCase() : value.data
|
|
@@ -1447,7 +1446,7 @@ This is useful when a memoized function may receive both objects and primitives,
|
|
|
1447
1446
|
Apply `@memoize` (or `@memoize.lenient`) to class getters or methods to share the same cache semantics. Getters cache per instance; methods cache per instance and argument tuple.
|
|
1448
1447
|
|
|
1449
1448
|
```typescript
|
|
1450
|
-
import { memoize, reactive } from 'mutts
|
|
1449
|
+
import { memoize, reactive } from 'mutts'
|
|
1451
1450
|
|
|
1452
1451
|
class Example {
|
|
1453
1452
|
state = reactive({ count: 0 })
|
package/docs/reactive/attend.md
CHANGED
|
@@ -137,6 +137,5 @@ attend(
|
|
|
137
137
|
| `attend` | — | Reactive lifecycle per key |
|
|
138
138
|
| `organized` | ✅ | Reactive record mapping with access objects |
|
|
139
139
|
| `project` | ❌ | Reactive collection mapping (manages a target + projection context) |
|
|
140
|
-
| `scan` | ❌ | Reactive accumulation (sequential key dependency) |
|
|
141
140
|
|
|
142
|
-
`project`
|
|
141
|
+
`project` has additional concerns (target management) that go beyond `attend`'s independent-key lifecycle model.
|
|
@@ -259,153 +259,6 @@ effect(() => {
|
|
|
259
259
|
})
|
|
260
260
|
```
|
|
261
261
|
|
|
262
|
-
### `Register`
|
|
263
|
-
|
|
264
|
-
`Register` is an ordered, array-like collection that keeps a stable mapping between keys and values. It is useful when you need array semantics (indexable access, ordering, iteration) but also require identity preservation by key—ideal for UI lists keyed by IDs or when you want to memoise entries across reorders.
|
|
265
|
-
|
|
266
|
-
```typescript
|
|
267
|
-
import { Register } from 'mutts/reactive'
|
|
268
|
-
|
|
269
|
-
// Create a register where the key comes from the `id` field
|
|
270
|
-
const list = new Register(({id}: { id: number }) => id, [
|
|
271
|
-
{ id: 1, label: 'Alpha' },
|
|
272
|
-
{ id: 2, label: 'Bravo' },
|
|
273
|
-
])
|
|
274
|
-
|
|
275
|
-
effect(() => {
|
|
276
|
-
console.log('Length:', list.length)
|
|
277
|
-
console.log('First label:', list[0]?.label)
|
|
278
|
-
})
|
|
279
|
-
|
|
280
|
-
// Push uses the key function to keep identities stable
|
|
281
|
-
list.push({ id: 3, label: 'Charlie' })
|
|
282
|
-
|
|
283
|
-
// Replacing with the same key updates watchers without creating a new identity
|
|
284
|
-
list[0] = { id: 1, label: 'Alpha (updated)' }
|
|
285
|
-
|
|
286
|
-
// Access by key
|
|
287
|
-
const second = list.get(2) // { id: 2, label: 'Bravo' }
|
|
288
|
-
|
|
289
|
-
// Duplicate keys share value identity
|
|
290
|
-
list.push({ id: 2, label: 'Bravo (new data)' })
|
|
291
|
-
console.log(list[1] === list[2]) // true
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
**Highlights:**
|
|
295
|
-
|
|
296
|
-
- Fully indexable (`list[0]`, `list.at(-1)`, `list.length`, iteration, etc.) thanks to the shared `Indexable` infrastructure.
|
|
297
|
-
- Complete array surface forwarding (`map`, `filter`, `reduce`, `concat`, `reverse`, `sort`, `fill`, `copyWithin`, and more) with reactivity preserved.
|
|
298
|
-
- Stable key/value map under the hood allows quick lookups via `get()`, `hasKey()`, and `indexOfKey()`.
|
|
299
|
-
- When the same key appears multiple times, all slots reference the same underlying value instance, making deduplication and memoisation straightforward.
|
|
300
|
-
- Reordering operations emit index-level touches so list reactivity remains predictable in rendered UIs.
|
|
301
|
-
|
|
302
|
-
### Register-specific API (beyond Array)
|
|
303
|
-
|
|
304
|
-
The `Register` exposes additional methods and behaviors that standard arrays do not have:
|
|
305
|
-
|
|
306
|
-
- `get(key)` / `set(key, value)`
|
|
307
|
-
- `get(key: K): T | undefined` returns the latest value for a key.
|
|
308
|
-
- `set(key: K, value: T): void` updates the value for an existing key (no-op if key absent).
|
|
309
|
-
- Example:
|
|
310
|
-
```typescript
|
|
311
|
-
list.set(2, { id: 2, label: 'Bravo (updated)' })
|
|
312
|
-
const v = list.get(2)
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
- `hasKey(key)` / `indexOfKey(key)`
|
|
316
|
-
- `hasKey(key: K): boolean` whether the key is present in any slot.
|
|
317
|
-
- `indexOfKey(key: K): number` first index at which the key appears, or `-1`.
|
|
318
|
-
|
|
319
|
-
- `remove(key)` / `removeAt(index)`
|
|
320
|
-
- `remove(key: K): void` removes all occurrences of `key` from the register.
|
|
321
|
-
- `removeAt(index: number): T | undefined` removes a single slot by index and returns its value.
|
|
322
|
-
|
|
323
|
-
- `keep(predicate)`
|
|
324
|
-
- `keep(predicate: (value: T) => boolean): void` keeps only items for which the predicate returns `true`; items for which it returns `false` are removed. The predicate is evaluated once per distinct key; duplicate keys follow the same decision.
|
|
325
|
-
|
|
326
|
-
- `update(...values)`
|
|
327
|
-
- `update(...values: T[]): void` updates existing entries by their key; ignores values whose key is not yet present.
|
|
328
|
-
|
|
329
|
-
- `upsert(insert, ...values)`
|
|
330
|
-
- `upsert(insert: (value: T) => void, ...values: T[]): void` updates by key when present, otherwise calls `insert(value)` so you can decide how to insert (e.g. `push`, `unshift`, or `splice`).
|
|
331
|
-
- Example:
|
|
332
|
-
```typescript
|
|
333
|
-
list.upsert(v => list.push(v), { id: 4, label: 'Delta' }, { id: 2, label: 'Bravo (again)' })
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
- `entries()`
|
|
337
|
-
- Iterates `[number, value]` pairs in index order: `IterableIterator<[number, T | undefined]>`.
|
|
338
|
-
|
|
339
|
-
- `keys` / `values`
|
|
340
|
-
- `keys: ArrayIterator<number>` provides the index iterator (mirrors `Array#keys()`).
|
|
341
|
-
- `values: IterableIterator<T>` provides an iterator of values (same as default iteration).
|
|
342
|
-
|
|
343
|
-
- `clear()`
|
|
344
|
-
- Removes all entries and disposes internal key-tracking effects.
|
|
345
|
-
|
|
346
|
-
- `toArray()` / `toString()`
|
|
347
|
-
- `toArray(): T[]` materializes the current values into a plain array.
|
|
348
|
-
- `toString(): string` returns a concise description like `[Register length=3]`.
|
|
349
|
-
|
|
350
|
-
### Register CRUD Events
|
|
351
|
-
|
|
352
|
-
`Register` emits lifecycle events for add, delete, update, and rekey operations. This enables side effects like logging, syncing with external systems, or triggering derived updates.
|
|
353
|
-
|
|
354
|
-
```typescript
|
|
355
|
-
const list = register(({id}: { id: number }) => id)
|
|
356
|
-
|
|
357
|
-
// Listen to individual events
|
|
358
|
-
list.on('add', (item, key, index) => {
|
|
359
|
-
console.log(`Added ${key} at index ${index}:`, item)
|
|
360
|
-
})
|
|
361
|
-
|
|
362
|
-
list.on('delete', (item, key, index) => {
|
|
363
|
-
console.log(`Removed ${key} from index ${index}:`, item)
|
|
364
|
-
})
|
|
365
|
-
|
|
366
|
-
list.on('update', (oldItem, newItem, key, index) => {
|
|
367
|
-
console.log(`Updated ${key} at index ${index}`)
|
|
368
|
-
})
|
|
369
|
-
|
|
370
|
-
list.on('rekey', (item, oldKey, newKey, index) => {
|
|
371
|
-
console.log(`Key changed from ${oldKey} to ${newKey}`)
|
|
372
|
-
})
|
|
373
|
-
|
|
374
|
-
// Bulk event registration
|
|
375
|
-
list.on({
|
|
376
|
-
add: (item) => console.log('Added:', item),
|
|
377
|
-
delete: (item) => console.log('Deleted:', item),
|
|
378
|
-
})
|
|
379
|
-
|
|
380
|
-
// Global hook - receive all events
|
|
381
|
-
const unhook = list.hook((event, ...args) => {
|
|
382
|
-
console.log(`Event: ${String(event)}`, args)
|
|
383
|
-
})
|
|
384
|
-
|
|
385
|
-
// Unsubscribe
|
|
386
|
-
const unsubscribe = list.on('add', handler)
|
|
387
|
-
unsubscribe()
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
**Event Types:**
|
|
391
|
-
|
|
392
|
-
| Event | Arguments | Description |
|
|
393
|
-
|-------|-----------|-------------|
|
|
394
|
-
| `add` | `(item, key, index)` | New item added to register |
|
|
395
|
-
| `delete` | `(item, key, index)` | Item removed from register |
|
|
396
|
-
| `update` | `(oldItem, newItem, key, index)` | Item value updated (same key) |
|
|
397
|
-
| `rekey` | `(item, oldKey, newKey, index)` | Item's key changed |
|
|
398
|
-
|
|
399
|
-
**Use Cases:**
|
|
400
|
-
- Audit logging
|
|
401
|
-
- Syncing with databases
|
|
402
|
-
- Triggering notifications
|
|
403
|
-
- Cascading updates to dependent systems
|
|
404
|
-
|
|
405
|
-
Notes:
|
|
406
|
-
- Direct length modification via `list.length = n` is not supported; use `splice` instead.
|
|
407
|
-
- Assigning to an index (`list[i] = value`) uses the key function to bind that slot to `value`’s key.
|
|
408
|
-
|
|
409
262
|
## Class Reactivity
|
|
410
263
|
|
|
411
264
|
## Morphing
|
|
@@ -417,7 +270,7 @@ Notes:
|
|
|
417
270
|
#### Basic Usage
|
|
418
271
|
|
|
419
272
|
```typescript
|
|
420
|
-
import { cleanup, morph, reactive } from 'mutts
|
|
273
|
+
import { cleanup, morph, reactive } from 'mutts'
|
|
421
274
|
|
|
422
275
|
// Arrays
|
|
423
276
|
const users = reactive([{ name: 'John', age: 30 }, { name: 'Jane', age: 25 }])
|
|
@@ -548,7 +401,7 @@ result[cleanup]() // Stops all effects and cleans up
|
|
|
548
401
|
### `organized()`
|
|
549
402
|
|
|
550
403
|
```typescript
|
|
551
|
-
import { cleanup, organized, reactive } from 'mutts
|
|
404
|
+
import { cleanup, organized, reactive } from 'mutts'
|
|
552
405
|
|
|
553
406
|
const source = reactive<Record<string, number>>({ apples: 1, oranges: 2 })
|
|
554
407
|
|