@pyreon/reactivity 0.18.0 → 0.20.0
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/lib/analysis/index.js.html +1 -1
- package/lib/index.js +319 -30
- package/lib/types/index.d.ts +127 -3
- package/package.json +1 -1
- package/src/computed.ts +57 -16
- package/src/effect.ts +15 -1
- package/src/index.ts +16 -0
- package/src/manifest.ts +68 -1
- package/src/reactive-devtools.ts +281 -0
- package/src/reactive-trace.ts +142 -0
- package/src/reconcile.ts +12 -0
- package/src/signal.ts +40 -21
- package/src/store.ts +11 -0
- package/src/tests/computed.test.ts +31 -0
- package/src/tests/coverage-hardening.test.ts +471 -0
- package/src/tests/manifest-snapshot.test.ts +5 -3
- package/src/tests/reactive-devtools-treeshake.test.ts +48 -0
- package/src/tests/reactive-devtools.test.ts +296 -0
- package/src/tests/reactive-trace.test.ts +102 -0
- package/src/tests/reconcile-security.test.ts +45 -0
- package/src/tests/signal.test.ts +35 -9
|
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
|
|
|
5386
5386
|
</script>
|
|
5387
5387
|
<script>
|
|
5388
5388
|
/*<!--*/
|
|
5389
|
-
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"c60e3531-1","name":"batch.ts"},{"uid":"c60e3531-3","name":"cell.ts"},{"uid":"c60e3531-5","name":"reactive-devtools.ts"},{"uid":"c60e3531-7","name":"scope.ts"},{"uid":"c60e3531-9","name":"tracking.ts"},{"uid":"c60e3531-11","name":"effect.ts"},{"uid":"c60e3531-13","name":"computed.ts"},{"uid":"c60e3531-15","name":"createSelector.ts"},{"uid":"c60e3531-17","name":"debug.ts"},{"uid":"c60e3531-19","name":"reactive-trace.ts"},{"uid":"c60e3531-21","name":"signal.ts"},{"uid":"c60e3531-23","name":"store.ts"},{"uid":"c60e3531-25","name":"reconcile.ts"},{"uid":"c60e3531-27","name":"resource.ts"},{"uid":"c60e3531-29","name":"watch.ts"},{"uid":"c60e3531-31","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"c60e3531-1":{"renderedLength":3016,"gzipLength":1167,"brotliLength":0,"metaUid":"c60e3531-0"},"c60e3531-3":{"renderedLength":1636,"gzipLength":786,"brotliLength":0,"metaUid":"c60e3531-2"},"c60e3531-5":{"renderedLength":4438,"gzipLength":1940,"brotliLength":0,"metaUid":"c60e3531-4"},"c60e3531-7":{"renderedLength":3026,"gzipLength":1226,"brotliLength":0,"metaUid":"c60e3531-6"},"c60e3531-9":{"renderedLength":2227,"gzipLength":858,"brotliLength":0,"metaUid":"c60e3531-8"},"c60e3531-11":{"renderedLength":7605,"gzipLength":2433,"brotliLength":0,"metaUid":"c60e3531-10"},"c60e3531-13":{"renderedLength":4983,"gzipLength":1524,"brotliLength":0,"metaUid":"c60e3531-12"},"c60e3531-15":{"renderedLength":2244,"gzipLength":981,"brotliLength":0,"metaUid":"c60e3531-14"},"c60e3531-17":{"renderedLength":2469,"gzipLength":1092,"brotliLength":0,"metaUid":"c60e3531-16"},"c60e3531-19":{"renderedLength":2721,"gzipLength":1363,"brotliLength":0,"metaUid":"c60e3531-18"},"c60e3531-21":{"renderedLength":3535,"gzipLength":1513,"brotliLength":0,"metaUid":"c60e3531-20"},"c60e3531-23":{"renderedLength":5232,"gzipLength":1867,"brotliLength":0,"metaUid":"c60e3531-22"},"c60e3531-25":{"renderedLength":2278,"gzipLength":940,"brotliLength":0,"metaUid":"c60e3531-24"},"c60e3531-27":{"renderedLength":1205,"gzipLength":524,"brotliLength":0,"metaUid":"c60e3531-26"},"c60e3531-29":{"renderedLength":1249,"gzipLength":582,"brotliLength":0,"metaUid":"c60e3531-28"},"c60e3531-31":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"c60e3531-30"}},"nodeMetas":{"c60e3531-0":{"id":"/src/batch.ts","moduleParts":{"index.js":"c60e3531-1"},"imported":[],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-12"},{"uid":"c60e3531-20"},{"uid":"c60e3531-8"}]},"c60e3531-2":{"id":"/src/cell.ts","moduleParts":{"index.js":"c60e3531-3"},"imported":[],"importedBy":[{"uid":"c60e3531-30"}]},"c60e3531-4":{"id":"/src/reactive-devtools.ts","moduleParts":{"index.js":"c60e3531-5"},"imported":[],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-12"},{"uid":"c60e3531-10"},{"uid":"c60e3531-20"}]},"c60e3531-6":{"id":"/src/scope.ts","moduleParts":{"index.js":"c60e3531-7"},"imported":[],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-12"},{"uid":"c60e3531-10"}]},"c60e3531-8":{"id":"/src/tracking.ts","moduleParts":{"index.js":"c60e3531-9"},"imported":[{"uid":"c60e3531-0"}],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-12"},{"uid":"c60e3531-14"},{"uid":"c60e3531-10"},{"uid":"c60e3531-26"},{"uid":"c60e3531-20"}]},"c60e3531-10":{"id":"/src/effect.ts","moduleParts":{"index.js":"c60e3531-11"},"imported":[{"uid":"c60e3531-4"},{"uid":"c60e3531-6"},{"uid":"c60e3531-8"}],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-12"},{"uid":"c60e3531-14"},{"uid":"c60e3531-26"},{"uid":"c60e3531-28"}]},"c60e3531-12":{"id":"/src/computed.ts","moduleParts":{"index.js":"c60e3531-13"},"imported":[{"uid":"c60e3531-0"},{"uid":"c60e3531-10"},{"uid":"c60e3531-4"},{"uid":"c60e3531-6"},{"uid":"c60e3531-8"}],"importedBy":[{"uid":"c60e3531-30"}]},"c60e3531-14":{"id":"/src/createSelector.ts","moduleParts":{"index.js":"c60e3531-15"},"imported":[{"uid":"c60e3531-10"},{"uid":"c60e3531-8"}],"importedBy":[{"uid":"c60e3531-30"}]},"c60e3531-16":{"id":"/src/debug.ts","moduleParts":{"index.js":"c60e3531-17"},"imported":[],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-20"}]},"c60e3531-18":{"id":"/src/reactive-trace.ts","moduleParts":{"index.js":"c60e3531-19"},"imported":[],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-20"}]},"c60e3531-20":{"id":"/src/signal.ts","moduleParts":{"index.js":"c60e3531-21"},"imported":[{"uid":"c60e3531-0"},{"uid":"c60e3531-16"},{"uid":"c60e3531-4"},{"uid":"c60e3531-18"},{"uid":"c60e3531-8"}],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-26"},{"uid":"c60e3531-22"}]},"c60e3531-22":{"id":"/src/store.ts","moduleParts":{"index.js":"c60e3531-23"},"imported":[{"uid":"c60e3531-20"}],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-24"}]},"c60e3531-24":{"id":"/src/reconcile.ts","moduleParts":{"index.js":"c60e3531-25"},"imported":[{"uid":"c60e3531-22"}],"importedBy":[{"uid":"c60e3531-30"}]},"c60e3531-26":{"id":"/src/resource.ts","moduleParts":{"index.js":"c60e3531-27"},"imported":[{"uid":"c60e3531-10"},{"uid":"c60e3531-20"},{"uid":"c60e3531-8"}],"importedBy":[{"uid":"c60e3531-30"}]},"c60e3531-28":{"id":"/src/watch.ts","moduleParts":{"index.js":"c60e3531-29"},"imported":[{"uid":"c60e3531-10"}],"importedBy":[{"uid":"c60e3531-30"}]},"c60e3531-30":{"id":"/src/index.ts","moduleParts":{"index.js":"c60e3531-31"},"imported":[{"uid":"c60e3531-0"},{"uid":"c60e3531-2"},{"uid":"c60e3531-12"},{"uid":"c60e3531-14"},{"uid":"c60e3531-16"},{"uid":"c60e3531-4"},{"uid":"c60e3531-18"},{"uid":"c60e3531-10"},{"uid":"c60e3531-24"},{"uid":"c60e3531-26"},{"uid":"c60e3531-6"},{"uid":"c60e3531-20"},{"uid":"c60e3531-22"},{"uid":"c60e3531-8"},{"uid":"c60e3531-28"}],"importedBy":[],"isEntry":true}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
|
|
5390
5390
|
|
|
5391
5391
|
const run = () => {
|
|
5392
5392
|
const width = window.innerWidth;
|
package/lib/index.js
CHANGED
|
@@ -149,6 +149,169 @@ function cell(value) {
|
|
|
149
149
|
return new Cell(value);
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region src/reactive-devtools.ts
|
|
154
|
+
let _active = false;
|
|
155
|
+
let _nextId = 1;
|
|
156
|
+
const _byId = /* @__PURE__ */ new Map();
|
|
157
|
+
const _subId = /* @__PURE__ */ new WeakMap();
|
|
158
|
+
/** @internal — finalizer callback; prunes the record when a node is GC'd. */
|
|
159
|
+
function _rdPrune(id) {
|
|
160
|
+
_byId.delete(id);
|
|
161
|
+
}
|
|
162
|
+
const _finalizer = new FinalizationRegistry(_rdPrune);
|
|
163
|
+
const FIRE_CAP = 512;
|
|
164
|
+
let _fireBuf = null;
|
|
165
|
+
let _fireCount = 0;
|
|
166
|
+
const PREVIEW_MAX$1 = 60;
|
|
167
|
+
function preview$1(v) {
|
|
168
|
+
let s;
|
|
169
|
+
try {
|
|
170
|
+
if (v === null) return "null";
|
|
171
|
+
if (v === void 0) return "undefined";
|
|
172
|
+
const t = typeof v;
|
|
173
|
+
if (t === "string") s = JSON.stringify(v);
|
|
174
|
+
else if (t === "number" || t === "boolean" || t === "bigint") s = String(v);
|
|
175
|
+
else if (t === "function") s = `[Function ${v.name || "anonymous"}]`;
|
|
176
|
+
else if (t === "symbol") s = v.toString();
|
|
177
|
+
else if (Array.isArray(v)) s = `Array(${v.length})`;
|
|
178
|
+
else {
|
|
179
|
+
const ctor = v.constructor?.name;
|
|
180
|
+
let keys = [];
|
|
181
|
+
try {
|
|
182
|
+
keys = Object.keys(v).slice(0, 3);
|
|
183
|
+
} catch {
|
|
184
|
+
keys = [];
|
|
185
|
+
}
|
|
186
|
+
s = `${ctor && ctor !== "Object" ? `${ctor} ` : ""}{${keys.join(", ")}${keys.length === 3 ? ", …" : ""}}`;
|
|
187
|
+
}
|
|
188
|
+
} catch {
|
|
189
|
+
s = "[unstringifiable]";
|
|
190
|
+
}
|
|
191
|
+
return s.length > PREVIEW_MAX$1 ? `${s.slice(0, PREVIEW_MAX$1)}…` : s;
|
|
192
|
+
}
|
|
193
|
+
/** Activate the bridge. Idempotent. Called when a devtools client attaches. */
|
|
194
|
+
function activateReactiveDevtools() {
|
|
195
|
+
_active = true;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Deactivate + drop all retained state. Called when the devtools client
|
|
199
|
+
* disconnects so a closed panel leaves zero residue.
|
|
200
|
+
*/
|
|
201
|
+
function deactivateReactiveDevtools() {
|
|
202
|
+
_active = false;
|
|
203
|
+
_byId.clear();
|
|
204
|
+
_fireBuf = null;
|
|
205
|
+
_fireCount = 0;
|
|
206
|
+
}
|
|
207
|
+
function isReactiveDevtoolsActive() {
|
|
208
|
+
return _active;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Register a signal/computed/effect node. `host` is the object carrying
|
|
212
|
+
* the `_s` subscriber Set (the signal read fn itself, or a computed's
|
|
213
|
+
* internal host). `sub` is the notify closure (`recompute`/`run`) whose
|
|
214
|
+
* identity appears in upstream `_s` Sets — used to resolve edges.
|
|
215
|
+
*
|
|
216
|
+
* @internal
|
|
217
|
+
*/
|
|
218
|
+
function _rdRegister(node, kind, host, sub, label) {
|
|
219
|
+
if (!_active) return void 0;
|
|
220
|
+
const id = _nextId++;
|
|
221
|
+
_byId.set(id, {
|
|
222
|
+
id,
|
|
223
|
+
kind,
|
|
224
|
+
name: label ?? `${kind === "signal" ? "signal" : kind}#${id}`,
|
|
225
|
+
ref: new WeakRef(node),
|
|
226
|
+
hostRef: host ? new WeakRef(host) : null,
|
|
227
|
+
fires: 0,
|
|
228
|
+
lastFire: null
|
|
229
|
+
});
|
|
230
|
+
if (sub) _subId.set(sub, id);
|
|
231
|
+
_finalizer.register(node, id);
|
|
232
|
+
Object.defineProperty(node, "__pxRdId", {
|
|
233
|
+
value: id,
|
|
234
|
+
enumerable: false,
|
|
235
|
+
configurable: true
|
|
236
|
+
});
|
|
237
|
+
return id;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Record that a node fired (signal write / computed recompute / effect
|
|
241
|
+
* run). Bumps counters + appends to the bounded fire buffer.
|
|
242
|
+
*
|
|
243
|
+
* @internal
|
|
244
|
+
*/
|
|
245
|
+
function _rdRecordFire(node) {
|
|
246
|
+
if (!_active) return;
|
|
247
|
+
const id = node.__pxRdId;
|
|
248
|
+
if (id === void 0) return;
|
|
249
|
+
const rec = _byId.get(id);
|
|
250
|
+
const ts = typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
251
|
+
if (rec) {
|
|
252
|
+
rec.fires++;
|
|
253
|
+
rec.lastFire = ts;
|
|
254
|
+
}
|
|
255
|
+
if (_fireBuf === null) _fireBuf = new Array(FIRE_CAP);
|
|
256
|
+
_fireBuf[_fireCount % FIRE_CAP] = {
|
|
257
|
+
id,
|
|
258
|
+
ts
|
|
259
|
+
};
|
|
260
|
+
_fireCount++;
|
|
261
|
+
}
|
|
262
|
+
function resolveSubId(sub) {
|
|
263
|
+
const direct = sub.__pxRdId;
|
|
264
|
+
if (direct !== void 0) return direct;
|
|
265
|
+
return _subId.get(sub);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Fresh snapshot of the live reactive graph. Edges are recomputed from
|
|
269
|
+
* each live node's current subscriber Set — always consistent with the
|
|
270
|
+
* framework's real subscription state, no incremental drift.
|
|
271
|
+
*/
|
|
272
|
+
function getReactiveGraph() {
|
|
273
|
+
const nodes = [];
|
|
274
|
+
const edges = [];
|
|
275
|
+
for (const rec of _byId.values()) {
|
|
276
|
+
const node = rec.ref.deref();
|
|
277
|
+
if (!node) continue;
|
|
278
|
+
const subs = (rec.hostRef?.deref() ?? null)?._s ?? null;
|
|
279
|
+
const valueStr = rec.kind === "effect" ? "" : preview$1(node._v);
|
|
280
|
+
nodes.push({
|
|
281
|
+
id: rec.id,
|
|
282
|
+
kind: rec.kind,
|
|
283
|
+
name: rec.name,
|
|
284
|
+
value: valueStr,
|
|
285
|
+
subscribers: subs?.size ?? 0,
|
|
286
|
+
fires: rec.fires,
|
|
287
|
+
lastFire: rec.lastFire
|
|
288
|
+
});
|
|
289
|
+
if (subs) for (const cb of subs) {
|
|
290
|
+
const to = resolveSubId(cb);
|
|
291
|
+
if (to !== void 0) edges.push({
|
|
292
|
+
from: rec.id,
|
|
293
|
+
to
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
nodes,
|
|
299
|
+
edges
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
/** Bounded recent-fire timeline (oldest → newest). Fresh copy. */
|
|
303
|
+
function getReactiveFires() {
|
|
304
|
+
if (_fireBuf === null || _fireCount === 0) return [];
|
|
305
|
+
if (_fireCount <= FIRE_CAP) return _fireBuf.slice(0, _fireCount);
|
|
306
|
+
const start = _fireCount % FIRE_CAP;
|
|
307
|
+
const out = [];
|
|
308
|
+
for (let i = 0; i < FIRE_CAP; i++) {
|
|
309
|
+
const e = _fireBuf[(start + i) % FIRE_CAP];
|
|
310
|
+
if (e) out.push(e);
|
|
311
|
+
}
|
|
312
|
+
return out;
|
|
313
|
+
}
|
|
314
|
+
|
|
152
315
|
//#endregion
|
|
153
316
|
//#region src/scope.ts
|
|
154
317
|
var EffectScope = class {
|
|
@@ -437,7 +600,10 @@ function effect(fn) {
|
|
|
437
600
|
};
|
|
438
601
|
const run = () => {
|
|
439
602
|
if (disposed) return;
|
|
440
|
-
if (process.env.NODE_ENV !== "production")
|
|
603
|
+
if (process.env.NODE_ENV !== "production") {
|
|
604
|
+
_countSink$2.__pyreon_count__?.("reactivity.effectRun");
|
|
605
|
+
_rdRecordFire(run);
|
|
606
|
+
}
|
|
441
607
|
runCleanup();
|
|
442
608
|
const outerCollector = _innerEffectCollector;
|
|
443
609
|
const myInners = [];
|
|
@@ -462,6 +628,7 @@ function effect(fn) {
|
|
|
462
628
|
if (!isFirstRun) scope?.notifyEffectRan();
|
|
463
629
|
isFirstRun = false;
|
|
464
630
|
};
|
|
631
|
+
if (process.env.NODE_ENV !== "production") _rdRegister(run, "effect", null, run, void 0);
|
|
465
632
|
run();
|
|
466
633
|
const e = { dispose() {
|
|
467
634
|
runCleanup();
|
|
@@ -560,6 +727,7 @@ function renderEffect(fn) {
|
|
|
560
727
|
}
|
|
561
728
|
} else renderEffectFullTrack(deps, run, trackedFn);
|
|
562
729
|
};
|
|
730
|
+
if (process.env.NODE_ENV !== "production") _rdRegister(run, "effect", null, run, void 0);
|
|
563
731
|
run();
|
|
564
732
|
const dispose = () => {
|
|
565
733
|
if (disposed) return;
|
|
@@ -615,13 +783,16 @@ function computedLazy(fn) {
|
|
|
615
783
|
if (disposed || dirty) return;
|
|
616
784
|
dirty = true;
|
|
617
785
|
if (host._s) notifySubscribers(host._s);
|
|
618
|
-
if (directFns) for (const f of directFns) f
|
|
786
|
+
if (directFns) for (const f of directFns) f();
|
|
619
787
|
};
|
|
620
788
|
_markRecompute(recompute);
|
|
621
789
|
const read = () => {
|
|
622
790
|
trackSubscriber(host);
|
|
623
791
|
if (dirty) {
|
|
624
|
-
if (process.env.NODE_ENV !== "production")
|
|
792
|
+
if (process.env.NODE_ENV !== "production") {
|
|
793
|
+
_countSink$1.__pyreon_count__?.("reactivity.computedRecompute");
|
|
794
|
+
_rdRecordFire(read);
|
|
795
|
+
}
|
|
625
796
|
try {
|
|
626
797
|
if (tracked) {
|
|
627
798
|
setSkipDepsCollection(true);
|
|
@@ -649,15 +820,19 @@ function computedLazy(fn) {
|
|
|
649
820
|
},
|
|
650
821
|
enumerable: false
|
|
651
822
|
});
|
|
823
|
+
Object.defineProperty(read, "_d", {
|
|
824
|
+
get: () => directFns,
|
|
825
|
+
enumerable: false
|
|
826
|
+
});
|
|
652
827
|
read.direct = (updater) => {
|
|
653
|
-
if (!directFns) directFns =
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
arr.push(updater);
|
|
828
|
+
if (!directFns) directFns = /* @__PURE__ */ new Set();
|
|
829
|
+
const set = directFns;
|
|
830
|
+
set.add(updater);
|
|
657
831
|
return () => {
|
|
658
|
-
|
|
832
|
+
set.delete(updater);
|
|
659
833
|
};
|
|
660
834
|
};
|
|
835
|
+
if (process.env.NODE_ENV !== "production") _rdRegister(read, "derived", host, recompute, void 0);
|
|
661
836
|
getCurrentScope()?.add({ dispose: read.dispose });
|
|
662
837
|
return read;
|
|
663
838
|
}
|
|
@@ -677,7 +852,10 @@ function computedWithEquals(fn, equals) {
|
|
|
677
852
|
let directFns = null;
|
|
678
853
|
const recompute = () => {
|
|
679
854
|
if (disposed) return;
|
|
680
|
-
if (process.env.NODE_ENV !== "production")
|
|
855
|
+
if (process.env.NODE_ENV !== "production") {
|
|
856
|
+
_countSink$1.__pyreon_count__?.("reactivity.computedRecompute");
|
|
857
|
+
_rdRecordFire(read);
|
|
858
|
+
}
|
|
681
859
|
cleanupLocalDeps(deps, recompute);
|
|
682
860
|
try {
|
|
683
861
|
const next = trackWithLocalDeps(deps, recompute, fn);
|
|
@@ -690,7 +868,7 @@ function computedWithEquals(fn, equals) {
|
|
|
690
868
|
return;
|
|
691
869
|
}
|
|
692
870
|
if (host._s) notifySubscribers(host._s);
|
|
693
|
-
if (directFns) for (const f of directFns) f
|
|
871
|
+
if (directFns) for (const f of directFns) f();
|
|
694
872
|
};
|
|
695
873
|
_markRecompute(recompute);
|
|
696
874
|
const read = () => {
|
|
@@ -720,15 +898,19 @@ function computedWithEquals(fn, equals) {
|
|
|
720
898
|
},
|
|
721
899
|
enumerable: false
|
|
722
900
|
});
|
|
901
|
+
Object.defineProperty(read, "_d", {
|
|
902
|
+
get: () => directFns,
|
|
903
|
+
enumerable: false
|
|
904
|
+
});
|
|
723
905
|
read.direct = (updater) => {
|
|
724
|
-
if (!directFns) directFns =
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
arr.push(updater);
|
|
906
|
+
if (!directFns) directFns = /* @__PURE__ */ new Set();
|
|
907
|
+
const set = directFns;
|
|
908
|
+
set.add(updater);
|
|
728
909
|
return () => {
|
|
729
|
-
|
|
910
|
+
set.delete(updater);
|
|
730
911
|
};
|
|
731
912
|
};
|
|
913
|
+
if (process.env.NODE_ENV !== "production") _rdRegister(read, "derived", host, recompute, void 0);
|
|
732
914
|
getCurrentScope()?.add({ dispose: read.dispose });
|
|
733
915
|
return read;
|
|
734
916
|
}
|
|
@@ -909,6 +1091,93 @@ function inspectSignal(sig) {
|
|
|
909
1091
|
return info;
|
|
910
1092
|
}
|
|
911
1093
|
|
|
1094
|
+
//#endregion
|
|
1095
|
+
//#region src/reactive-trace.ts
|
|
1096
|
+
/**
|
|
1097
|
+
* Ring-buffer capacity. 50 entries is enough to see the causal chain
|
|
1098
|
+
* for a crash (the writes in the few ticks before the throw) without
|
|
1099
|
+
* the buffer itself becoming a memory concern — each entry is a small
|
|
1100
|
+
* object with two short strings.
|
|
1101
|
+
*/
|
|
1102
|
+
const CAP = 50;
|
|
1103
|
+
let _buf = null;
|
|
1104
|
+
let _count = 0;
|
|
1105
|
+
/** Max characters of a value preview before truncation. Keeps the buffer + serialized report small. */
|
|
1106
|
+
const PREVIEW_MAX = 80;
|
|
1107
|
+
/**
|
|
1108
|
+
* Safe, bounded stringification. Never throws (a getter or `toJSON`
|
|
1109
|
+
* that throws must not break the trace recorder), never returns more
|
|
1110
|
+
* than `PREVIEW_MAX` chars + an ellipsis marker.
|
|
1111
|
+
*/
|
|
1112
|
+
function preview(v) {
|
|
1113
|
+
let s;
|
|
1114
|
+
try {
|
|
1115
|
+
if (v === null) return "null";
|
|
1116
|
+
if (v === void 0) return "undefined";
|
|
1117
|
+
const t = typeof v;
|
|
1118
|
+
if (t === "string") s = JSON.stringify(v);
|
|
1119
|
+
else if (t === "number" || t === "boolean" || t === "bigint") s = String(v);
|
|
1120
|
+
else if (t === "function") s = `[Function ${v.name || "anonymous"}]`;
|
|
1121
|
+
else if (t === "symbol") s = v.toString();
|
|
1122
|
+
else if (Array.isArray(v)) s = `Array(${v.length})`;
|
|
1123
|
+
else {
|
|
1124
|
+
const ctor = v.constructor?.name;
|
|
1125
|
+
const keys = (() => {
|
|
1126
|
+
try {
|
|
1127
|
+
return Object.keys(v).slice(0, 4);
|
|
1128
|
+
} catch {
|
|
1129
|
+
return [];
|
|
1130
|
+
}
|
|
1131
|
+
})();
|
|
1132
|
+
s = `${ctor && ctor !== "Object" ? ctor + " " : ""}{${keys.join(", ")}${keys.length === 4 ? ", …" : ""}}`;
|
|
1133
|
+
}
|
|
1134
|
+
} catch {
|
|
1135
|
+
s = "[unstringifiable]";
|
|
1136
|
+
}
|
|
1137
|
+
return s.length > PREVIEW_MAX ? s.slice(0, PREVIEW_MAX) + "…" : s;
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Record one signal write. Called from `signal.ts` `_set`, already
|
|
1141
|
+
* inside the prod-gate, so this never runs in production builds.
|
|
1142
|
+
*
|
|
1143
|
+
* @internal
|
|
1144
|
+
*/
|
|
1145
|
+
function _recordSignalWrite(name, prev, next) {
|
|
1146
|
+
if (_buf === null) _buf = new Array(CAP);
|
|
1147
|
+
_buf[_count % CAP] = {
|
|
1148
|
+
name,
|
|
1149
|
+
prev: preview(prev),
|
|
1150
|
+
next: preview(next),
|
|
1151
|
+
timestamp: typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now()
|
|
1152
|
+
};
|
|
1153
|
+
_count++;
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Returns the recorded writes in chronological order (oldest → newest),
|
|
1157
|
+
* at most `CAP` entries. Empty array when nothing has been recorded.
|
|
1158
|
+
* The returned array is a fresh copy — safe to retain / serialize
|
|
1159
|
+
* without pinning the ring buffer.
|
|
1160
|
+
*
|
|
1161
|
+
* Consumed by `@pyreon/core`'s `reportError` to attach `reactiveTrace`
|
|
1162
|
+
* to the error context.
|
|
1163
|
+
*/
|
|
1164
|
+
function getReactiveTrace() {
|
|
1165
|
+
if (_buf === null || _count === 0) return [];
|
|
1166
|
+
if (_count <= CAP) return _buf.slice(0, _count);
|
|
1167
|
+
const start = _count % CAP;
|
|
1168
|
+
const out = [];
|
|
1169
|
+
for (let i = 0; i < CAP; i++) {
|
|
1170
|
+
const e = _buf[(start + i) % CAP];
|
|
1171
|
+
if (e) out.push(e);
|
|
1172
|
+
}
|
|
1173
|
+
return out;
|
|
1174
|
+
}
|
|
1175
|
+
/** Clears the buffer. For test isolation; not part of the app-facing API. */
|
|
1176
|
+
function clearReactiveTrace() {
|
|
1177
|
+
_buf = null;
|
|
1178
|
+
_count = 0;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
912
1181
|
//#endregion
|
|
913
1182
|
//#region src/signal.ts
|
|
914
1183
|
const _countSink = globalThis;
|
|
@@ -920,6 +1189,10 @@ function _set(newValue) {
|
|
|
920
1189
|
if (process.env.NODE_ENV !== "production") _countSink.__pyreon_count__?.("reactivity.signalWrite");
|
|
921
1190
|
const prev = this._v;
|
|
922
1191
|
this._v = newValue;
|
|
1192
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1193
|
+
_recordSignalWrite(this.label, prev, newValue);
|
|
1194
|
+
_rdRecordFire(this);
|
|
1195
|
+
}
|
|
923
1196
|
if (isTracing()) try {
|
|
924
1197
|
_notifyTraceListeners(this, prev, newValue);
|
|
925
1198
|
} catch (err) {
|
|
@@ -943,28 +1216,33 @@ function _subscribe(listener) {
|
|
|
943
1216
|
}
|
|
944
1217
|
/**
|
|
945
1218
|
* Register a direct updater — lighter than subscribe().
|
|
946
|
-
* Uses a flat array instead of Set. Disposal nulls the slot (no Set.delete overhead).
|
|
947
1219
|
* Used by compiler-emitted _bindText/_bindDirect for zero-overhead DOM bindings.
|
|
1220
|
+
*
|
|
1221
|
+
* Backed by a `Set` (same as `_s`), NOT a flat array. The array form
|
|
1222
|
+
* disposed by nulling the slot (`arr[idx] = null`) but never compacted —
|
|
1223
|
+
* so a long-lived signal (theme/locale/auth, or a signal read inside
|
|
1224
|
+
* `<For>` rows) bound by churning components accumulated one permanent
|
|
1225
|
+
* dead slot per ever-mounted binding. That is an app-lifetime memory
|
|
1226
|
+
* leak AND degrades the signal-write hot path: `notifyDirect` iterated
|
|
1227
|
+
* O(total-ever-registered), not O(live). A Set bounds growth to the live
|
|
1228
|
+
* set and keeps disposal + iteration O(live); the "Set.delete overhead"
|
|
1229
|
+
* the array form optimised for is negligible against an unbounded array.
|
|
948
1230
|
*/
|
|
949
1231
|
function _directFn(updater) {
|
|
950
|
-
if (!this._d) this._d =
|
|
951
|
-
const
|
|
952
|
-
|
|
953
|
-
arr.push(updater);
|
|
1232
|
+
if (!this._d) this._d = /* @__PURE__ */ new Set();
|
|
1233
|
+
const set = this._d;
|
|
1234
|
+
set.add(updater);
|
|
954
1235
|
return () => {
|
|
955
|
-
|
|
1236
|
+
set.delete(updater);
|
|
956
1237
|
};
|
|
957
1238
|
}
|
|
958
1239
|
/**
|
|
959
|
-
* Notify direct updaters —
|
|
960
|
-
*
|
|
1240
|
+
* Notify direct updaters — set iteration, batch-aware. Disposed updaters
|
|
1241
|
+
* are already absent from the set (O(1) delete on disposal).
|
|
961
1242
|
*/
|
|
962
1243
|
function notifyDirect(updaters) {
|
|
963
|
-
if (isBatching()) for (
|
|
964
|
-
|
|
965
|
-
if (fn) enqueuePendingNotification(fn);
|
|
966
|
-
}
|
|
967
|
-
else for (let i = 0; i < updaters.length; i++) updaters[i]?.();
|
|
1244
|
+
if (isBatching()) for (const fn of updaters) enqueuePendingNotification(fn);
|
|
1245
|
+
else for (const fn of updaters) fn();
|
|
968
1246
|
}
|
|
969
1247
|
function _debug() {
|
|
970
1248
|
return {
|
|
@@ -997,6 +1275,7 @@ function signal(initialValue, options) {
|
|
|
997
1275
|
read.direct = _directFn;
|
|
998
1276
|
read.debug = _debug;
|
|
999
1277
|
read.label = options?.name;
|
|
1278
|
+
if (process.env.NODE_ENV !== "production") _rdRegister(read, "signal", read, null, read.label);
|
|
1000
1279
|
return read;
|
|
1001
1280
|
}
|
|
1002
1281
|
|
|
@@ -1111,6 +1390,7 @@ function wrap(raw, shallow) {
|
|
|
1111
1390
|
target[key] = value;
|
|
1112
1391
|
return true;
|
|
1113
1392
|
}
|
|
1393
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") return true;
|
|
1114
1394
|
const prevLength = isArray ? target.length : 0;
|
|
1115
1395
|
target[key] = value;
|
|
1116
1396
|
if (isArray && key === "length") {
|
|
@@ -1164,6 +1444,11 @@ function wrap(raw, shallow) {
|
|
|
1164
1444
|
* Arrays are reconciled by index — elements at the same index are recursively
|
|
1165
1445
|
* diffed rather than replaced wholesale. Excess old elements are removed.
|
|
1166
1446
|
*/
|
|
1447
|
+
const DANGEROUS_KEYS = new Set([
|
|
1448
|
+
"__proto__",
|
|
1449
|
+
"constructor",
|
|
1450
|
+
"prototype"
|
|
1451
|
+
]);
|
|
1167
1452
|
function reconcile(source, target) {
|
|
1168
1453
|
_reconcileInner(source, target, /* @__PURE__ */ new WeakSet());
|
|
1169
1454
|
}
|
|
@@ -1188,6 +1473,7 @@ function _reconcileObject(source, target, seen) {
|
|
|
1188
1473
|
const sourceKeys = Object.keys(source);
|
|
1189
1474
|
const targetKeys = new Set(Object.keys(target));
|
|
1190
1475
|
for (const key of sourceKeys) {
|
|
1476
|
+
if (DANGEROUS_KEYS.has(key)) continue;
|
|
1191
1477
|
const sv = source[key];
|
|
1192
1478
|
const tv = target[key];
|
|
1193
1479
|
if (sv !== null && typeof sv === "object" && tv !== null && typeof tv === "object") if (isStore(tv)) _reconcileInner(sv, tv, seen);
|
|
@@ -1195,7 +1481,10 @@ function _reconcileObject(source, target, seen) {
|
|
|
1195
1481
|
else target[key] = sv;
|
|
1196
1482
|
targetKeys.delete(key);
|
|
1197
1483
|
}
|
|
1198
|
-
for (const key of targetKeys)
|
|
1484
|
+
for (const key of targetKeys) {
|
|
1485
|
+
if (DANGEROUS_KEYS.has(key)) continue;
|
|
1486
|
+
delete target[key];
|
|
1487
|
+
}
|
|
1199
1488
|
}
|
|
1200
1489
|
|
|
1201
1490
|
//#endregion
|
|
@@ -1307,5 +1596,5 @@ function watch(source, callback, opts = {}) {
|
|
|
1307
1596
|
}
|
|
1308
1597
|
|
|
1309
1598
|
//#endregion
|
|
1310
|
-
export { Cell, EffectScope, _bind, batch, cell, computed, createResource, createSelector, createStore, effect, effectScope, getCurrentScope, inspectSignal, isStore, markRaw, nextTick, onCleanup, onScopeDispose, onSignalUpdate, reconcile, renderEffect, runUntracked, runUntracked as untrack, setCurrentScope, setErrorHandler, setSnapshotCapture, shallowReactive, signal, watch, why };
|
|
1599
|
+
export { Cell, EffectScope, _bind, activateReactiveDevtools, batch, cell, clearReactiveTrace, computed, createResource, createSelector, createStore, deactivateReactiveDevtools, effect, effectScope, getCurrentScope, getReactiveFires, getReactiveGraph, getReactiveTrace, inspectSignal, isReactiveDevtoolsActive, isStore, markRaw, nextTick, onCleanup, onScopeDispose, onSignalUpdate, reconcile, renderEffect, runUntracked, runUntracked as untrack, setCurrentScope, setErrorHandler, setSnapshotCapture, shallowReactive, signal, watch, why };
|
|
1311
1600
|
//# sourceMappingURL=index.js.map
|