mutts 1.0.6 → 1.0.7
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 +1 -1
- package/dist/browser.d.ts +2 -0
- package/dist/browser.esm.js +70 -0
- package/dist/browser.esm.js.map +1 -0
- package/dist/browser.js +161 -0
- package/dist/browser.js.map +1 -0
- package/dist/chunks/{index-CDCOjzTy.js → index-BFYK02LG.js} +5760 -4338
- package/dist/chunks/index-BFYK02LG.js.map +1 -0
- package/dist/chunks/{index-DiP0RXoZ.esm.js → index-CNR6QRUl.esm.js} +5440 -4054
- package/dist/chunks/index-CNR6QRUl.esm.js.map +1 -0
- package/dist/devtools/panel.js.map +1 -1
- package/dist/mutts.umd.js +1 -1
- 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.d.ts +2 -0
- package/dist/node.esm.js +45 -0
- package/dist/node.esm.js.map +1 -0
- package/dist/node.js +136 -0
- package/dist/node.js.map +1 -0
- package/docs/ai/api-reference.md +0 -2
- package/docs/reactive/advanced.md +2 -5
- package/docs/reactive/collections.md +0 -125
- package/docs/reactive/core.md +27 -24
- package/docs/reactive/debugging.md +12 -2
- package/docs/reactive/project.md +1 -1
- package/docs/reactive/scan.md +78 -0
- package/docs/reactive.md +2 -1
- package/docs/std-decorators.md +1 -0
- package/docs/zone.md +88 -0
- package/package.json +42 -10
- package/src/async/browser.ts +87 -0
- package/src/async/index.ts +8 -0
- package/src/async/node.ts +46 -0
- package/src/decorator.ts +5 -1
- package/src/destroyable.ts +1 -1
- package/src/index.ts +22 -14
- package/src/indexable.ts +42 -0
- package/src/mixins.ts +2 -2
- package/src/reactive/array.ts +149 -141
- package/src/reactive/buffer.ts +168 -0
- package/src/reactive/change.ts +2 -2
- package/src/reactive/effect-context.ts +15 -91
- package/src/reactive/effects.ts +119 -179
- package/src/reactive/index.ts +10 -13
- package/src/reactive/interface.ts +19 -33
- package/src/reactive/map.ts +48 -61
- package/src/reactive/memoize.ts +19 -9
- package/src/reactive/project.ts +43 -22
- package/src/reactive/proxy.ts +16 -41
- package/src/reactive/record.ts +3 -3
- package/src/reactive/register.ts +5 -7
- package/src/reactive/registry.ts +9 -17
- package/src/reactive/set.ts +42 -56
- package/src/reactive/tracking.ts +1 -29
- package/src/reactive/types.ts +46 -23
- package/src/utils.ts +80 -37
- package/src/zone.ts +127 -0
- package/dist/chunks/_tslib-BgjropY9.js +0 -81
- package/dist/chunks/_tslib-BgjropY9.js.map +0 -1
- package/dist/chunks/_tslib-MCKDzsSq.esm.js +0 -75
- package/dist/chunks/_tslib-MCKDzsSq.esm.js.map +0 -1
- package/dist/chunks/decorator-BGILvPtN.esm.js +0 -627
- package/dist/chunks/decorator-BGILvPtN.esm.js.map +0 -1
- package/dist/chunks/decorator-BQ2eBTCj.js +0 -651
- package/dist/chunks/decorator-BQ2eBTCj.js.map +0 -1
- package/dist/chunks/index-CDCOjzTy.js.map +0 -1
- package/dist/chunks/index-DiP0RXoZ.esm.js.map +0 -1
- package/dist/decorator.d.ts +0 -107
- package/dist/decorator.esm.js +0 -2
- package/dist/decorator.esm.js.map +0 -1
- package/dist/decorator.js +0 -11
- package/dist/decorator.js.map +0 -1
- package/dist/destroyable.d.ts +0 -90
- package/dist/destroyable.esm.js +0 -109
- package/dist/destroyable.esm.js.map +0 -1
- package/dist/destroyable.js +0 -116
- package/dist/destroyable.js.map +0 -1
- package/dist/eventful.d.ts +0 -20
- package/dist/eventful.esm.js +0 -66
- package/dist/eventful.esm.js.map +0 -1
- package/dist/eventful.js +0 -68
- package/dist/eventful.js.map +0 -1
- package/dist/index.d.ts +0 -19
- package/dist/index.esm.js +0 -53
- package/dist/index.esm.js.map +0 -1
- package/dist/index.js +0 -139
- package/dist/index.js.map +0 -1
- package/dist/indexable.d.ts +0 -243
- package/dist/indexable.esm.js +0 -285
- package/dist/indexable.esm.js.map +0 -1
- package/dist/indexable.js +0 -291
- package/dist/indexable.js.map +0 -1
- package/dist/promiseChain.d.ts +0 -21
- package/dist/promiseChain.esm.js +0 -78
- package/dist/promiseChain.esm.js.map +0 -1
- package/dist/promiseChain.js +0 -80
- package/dist/promiseChain.js.map +0 -1
- package/dist/reactive.d.ts +0 -910
- package/dist/reactive.esm.js +0 -5
- package/dist/reactive.esm.js.map +0 -1
- package/dist/reactive.js +0 -59
- package/dist/reactive.js.map +0 -1
- package/dist/std-decorators.d.ts +0 -52
- package/dist/std-decorators.esm.js +0 -196
- package/dist/std-decorators.esm.js.map +0 -1
- package/dist/std-decorators.js +0 -204
- package/dist/std-decorators.js.map +0 -1
- package/src/reactive/mapped.ts +0 -129
- package/src/reactive/zone.ts +0 -208
package/dist/node.d.ts
ADDED
package/dist/node.esm.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createHook } from 'node:async_hooks';
|
|
2
|
+
import { a as asyncHooks } from './chunks/index-CNR6QRUl.esm.js';
|
|
3
|
+
export { A as AZone, b as ArrayReadForward, D as DecoratorError, c as Destroyable, d as DestructionError, E as Eventful, F as FoolProof, I as Indexable, e as IterableWeakMap, f as IterableWeakSet, R as ReactiveBase, g as ReactiveError, h as ReactiveErrorCode, i as Register, Z as Zone, j as ZoneAggregator, k as ZoneHistory, l as addBatchCleanup, m as allocated, n as allocatedValues, o as arrayEquals, p as asyncZone, q as atomic, r as biDi, s as buildReactivityGraph, t as cache, u as cached, v as callOnGC, w as chainPromise, x as cleanedBy, y as cleanup, z as contentRef, B as debounce, C as decorator, G as deepCompare, H as deepWatch, J as defer, K as deprecated, L as derived, M as describe, N as destructor, O as effect, P as enableDevTools, Q as forwardArray, S as getActivationLog, T as getActiveProjection, U as getAt, V as getState, W as immutables, X as isCached, Y as isConstructor, _ as isDevtoolsEnabled, $ as isNonReactive, a0 as isOwnAccessor, a1 as isReactive, a2 as legacyDecorator, a3 as memoize, a4 as mixin, a5 as modernDecorator, a6 as named, a7 as organize, a8 as organized, a9 as profileInfo, aa as project, ab as reactive, ac as reactiveOptions, ad as register, ae as registerEffectForDebug, af as registerNativeReactivity, ag as registerObjectForDebug, ah as renamed, ai as root, aj as scan, ak as setAt, al as setEffectName, am as setObjectName, an as tag, ao as throttle, ap as touched, aq as touched1, ar as trackEffect, as as unreactive, at as untracked, au as unwrap, av as watch, aw as zip } from './chunks/index-CNR6QRUl.esm.js';
|
|
4
|
+
|
|
5
|
+
const hooks = new Set();
|
|
6
|
+
const restorersPerAsyncId = new Map();
|
|
7
|
+
const undoersPerAsyncId = new Map();
|
|
8
|
+
createHook({
|
|
9
|
+
init(asyncId) {
|
|
10
|
+
const restorers = new Set();
|
|
11
|
+
for (const hook of hooks) {
|
|
12
|
+
restorers.add(hook());
|
|
13
|
+
}
|
|
14
|
+
restorersPerAsyncId.set(asyncId, restorers);
|
|
15
|
+
},
|
|
16
|
+
before(asyncId) {
|
|
17
|
+
const restorers = restorersPerAsyncId.get(asyncId);
|
|
18
|
+
if (restorers) {
|
|
19
|
+
const undoers = new Set();
|
|
20
|
+
for (const restore of restorers) {
|
|
21
|
+
undoers.add(restore());
|
|
22
|
+
}
|
|
23
|
+
undoersPerAsyncId.set(asyncId, undoers);
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
after(asyncId) {
|
|
27
|
+
const undoers = undoersPerAsyncId.get(asyncId);
|
|
28
|
+
if (undoers) {
|
|
29
|
+
for (const undo of undoers)
|
|
30
|
+
undo();
|
|
31
|
+
undoersPerAsyncId.delete(asyncId);
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
destroy(asyncId) {
|
|
35
|
+
restorersPerAsyncId.delete(asyncId);
|
|
36
|
+
undoersPerAsyncId.delete(asyncId);
|
|
37
|
+
}
|
|
38
|
+
}).enable();
|
|
39
|
+
asyncHooks.addHook = function (hook) {
|
|
40
|
+
hooks.add(hook);
|
|
41
|
+
return () => {
|
|
42
|
+
hooks.delete(hook);
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=node.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.esm.js","sources":["../src/async/node.ts"],"sourcesContent":["import { createHook } from 'node:async_hooks'\nimport { Hook, Restorer, asyncHooks } from '.'\n\nconst hooks = new Set<Hook>()\nconst restorersPerAsyncId = new Map<number, Set<Restorer>>()\nconst undoersPerAsyncId = new Map<number, Set<() => void>>()\n\ncreateHook({\n\tinit(asyncId) {\n\t\tconst restorers = new Set<Restorer>()\n\t\tfor (const hook of hooks) {\n\t\t\trestorers.add(hook())\n\t\t}\n\t\trestorersPerAsyncId.set(asyncId, restorers)\n\t},\n\tbefore(asyncId) {\n\t\tconst restorers = restorersPerAsyncId.get(asyncId)\n\t\tif (restorers) {\n\t\t\tconst undoers = new Set<() => void>()\n\t\t\tfor (const restore of restorers) {\n\t\t\t\tundoers.add(restore())\n\t\t\t}\n\t\t\tundoersPerAsyncId.set(asyncId, undoers)\n\t\t}\n\t},\n\tafter(asyncId) {\n\t\tconst undoers = undoersPerAsyncId.get(asyncId)\n\t\tif (undoers) {\n\t\t\tfor (const undo of undoers) undo()\n\t\t\tundoersPerAsyncId.delete(asyncId)\n\t\t}\n\t},\n\tdestroy(asyncId) {\n\t\trestorersPerAsyncId.delete(asyncId)\n\t\tundoersPerAsyncId.delete(asyncId)\n\t}\n}).enable()\n\nasyncHooks.addHook = function (hook: Hook) {\n\thooks.add(hook)\n\treturn () => {\n\t\thooks.delete(hook)\n\t}\n}\n\nexport * from '../index'\n"],"names":[],"mappings":";;;;AAGA,MAAM,KAAK,GAAG,IAAI,GAAG,EAAQ;AAC7B,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAAyB;AAC5D,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAA2B;AAE5D,UAAU,CAAC;AACV,IAAA,IAAI,CAAC,OAAO,EAAA;AACX,QAAA,MAAM,SAAS,GAAG,IAAI,GAAG,EAAY;AACrC,QAAA,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;AACzB,YAAA,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACtB;AACA,QAAA,mBAAmB,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5C,CAAC;AACD,IAAA,MAAM,CAAC,OAAO,EAAA;QACb,MAAM,SAAS,GAAG,mBAAmB,CAAC,GAAG,CAAC,OAAO,CAAC;QAClD,IAAI,SAAS,EAAE;AACd,YAAA,MAAM,OAAO,GAAG,IAAI,GAAG,EAAc;AACrC,YAAA,KAAK,MAAM,OAAO,IAAI,SAAS,EAAE;AAChC,gBAAA,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;YACvB;AACA,YAAA,iBAAiB,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC;QACxC;IACD,CAAC;AACD,IAAA,KAAK,CAAC,OAAO,EAAA;QACZ,MAAM,OAAO,GAAG,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC;QAC9C,IAAI,OAAO,EAAE;YACZ,KAAK,MAAM,IAAI,IAAI,OAAO;AAAE,gBAAA,IAAI,EAAE;AAClC,YAAA,iBAAiB,CAAC,MAAM,CAAC,OAAO,CAAC;QAClC;IACD,CAAC;AACD,IAAA,OAAO,CAAC,OAAO,EAAA;AACd,QAAA,mBAAmB,CAAC,MAAM,CAAC,OAAO,CAAC;AACnC,QAAA,iBAAiB,CAAC,MAAM,CAAC,OAAO,CAAC;IAClC;CACA,CAAC,CAAC,MAAM,EAAE;AAEX,UAAU,CAAC,OAAO,GAAG,UAAU,IAAU,EAAA;AACxC,IAAA,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;AACf,IAAA,OAAO,MAAK;AACX,QAAA,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC;AACnB,IAAA,CAAC;AACF,CAAC"}
|
package/dist/node.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var node_async_hooks = require('node:async_hooks');
|
|
4
|
+
var index = require('./chunks/index-BFYK02LG.js');
|
|
5
|
+
|
|
6
|
+
const hooks = new Set();
|
|
7
|
+
const restorersPerAsyncId = new Map();
|
|
8
|
+
const undoersPerAsyncId = new Map();
|
|
9
|
+
node_async_hooks.createHook({
|
|
10
|
+
init(asyncId) {
|
|
11
|
+
const restorers = new Set();
|
|
12
|
+
for (const hook of hooks) {
|
|
13
|
+
restorers.add(hook());
|
|
14
|
+
}
|
|
15
|
+
restorersPerAsyncId.set(asyncId, restorers);
|
|
16
|
+
},
|
|
17
|
+
before(asyncId) {
|
|
18
|
+
const restorers = restorersPerAsyncId.get(asyncId);
|
|
19
|
+
if (restorers) {
|
|
20
|
+
const undoers = new Set();
|
|
21
|
+
for (const restore of restorers) {
|
|
22
|
+
undoers.add(restore());
|
|
23
|
+
}
|
|
24
|
+
undoersPerAsyncId.set(asyncId, undoers);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
after(asyncId) {
|
|
28
|
+
const undoers = undoersPerAsyncId.get(asyncId);
|
|
29
|
+
if (undoers) {
|
|
30
|
+
for (const undo of undoers)
|
|
31
|
+
undo();
|
|
32
|
+
undoersPerAsyncId.delete(asyncId);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
destroy(asyncId) {
|
|
36
|
+
restorersPerAsyncId.delete(asyncId);
|
|
37
|
+
undoersPerAsyncId.delete(asyncId);
|
|
38
|
+
}
|
|
39
|
+
}).enable();
|
|
40
|
+
index.asyncHooks.addHook = function (hook) {
|
|
41
|
+
hooks.add(hook);
|
|
42
|
+
return () => {
|
|
43
|
+
hooks.delete(hook);
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
exports.AZone = index.AZone;
|
|
48
|
+
exports.ArrayReadForward = index.ArrayReadForward;
|
|
49
|
+
exports.DecoratorError = index.DecoratorError;
|
|
50
|
+
exports.Destroyable = index.Destroyable;
|
|
51
|
+
exports.DestructionError = index.DestructionError;
|
|
52
|
+
exports.Eventful = index.Eventful;
|
|
53
|
+
exports.FoolProof = index.FoolProof;
|
|
54
|
+
exports.Indexable = index.Indexable;
|
|
55
|
+
exports.IterableWeakMap = index.IterableWeakMap;
|
|
56
|
+
exports.IterableWeakSet = index.IterableWeakSet;
|
|
57
|
+
exports.ReactiveBase = index.ReactiveBase;
|
|
58
|
+
exports.ReactiveError = index.ReactiveError;
|
|
59
|
+
Object.defineProperty(exports, "ReactiveErrorCode", {
|
|
60
|
+
enumerable: true,
|
|
61
|
+
get: function () { return index.ReactiveErrorCode; }
|
|
62
|
+
});
|
|
63
|
+
exports.Register = index.Register;
|
|
64
|
+
exports.Zone = index.Zone;
|
|
65
|
+
exports.ZoneAggregator = index.ZoneAggregator;
|
|
66
|
+
exports.ZoneHistory = index.ZoneHistory;
|
|
67
|
+
exports.addBatchCleanup = index.addBatchCleanup;
|
|
68
|
+
exports.allocated = index.allocated;
|
|
69
|
+
exports.allocatedValues = index.allocatedValues;
|
|
70
|
+
exports.arrayEquals = index.arrayEquals;
|
|
71
|
+
exports.asyncZone = index.asyncZone;
|
|
72
|
+
exports.atomic = index.atomic;
|
|
73
|
+
exports.biDi = index.biDi;
|
|
74
|
+
exports.buildReactivityGraph = index.buildReactivityGraph;
|
|
75
|
+
exports.cache = index.cache;
|
|
76
|
+
exports.cached = index.cached;
|
|
77
|
+
exports.callOnGC = index.callOnGC;
|
|
78
|
+
exports.chainPromise = index.chainPromise;
|
|
79
|
+
exports.cleanedBy = index.cleanedBy;
|
|
80
|
+
exports.cleanup = index.cleanup;
|
|
81
|
+
exports.contentRef = index.contentRef;
|
|
82
|
+
exports.debounce = index.debounce;
|
|
83
|
+
exports.decorator = index.decorator;
|
|
84
|
+
exports.deepCompare = index.deepCompare;
|
|
85
|
+
exports.deepWatch = index.deepWatch;
|
|
86
|
+
exports.defer = index.defer;
|
|
87
|
+
exports.deprecated = index.deprecated;
|
|
88
|
+
exports.derived = index.derived;
|
|
89
|
+
exports.describe = index.describe;
|
|
90
|
+
exports.destructor = index.destructor;
|
|
91
|
+
exports.effect = index.effect;
|
|
92
|
+
exports.enableDevTools = index.enableDevTools;
|
|
93
|
+
exports.forwardArray = index.forwardArray;
|
|
94
|
+
exports.getActivationLog = index.getActivationLog;
|
|
95
|
+
exports.getActiveProjection = index.getActiveProjection;
|
|
96
|
+
exports.getAt = index.getAt;
|
|
97
|
+
exports.getState = index.getState;
|
|
98
|
+
exports.immutables = index.immutables;
|
|
99
|
+
exports.isCached = index.isCached;
|
|
100
|
+
exports.isConstructor = index.isConstructor;
|
|
101
|
+
exports.isDevtoolsEnabled = index.isDevtoolsEnabled;
|
|
102
|
+
exports.isNonReactive = index.isNonReactive;
|
|
103
|
+
exports.isOwnAccessor = index.isOwnAccessor;
|
|
104
|
+
exports.isReactive = index.isReactive;
|
|
105
|
+
exports.legacyDecorator = index.legacyDecorator;
|
|
106
|
+
exports.memoize = index.memoize;
|
|
107
|
+
exports.mixin = index.mixin;
|
|
108
|
+
exports.modernDecorator = index.modernDecorator;
|
|
109
|
+
exports.named = index.named;
|
|
110
|
+
exports.organize = index.organize;
|
|
111
|
+
exports.organized = index.organized;
|
|
112
|
+
exports.profileInfo = index.profileInfo;
|
|
113
|
+
exports.project = index.project;
|
|
114
|
+
exports.reactive = index.reactive;
|
|
115
|
+
exports.reactiveOptions = index.options;
|
|
116
|
+
exports.register = index.register;
|
|
117
|
+
exports.registerEffectForDebug = index.registerEffectForDebug;
|
|
118
|
+
exports.registerNativeReactivity = index.registerNativeReactivity;
|
|
119
|
+
exports.registerObjectForDebug = index.registerObjectForDebug;
|
|
120
|
+
exports.renamed = index.renamed;
|
|
121
|
+
exports.root = index.root;
|
|
122
|
+
exports.scan = index.scan;
|
|
123
|
+
exports.setAt = index.setAt;
|
|
124
|
+
exports.setEffectName = index.setEffectName;
|
|
125
|
+
exports.setObjectName = index.setObjectName;
|
|
126
|
+
exports.tag = index.tag;
|
|
127
|
+
exports.throttle = index.throttle;
|
|
128
|
+
exports.touched = index.touched;
|
|
129
|
+
exports.touched1 = index.touched1;
|
|
130
|
+
exports.trackEffect = index.trackEffect;
|
|
131
|
+
exports.unreactive = index.unreactive;
|
|
132
|
+
exports.untracked = index.untracked;
|
|
133
|
+
exports.unwrap = index.unwrap;
|
|
134
|
+
exports.watch = index.watch;
|
|
135
|
+
exports.zip = index.zip;
|
|
136
|
+
//# sourceMappingURL=node.js.map
|
package/dist/node.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.js","sources":["../src/async/node.ts"],"sourcesContent":["import { createHook } from 'node:async_hooks'\nimport { Hook, Restorer, asyncHooks } from '.'\n\nconst hooks = new Set<Hook>()\nconst restorersPerAsyncId = new Map<number, Set<Restorer>>()\nconst undoersPerAsyncId = new Map<number, Set<() => void>>()\n\ncreateHook({\n\tinit(asyncId) {\n\t\tconst restorers = new Set<Restorer>()\n\t\tfor (const hook of hooks) {\n\t\t\trestorers.add(hook())\n\t\t}\n\t\trestorersPerAsyncId.set(asyncId, restorers)\n\t},\n\tbefore(asyncId) {\n\t\tconst restorers = restorersPerAsyncId.get(asyncId)\n\t\tif (restorers) {\n\t\t\tconst undoers = new Set<() => void>()\n\t\t\tfor (const restore of restorers) {\n\t\t\t\tundoers.add(restore())\n\t\t\t}\n\t\t\tundoersPerAsyncId.set(asyncId, undoers)\n\t\t}\n\t},\n\tafter(asyncId) {\n\t\tconst undoers = undoersPerAsyncId.get(asyncId)\n\t\tif (undoers) {\n\t\t\tfor (const undo of undoers) undo()\n\t\t\tundoersPerAsyncId.delete(asyncId)\n\t\t}\n\t},\n\tdestroy(asyncId) {\n\t\trestorersPerAsyncId.delete(asyncId)\n\t\tundoersPerAsyncId.delete(asyncId)\n\t}\n}).enable()\n\nasyncHooks.addHook = function (hook: Hook) {\n\thooks.add(hook)\n\treturn () => {\n\t\thooks.delete(hook)\n\t}\n}\n\nexport * from '../index'\n"],"names":["createHook","asyncHooks"],"mappings":";;;;;AAGA,MAAM,KAAK,GAAG,IAAI,GAAG,EAAQ;AAC7B,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAAyB;AAC5D,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAA2B;AAE5DA,2BAAU,CAAC;AACV,IAAA,IAAI,CAAC,OAAO,EAAA;AACX,QAAA,MAAM,SAAS,GAAG,IAAI,GAAG,EAAY;AACrC,QAAA,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;AACzB,YAAA,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACtB;AACA,QAAA,mBAAmB,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5C,CAAC;AACD,IAAA,MAAM,CAAC,OAAO,EAAA;QACb,MAAM,SAAS,GAAG,mBAAmB,CAAC,GAAG,CAAC,OAAO,CAAC;QAClD,IAAI,SAAS,EAAE;AACd,YAAA,MAAM,OAAO,GAAG,IAAI,GAAG,EAAc;AACrC,YAAA,KAAK,MAAM,OAAO,IAAI,SAAS,EAAE;AAChC,gBAAA,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;YACvB;AACA,YAAA,iBAAiB,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC;QACxC;IACD,CAAC;AACD,IAAA,KAAK,CAAC,OAAO,EAAA;QACZ,MAAM,OAAO,GAAG,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC;QAC9C,IAAI,OAAO,EAAE;YACZ,KAAK,MAAM,IAAI,IAAI,OAAO;AAAE,gBAAA,IAAI,EAAE;AAClC,YAAA,iBAAiB,CAAC,MAAM,CAAC,OAAO,CAAC;QAClC;IACD,CAAC;AACD,IAAA,OAAO,CAAC,OAAO,EAAA;AACd,QAAA,mBAAmB,CAAC,MAAM,CAAC,OAAO,CAAC;AACnC,QAAA,iBAAiB,CAAC,MAAM,CAAC,OAAO,CAAC;IAClC;CACA,CAAC,CAAC,MAAM,EAAE;AAEXC,gBAAU,CAAC,OAAO,GAAG,UAAU,IAAU,EAAA;AACxC,IAAA,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;AACf,IAAA,OAAO,MAAK;AACX,QAAA,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC;AACnB,IAAA,CAAC;AACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/docs/ai/api-reference.md
CHANGED
|
@@ -68,8 +68,6 @@ export interface Register<T, K extends PropertyKey = PropertyKey> extends Iterab
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
export declare function register<T, K extends PropertyKey = PropertyKey>(keyFn: (item: T) => K, initial?: Iterable<T>): Register<T, K>;
|
|
71
|
-
export declare function mapped<T, U>(inputs: readonly T[], compute: (input: T, index: number, output: U[]) => U): readonly U[];
|
|
72
|
-
export declare function reduced<T, U, R extends object = any>(inputs: readonly T[], compute: (input: T, factor: R) => readonly U[]): readonly U[];
|
|
73
71
|
export declare function project<S, R>(source: S, apply: (access: any, target: any) => any): R;
|
|
74
72
|
export declare function organized<S, T>(source: S, apply: (access: any, target: T) => any, baseTarget?: T): T;
|
|
75
73
|
|
|
@@ -1161,10 +1161,6 @@ class Example {
|
|
|
1161
1161
|
}
|
|
1162
1162
|
```
|
|
1163
1163
|
|
|
1164
|
-
### Working with `mapped()`
|
|
1165
|
-
|
|
1166
|
-
Pair `memoize()` with [`mapped()`](#mapped) to keep derived array elements stable across reorders (see [Identity-preserving mapped arrays](#identity-preserving-mapped-arrays)). Memoizing the mapper ensures each input object is processed at most once and its derived state persists while the input reference lives.
|
|
1167
|
-
|
|
1168
1164
|
## Debugging and Development
|
|
1169
1165
|
|
|
1170
1166
|
The `mutts` reactive system includes built-in tools for troubleshooting complex dependency graphs and identifying performance bottlenecks.
|
|
@@ -1173,7 +1169,8 @@ For a full guide on debugging, including cycle detection and memoization discrep
|
|
|
1173
1169
|
|
|
1174
1170
|
### Quick Summary
|
|
1175
1171
|
|
|
1176
|
-
- **Cycle Detection**: Automatically catch circular dependencies via `reactiveOptions.cycleHandling`.
|
|
1172
|
+
- **Cycle Detection**: Automatically catch circular dependencies via `reactiveOptions.cycleHandling`. Note: Instant mathematical detection requires choosing a mode other than `'none'`.
|
|
1173
|
+
- **Flat Mode**: The default `reactiveOptions.cycleHandling = 'none'` provides maximum performance in high-frequency update scenarios by disabling graph maintenance.
|
|
1177
1174
|
- **Memoization Discrepancy**: Detect "missing dependencies" by running computations twice during development using `reactiveOptions.onMemoizationDiscrepancy`.
|
|
1178
1175
|
- **Global Hooks**: Use `reactiveOptions.touched`, `enter`, and `leave` to observe system activity.
|
|
1179
1176
|
|
|
@@ -352,115 +352,6 @@ Notes:
|
|
|
352
352
|
- Assigning to an index (`list[i] = value`) uses the key function to bind that slot to `value`’s key.
|
|
353
353
|
|
|
354
354
|
## Class Reactivity
|
|
355
|
-
## Array Mapping
|
|
356
|
-
|
|
357
|
-
### `mapped()`
|
|
358
|
-
|
|
359
|
-
Creates a reactive array by mapping over an input array. The mapper receives the current item value, its index, and the previous mapped value for that index.
|
|
360
|
-
|
|
361
|
-
```typescript
|
|
362
|
-
import { mapped, reactive } from 'mutts/reactive'
|
|
363
|
-
|
|
364
|
-
const input = reactive([1, 2, 3])
|
|
365
|
-
const doubles = mapped(input, (value, index, oldValue) => value * 2)
|
|
366
|
-
|
|
367
|
-
console.log(doubles) // [2, 4, 6]
|
|
368
|
-
|
|
369
|
-
// When input changes, the mapped output updates in place
|
|
370
|
-
input.push(4)
|
|
371
|
-
console.log(doubles) // [2, 4, 6, 8]
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
**Mapper signature:**
|
|
375
|
-
|
|
376
|
-
```typescript
|
|
377
|
-
(value: T, index: number, oldValue?: U) => U
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
- **value**: current element from the input array
|
|
381
|
-
- **index**: current element index
|
|
382
|
-
- **oldValue**: previously computed value at the same index (useful for incremental updates)
|
|
383
|
-
|
|
384
|
-
**Key features:**
|
|
385
|
-
|
|
386
|
-
- **Live reactivity**: Output array updates when the input array changes (push/pop/splice/assignments).
|
|
387
|
-
- **Granular recompute**: Only indices that change are recomputed; `oldValue` enables incremental updates.
|
|
388
|
-
- **Simple contract**: Mapper works directly with `(value, index, oldValue)` and can freely return reactive objects.
|
|
389
|
-
|
|
390
|
-
**Performance characteristics:**
|
|
391
|
-
|
|
392
|
-
```typescript
|
|
393
|
-
const users = reactive([
|
|
394
|
-
{ name: 'John', age: 30 },
|
|
395
|
-
{ name: 'Jane', age: 25 }
|
|
396
|
-
])
|
|
397
|
-
|
|
398
|
-
let computeCount = 0
|
|
399
|
-
const processedUsers = mapped(users, (user) => {
|
|
400
|
-
computeCount++
|
|
401
|
-
return `${user.name} (${user.age})`
|
|
402
|
-
})
|
|
403
|
-
|
|
404
|
-
console.log(computeCount) // 2 (initial computation)
|
|
405
|
-
|
|
406
|
-
// Modify one user - only that index recomputes
|
|
407
|
-
users[0].age = 31
|
|
408
|
-
console.log(processedUsers[0]) // "John (31)"
|
|
409
|
-
console.log(computeCount) // 3
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
**Advanced usage:**
|
|
413
|
-
|
|
414
|
-
```typescript
|
|
415
|
-
const orders = reactive([
|
|
416
|
-
{ items: [{ price: 10 }, { price: 20 }] },
|
|
417
|
-
{ items: [{ price: 15 }] }
|
|
418
|
-
])
|
|
419
|
-
|
|
420
|
-
const orderTotals = mapped(orders, (order) => (
|
|
421
|
-
order.items.reduce((sum, item) => sum + item.price, 0)
|
|
422
|
-
))
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
### Identity-preserving mapped arrays
|
|
426
|
-
|
|
427
|
-
Combine `mapped()` with [`memoize()`](#memoize) when you need to reuse mapped results for the same input identity. The memoized mapper runs at most once per input object, even when the source array is reordered, and it can host additional reactive state that should survive reordering.
|
|
428
|
-
|
|
429
|
-
```typescript
|
|
430
|
-
import { effect, mapped, memoize, reactive } from 'mutts/reactive'
|
|
431
|
-
|
|
432
|
-
const inputs = reactive([{ name: 'John' }, { name: 'Jane' }])
|
|
433
|
-
|
|
434
|
-
const memoizedCard = memoize((user: { name: string }) => {
|
|
435
|
-
const view: { name?: string; setName(next: string): void } = {
|
|
436
|
-
setName(next) {
|
|
437
|
-
user.name = next
|
|
438
|
-
},
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
effect(() => {
|
|
442
|
-
view.name = user.name.toUpperCase()
|
|
443
|
-
})
|
|
444
|
-
|
|
445
|
-
return view
|
|
446
|
-
})
|
|
447
|
-
|
|
448
|
-
const cards = mapped(inputs, (user) => memoizedCard(user))
|
|
449
|
-
|
|
450
|
-
cards[0].setName('Johnny')
|
|
451
|
-
console.log(cards[0].name) // 'JOHNNY'
|
|
452
|
-
|
|
453
|
-
// Reorder: cached output follows the original object
|
|
454
|
-
const first = inputs.shift()!
|
|
455
|
-
inputs.push(first)
|
|
456
|
-
console.log(cards[1].name) // still 'JOHNNY'
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
Use this pattern when:
|
|
460
|
-
|
|
461
|
-
- You are mapping an array of reactive objects and want to keep derived objects stable across reorders.
|
|
462
|
-
- The mapper returns objects with internal state or nested effects that should survive reordering.
|
|
463
|
-
- You prefer to share memoized helpers across multiple mapped arrays.
|
|
464
355
|
|
|
465
356
|
## Projection
|
|
466
357
|
|
|
@@ -468,8 +359,6 @@ Use this pattern when:
|
|
|
468
359
|
|
|
469
360
|
`project()` provides a unified API for transforming reactive collections (arrays, records, and maps) into new reactive collections. Each source entry gets its own reactive effect that recomputes only when that specific entry changes, enabling granular updates perfect for rendering pipelines.
|
|
470
361
|
|
|
471
|
-
**Note:** `project()` is the modern replacement for `mapped()`. It offers the same per-entry reactivity benefits but works across all collection types with a consistent API.
|
|
472
|
-
|
|
473
362
|
#### Basic Usage
|
|
474
363
|
|
|
475
364
|
```typescript
|
|
@@ -617,23 +506,10 @@ result[cleanup]() // Stops all effects and cleans up
|
|
|
617
506
|
- **Data Transformation**: Convert between collection types while maintaining reactivity
|
|
618
507
|
- **Performance Optimization**: Avoid full recomputation when only a few entries change
|
|
619
508
|
|
|
620
|
-
#### Comparison with `mapped()`
|
|
621
|
-
|
|
622
|
-
`project()` is designed to eventually replace `mapped()`. Key differences:
|
|
623
|
-
|
|
624
|
-
- **Unified API**: Works with arrays, records, and maps (vs. `mapped()` only for arrays)
|
|
625
|
-
- **Access Pattern**: Uses an access object with `get()`/`set()` instead of direct value/index parameters
|
|
626
|
-
- **Automatic Target Creation**: Creates its own reactive target container (no need to provide a base target)
|
|
627
|
-
- **Consistent Behavior**: Same per-entry reactivity model across all collection types
|
|
628
|
-
|
|
629
|
-
For new code, prefer `project()` over `mapped()`. Existing `mapped()` code will continue to work, but consider migrating for better consistency and future features.
|
|
630
|
-
|
|
631
509
|
## Record Organization
|
|
632
510
|
|
|
633
511
|
### `organized()`
|
|
634
512
|
|
|
635
|
-
`organized()` is the record companion to [`mapped()`](#mapped). Instead of iterating over numeric indices, it reacts to property additions, updates, and deletions on any `Record<PropertyKey, T>` (plain objects, dictionaries, even reactive proxies) and lets you build **whatever target structure you need**—a new record, nested buckets, a `Map`, or a more elaborate object with metadata.
|
|
636
|
-
|
|
637
513
|
```typescript
|
|
638
514
|
import { cleanup, organized, reactive } from 'mutts/reactive'
|
|
639
515
|
|
|
@@ -677,7 +553,6 @@ function organized<
|
|
|
677
553
|
|
|
678
554
|
Under the hood there is:
|
|
679
555
|
|
|
680
|
-
- One effect watching `Reflect.ownKeys(source)` (similar to how `mapped()` tracks `length`)
|
|
681
556
|
- A child effect per key that re-runs whenever that key’s value changes, automatically reusing and replacing the cleanup you returned.
|
|
682
557
|
- Automatic disposal when keys disappear or when `target[cleanup]()` is invoked.
|
|
683
558
|
|
package/docs/reactive/core.md
CHANGED
|
@@ -105,7 +105,7 @@ const result = memoized(user)
|
|
|
105
105
|
**5. Map over arrays:**
|
|
106
106
|
```typescript
|
|
107
107
|
const source = reactive([1, 2, 3])
|
|
108
|
-
const doubled =
|
|
108
|
+
const doubled = project(source, ({ value }) => value * 2)
|
|
109
109
|
// [2, 4, 6]
|
|
110
110
|
|
|
111
111
|
source.push(4) // doubled automatically becomes [2, 4, 6, 8]
|
|
@@ -446,13 +446,13 @@ state.c = 15 // Does NOT trigger effect
|
|
|
446
446
|
|
|
447
447
|
### Async Effects and the `access` Parameter
|
|
448
448
|
|
|
449
|
-
The `effect` function provides a special `access` parameter with `tracked` and `ascend` functions that restore the active effect context for dependency tracking in asynchronous operations.
|
|
449
|
+
The `effect` function provides a special `access` parameter with `tracked` and `ascend` functions that restore the active effect context for dependency tracking in asynchronous operations.
|
|
450
450
|
|
|
451
|
-
|
|
451
|
+
In modern `mutts`, this is powered by the **Zone system**. When you use `configureAsyncZone()`, the active effect context is automatically preserved across `await` points and timers, making manual use of `tracked` optional for these cases.
|
|
452
452
|
|
|
453
|
-
|
|
453
|
+
#### The Problem with Async Effects
|
|
454
454
|
|
|
455
|
-
|
|
455
|
+
Traditionally, in JavaScript, async functions lose their context when they yield control.
|
|
456
456
|
|
|
457
457
|
#### Understanding the active effect context
|
|
458
458
|
|
|
@@ -463,37 +463,42 @@ The reactive system uses a global active effect variable to track which effect i
|
|
|
463
463
|
effect(() => {
|
|
464
464
|
// active effect = this effect
|
|
465
465
|
const value = state.count // ✅ Tracked (active effect is set)
|
|
466
|
-
// active effect = this effect (still set)
|
|
467
466
|
const another = state.name // ✅ Tracked (active effect is still set)
|
|
468
467
|
})
|
|
469
468
|
|
|
470
|
-
// Async effect -
|
|
469
|
+
// Async effect WITHOUT configureAsyncZone() - context is lost after await
|
|
471
470
|
effect(async () => {
|
|
472
|
-
|
|
473
|
-
const value = state.count // ✅ Tracked (active effect is set)
|
|
471
|
+
const value = state.count // ✅ Tracked
|
|
474
472
|
|
|
475
|
-
await someAsyncOperation()
|
|
473
|
+
await someAsyncOperation()
|
|
476
474
|
|
|
477
|
-
//
|
|
478
|
-
const another = state.name // ❌ NOT tracked
|
|
475
|
+
// context is lost!
|
|
476
|
+
const another = state.name // ❌ NOT tracked
|
|
479
477
|
})
|
|
480
478
|
|
|
481
|
-
// Async effect
|
|
482
|
-
effect(async (
|
|
483
|
-
|
|
484
|
-
|
|
479
|
+
// Async effect WITH configureAsyncZone() - context is preserved
|
|
480
|
+
effect(async () => {
|
|
481
|
+
const value = state.count // ✅ Tracked
|
|
482
|
+
|
|
483
|
+
await someAsyncOperation()
|
|
485
484
|
|
|
486
|
-
|
|
485
|
+
// context is automatically restored!
|
|
486
|
+
const another = state.name // ✅ Tracked
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
// Using access.tracked() for manual restoration
|
|
490
|
+
effect(async ({ tracked }) => {
|
|
491
|
+
await someAsyncOperation()
|
|
487
492
|
|
|
488
|
-
//
|
|
489
|
-
const another = tracked(() => state.name) // ✅ Tracked
|
|
493
|
+
// Useful for non-patched APIs or explicit scoping
|
|
494
|
+
const another = tracked(() => state.name) // ✅ Tracked
|
|
490
495
|
})
|
|
491
496
|
```
|
|
492
497
|
|
|
493
|
-
#### Key Benefits of
|
|
498
|
+
#### Key Benefits of the Zone System
|
|
494
499
|
|
|
495
|
-
1.
|
|
496
|
-
2.
|
|
500
|
+
1. **Automatic Restoration**: With `configureAsyncZone()`, most native async APIs (Promises, timers) automatically preserve the reactive context.
|
|
501
|
+
2. **Manual Control**: `access.tracked()` allows you to manually "passport" the context into third-party libraries or unmanaged callbacks.
|
|
497
502
|
|
|
498
503
|
### Using `ascend` for Parent Effect Tracking
|
|
499
504
|
|
|
@@ -518,8 +523,6 @@ effect(({ ascend }) => {
|
|
|
518
523
|
}
|
|
519
524
|
})
|
|
520
525
|
```
|
|
521
|
-
<|tool▁calls▁begin|><|tool▁call▁begin|>
|
|
522
|
-
grep
|
|
523
526
|
|
|
524
527
|
**When to use `ascend`:**
|
|
525
528
|
- When creating child effects that should be cleaned up with their parent
|
|
@@ -25,7 +25,7 @@ These hooks are called during the execution of effects and computed values.
|
|
|
25
25
|
|
|
26
26
|
- **`beginChain(targets: Function[]) / endChain()`**: Called when a batch of effects starts and ends its execution.
|
|
27
27
|
- **`maxEffectChain`**: (Default: `100`) Limits the depth of synchronous effect triggering to prevent stack overflows.
|
|
28
|
-
- **`maxTriggerPerBatch`**: (Default: `10`) Limits how many times a single effect can be triggered within the same batch. Useful for detecting aggressive re-computation.
|
|
28
|
+
- **`maxTriggerPerBatch`**: (Default: `10`) Limits how many times a single effect can be triggered within the same batch. Useful for detecting aggressive re-computation or infinite cycles in `cycleHandling: 'none'` mode.
|
|
29
29
|
|
|
30
30
|
## Cycle Detection
|
|
31
31
|
|
|
@@ -35,11 +35,21 @@ These hooks are called during the execution of effects and computed values.
|
|
|
35
35
|
|
|
36
36
|
You can control how cycles are handled via `reactiveOptions.cycleHandling`:
|
|
37
37
|
|
|
38
|
-
- **`'
|
|
38
|
+
- **`'none'`** (Default): High-performance FIFO mode. Disables the dependency graph and topological sorting.
|
|
39
|
+
- **`'throw'`**: Throws a `ReactiveError` with a detailed path.
|
|
39
40
|
- **`'warn'`**: Logs a warning but breaks the cycle to allow the application to continue.
|
|
40
41
|
- **`'break'`**: Silently breaks the cycle.
|
|
41
42
|
- **`'strict'`**: Performs a graph check *before* execution to prevent cycles from even starting. This has the highest overhead.
|
|
42
43
|
|
|
44
|
+
### Topological vs. Flat Mode Detection
|
|
45
|
+
|
|
46
|
+
| Mode | `cycleHandling` | Detection Method | Error Code |
|
|
47
|
+
| :--- | :--- | :--- | :--- |
|
|
48
|
+
| **Topological** | `'throw'` (or other) | **Mathematical**: Analyzes the dependency graph. | `CYCLE_DETECTED` |
|
|
49
|
+
| **Flat Mode** | `'none'` (Default) | **Heuristic**: Counts executions per batch. | `MAX_REACTION_EXCEEDED` |
|
|
50
|
+
|
|
51
|
+
In **Topological mode**, the system maintains a transitive closure of all effects, allowing it to know instantly if an effect is its own cause. In **Flat mode**, the system is "blind" to the graph and relies on the execution threshold (`maxTriggerPerBatch`) to interrupt infinite loops.
|
|
52
|
+
|
|
43
53
|
## Memoization Discrepancy Detection
|
|
44
54
|
|
|
45
55
|
The most powerful debugging tool in `mutts` is the **Discrepancy Detector**. It helps identify "missing dependencies"—reactive values used inside a computation that the system isn't tracking.
|
package/docs/reactive/project.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
- `memoize` caches results but invalidates on `Map.set`, so large effects still re-run.
|
|
14
14
|
- `organized` (designed for `Record` sources) creates per-key effects so downstream work reruns only for the touched key; this matches the desired behaviour.
|
|
15
|
-
- **Gap:** `organized` operates on plain objects: key enumeration relies on property iteration and `
|
|
15
|
+
- **Gap:** `organized` operates on plain objects: key enumeration relies on property iteration and `FoolProof.get/set`. Registers and other keyed collections (`Map`, `Register`, custom stores) need the same per-entry orchestration without converting to records.
|
|
16
16
|
|
|
17
17
|
### Completed Evolution: `project` Implementation
|
|
18
18
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Reactive Scan
|
|
2
|
+
|
|
3
|
+
The `scan` function perform a reactive accumulation over an array of items. Unlike a standard `Array.reduce`, it is designed to be highly efficient in a reactive system, particularly when items are moved or changed, by returning a reactive array of all intermediate results.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
In a typical reactive system, calling `array.reduce(...)` inside an `effect` means the entire reduction re-runs every time the array structure or a single item changes.
|
|
8
|
+
|
|
9
|
+
Reactive `scan` solves this by maintaining a chain of **reactive intermediates**. Each item in the source array is linked to an intermediate that depends on the *previous* intermediate's result.
|
|
10
|
+
|
|
11
|
+
## Key Features
|
|
12
|
+
|
|
13
|
+
- **Fine-Grained Reactivity**: Changing a property on an item only re-computes the accumulated value for that item and its successors.
|
|
14
|
+
- **Move Optimization**: If a subsequence of items moves together (e.g., sorting or splicing), their intermediates are reused. As long as an item's predecessor in the array hasn't changed, its accumulated value is hit from the cache.
|
|
15
|
+
- **Duplicate Support**: Correctly handles multiple occurrences of the same object instance.
|
|
16
|
+
- **Memory Safety**: Uses `WeakMap` for intermediate storage, ensuring data is cleared when source items are garbage collected.
|
|
17
|
+
- **Granular Sync**: Uses per-index effects to sync results, preventing broad dependency tracking of the source array in every calculation.
|
|
18
|
+
|
|
19
|
+
## Basic Usage
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { reactive, scan } from 'mutts/reactive'
|
|
23
|
+
|
|
24
|
+
const source = reactive([
|
|
25
|
+
{ id: 'A', val: 1 },
|
|
26
|
+
{ id: 'B', val: 2 },
|
|
27
|
+
{ id: 'C', val: 3 },
|
|
28
|
+
])
|
|
29
|
+
|
|
30
|
+
// result is a reactive array: [1, 3, 6]
|
|
31
|
+
const result = scan(source, (acc, item) => acc + item.val, 0)
|
|
32
|
+
|
|
33
|
+
// Updating an item only re-computes for that position and successors
|
|
34
|
+
source[1].val = 10
|
|
35
|
+
// result stays [1, 11, 14]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## How it Works
|
|
39
|
+
|
|
40
|
+
The implementation consists of:
|
|
41
|
+
1. **A Main Effect**: Tracks the structure of the source array (length and item identities). It manages a list of `Intermediate` objects and stays updated on their `prev` links.
|
|
42
|
+
2. **Intermediates**: Class instances that link `val` and `prev`. They expose an `acc` getter decorated with `@memoize`.
|
|
43
|
+
3. **Index Sync Effects**: Granular effects (one per result index) that subscribe to `indexToIntermediate[i].acc`.
|
|
44
|
+
|
|
45
|
+
This "Project-like" architecture ensures that the main loop only does structural work, while the actual logic propagation is handled by the dependency chain of the intermediates.
|
|
46
|
+
|
|
47
|
+
## API Reference
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
function scan<Input extends object, Output>(
|
|
51
|
+
source: readonly Input[],
|
|
52
|
+
callback: (acc: Output, val: Input) => Output,
|
|
53
|
+
initialValue: Output
|
|
54
|
+
): ScanResult<Output>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Parameters
|
|
58
|
+
- `source`: The source array. All items must be objects (WeakKeys) to enable intermediate caching.
|
|
59
|
+
- `callback`: The accumulator function `(acc, val) => nextAcc`.
|
|
60
|
+
- `initialValue`: The value used as the accumulator for the first item.
|
|
61
|
+
|
|
62
|
+
### Returns
|
|
63
|
+
A reactive array of accumulated values. It includes a `[cleanup]` symbol that should be called to stop the reactive tracking.
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { cleanup } from 'mutts/reactive'
|
|
67
|
+
// ...
|
|
68
|
+
result[cleanup]()
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Performance Comparison
|
|
72
|
+
|
|
73
|
+
| Operation | Standard `Array.reduce` in `effect` | Reactive `scan` |
|
|
74
|
+
| :--- | :--- | :--- |
|
|
75
|
+
| **Initial Run** | O(N) calls | O(N) calls |
|
|
76
|
+
| **Modify Item at `i`** | O(N) calls (entire reduction) | O(N-i) calls |
|
|
77
|
+
| **Append Item** | O(N+1) calls | 1 call |
|
|
78
|
+
| **Move Item** | O(N) calls | O(affected chain) |
|
package/docs/reactive.md
CHANGED
|
@@ -11,7 +11,8 @@ The Mutts Reactive System documentation has been split into focused sections for
|
|
|
11
11
|
* **[Reactive Collections](./reactive/collections.md#collections)**: Map, Set, WeakMap, WeakSet
|
|
12
12
|
* **[Reactive Arrays](./reactive/collections.md#reactivearray)**: Full array method support
|
|
13
13
|
* **[Register](./reactive/collections.md#register)**: ID-keyed ordered collections
|
|
14
|
-
* **[Projections](./reactive/collections.md#projection)**: `project`, `
|
|
14
|
+
* **[Projections](./reactive/collections.md#projection)**: `project`, `organized`
|
|
15
|
+
* **[Scan](./reactive/scan.md)**: Reactive scan and accumulation
|
|
15
16
|
|
|
16
17
|
## [Advanced Topics](./reactive/advanced.md)
|
|
17
18
|
* **[Atomic Operations](./reactive/advanced.md#atomic-operations)**: Batching and Bidirectional binding
|
package/docs/std-decorators.md
CHANGED