react-fine-grained-signals 0.1.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.
@@ -0,0 +1,914 @@
1
+ import { a as untrackedRender, n as hasActiveRenderCollector } from "./render-tracking-DCzw57BW.js";
2
+ import { a as effect$1, c as signal$1, i as computed$1, l as untracked, n as SignalImpl, o as isSignal, r as batch, s as registerSignal, t as SIGNAL_BRAND } from "./base-BZTnpU8U.js";
3
+ import { getActiveSub, getBatchDepth } from "alien-signals";
4
+ import { useCallback, useEffect, useMemo, useRef, useSyncExternalStore } from "react";
5
+ //#region src/core/deep-signal.ts
6
+ const ARRAY_MUTATORS = /* @__PURE__ */ new Set([
7
+ "copyWithin",
8
+ "fill",
9
+ "pop",
10
+ "push",
11
+ "reverse",
12
+ "shift",
13
+ "sort",
14
+ "splice",
15
+ "unshift"
16
+ ]);
17
+ const proxyToRaw = /* @__PURE__ */ new WeakMap();
18
+ const rawToMetadata = /* @__PURE__ */ new WeakMap();
19
+ const readonlyMapViews = /* @__PURE__ */ new WeakMap();
20
+ const readonlySetViews = /* @__PURE__ */ new WeakMap();
21
+ const readonlyCollectionViewToRaw = /* @__PURE__ */ new WeakMap();
22
+ const normalizedProxyCarriers = /* @__PURE__ */ new WeakMap();
23
+ /**
24
+ * The realm's built-in prototype objects, rejected deliberately rather than by
25
+ * accident. Several of them look exactly like plain data to the rest of this
26
+ * module: `Array.prototype` is array-exotic (so `Array.isArray` short-circuits
27
+ * the prototype check below), while `Error.prototype`, `Date.prototype`,
28
+ * `Promise.prototype`, `String.prototype`, ... all inherit straight from
29
+ * `Object.prototype` and expose only extensible, writable data properties, so
30
+ * neither `assertExtensible` nor `assertDataProperties` stops them. Adopting
31
+ * one as deep state would let the `set` trap `Reflect.set` onto the real,
32
+ * globally shared intrinsic. Some of them (`Object.prototype.__proto__`,
33
+ * `Map.prototype.size`, ...) happen to be caught today by the accessor check,
34
+ * but that protection is incidental and asymmetric; this set makes it uniform.
35
+ *
36
+ * Built from `globalThis` so an engine missing a builtin simply contributes
37
+ * nothing. Only this realm's intrinsics are listed: an object from another
38
+ * realm already fails the `Object.prototype` identity check below (the one
39
+ * exception being a cross-realm array, which stays accepted as it is today).
40
+ */
41
+ const INTRINSIC_PROTOTYPES = (() => {
42
+ const prototypes = /* @__PURE__ */ new Set();
43
+ for (const name of [
44
+ "Object",
45
+ "Array",
46
+ "Function",
47
+ "Boolean",
48
+ "Number",
49
+ "String",
50
+ "Symbol",
51
+ "BigInt",
52
+ "Date",
53
+ "RegExp",
54
+ "Error",
55
+ "AggregateError",
56
+ "EvalError",
57
+ "RangeError",
58
+ "ReferenceError",
59
+ "SyntaxError",
60
+ "TypeError",
61
+ "URIError",
62
+ "Map",
63
+ "Set",
64
+ "WeakMap",
65
+ "WeakSet",
66
+ "WeakRef",
67
+ "FinalizationRegistry",
68
+ "Promise",
69
+ "ArrayBuffer",
70
+ "SharedArrayBuffer",
71
+ "DataView"
72
+ ]) {
73
+ const intrinsic = globalThis[name];
74
+ if (typeof intrinsic !== "function") continue;
75
+ const prototype = intrinsic.prototype;
76
+ if (typeof prototype === "object" && prototype !== null || typeof prototype === "function") prototypes.add(prototype);
77
+ }
78
+ return prototypes;
79
+ })();
80
+ const isIntrinsicPrototype = (value) => isObjectLike(value) && INTRINSIC_PROTOTYPES.has(value);
81
+ function isPlainObjectOrArray(value) {
82
+ if (typeof value !== "object" || value === null || isSignal(value)) return false;
83
+ if (INTRINSIC_PROTOTYPES.has(value)) return false;
84
+ if (Array.isArray(value)) return true;
85
+ const prototype = Object.getPrototypeOf(value);
86
+ return prototype === Object.prototype || prototype === null;
87
+ }
88
+ function isObjectLike(value) {
89
+ return typeof value === "object" && value !== null || typeof value === "function";
90
+ }
91
+ /**
92
+ * Resolves a value that may be one of our proxies or one of our readonly
93
+ * collection views back to the raw object it wraps. Returns `undefined` when
94
+ * `value` is not object-like or is not a value this module has wrapped, so
95
+ * callers can fall back to using it as-is.
96
+ */
97
+ function toRaw(value) {
98
+ if (!isObjectLike(value)) return void 0;
99
+ const directRaw = proxyToRaw.get(value);
100
+ if (directRaw !== void 0) return directRaw;
101
+ return readonlyCollectionViewToRaw.get(value);
102
+ }
103
+ /**
104
+ * Shared scaffolding for `readonlyMapView`/`readonlySetView`: cache lookup,
105
+ * the "reject this mutation" factory, defining the descriptors, the
106
+ * feature-detected extras loop, and the dual-WeakMap registration. Only the
107
+ * per-type descriptor bodies (which truly differ between Map and Set) and
108
+ * the extras list are supplied by the caller.
109
+ */
110
+ function createReadonlyCollectionView(raw, cache, prototype, kind, buildCore, buildExtras) {
111
+ const cached = cache.get(raw);
112
+ if (cached !== void 0) return cached;
113
+ const view = Object.create(prototype);
114
+ const rejectMutation = (operation) => () => {
115
+ throw new TypeError(`deepSignal() ${kind}#${operation}() is not allowed through .value; replace the ${kind} immutably`);
116
+ };
117
+ Object.defineProperties(view, buildCore(raw, view, rejectMutation));
118
+ for (const [name, impl] of buildExtras(raw, rejectMutation)) Object.defineProperty(view, name, {
119
+ enumerable: false,
120
+ configurable: false,
121
+ value: impl
122
+ });
123
+ cache.set(raw, view);
124
+ readonlyCollectionViewToRaw.set(view, raw);
125
+ return view;
126
+ }
127
+ function readonlyMapView(raw) {
128
+ return createReadonlyCollectionView(raw, readonlyMapViews, Map.prototype, "Map", (target, view, rejectMutation) => ({
129
+ size: {
130
+ enumerable: false,
131
+ configurable: false,
132
+ get: () => target.size
133
+ },
134
+ get: {
135
+ enumerable: false,
136
+ configurable: false,
137
+ value: (key) => target.get(key)
138
+ },
139
+ has: {
140
+ enumerable: false,
141
+ configurable: false,
142
+ value: (key) => target.has(key)
143
+ },
144
+ entries: {
145
+ enumerable: false,
146
+ configurable: false,
147
+ value: () => Map.prototype.entries.call(target)
148
+ },
149
+ keys: {
150
+ enumerable: false,
151
+ configurable: false,
152
+ value: () => Map.prototype.keys.call(target)
153
+ },
154
+ values: {
155
+ enumerable: false,
156
+ configurable: false,
157
+ value: () => Map.prototype.values.call(target)
158
+ },
159
+ forEach: {
160
+ enumerable: false,
161
+ configurable: false,
162
+ value: (callback, thisArg) => {
163
+ Map.prototype.forEach.call(target, (value, key) => callback.call(thisArg, value, key, view));
164
+ }
165
+ },
166
+ [Symbol.iterator]: {
167
+ enumerable: false,
168
+ configurable: false,
169
+ value: () => Map.prototype.entries.call(target)
170
+ },
171
+ set: {
172
+ enumerable: false,
173
+ configurable: false,
174
+ value: rejectMutation("set")
175
+ },
176
+ delete: {
177
+ enumerable: false,
178
+ configurable: false,
179
+ value: rejectMutation("delete")
180
+ },
181
+ clear: {
182
+ enumerable: false,
183
+ configurable: false,
184
+ value: rejectMutation("clear")
185
+ }
186
+ }), (_target, rejectMutation) => {
187
+ const extras = [];
188
+ for (const operation of [
189
+ "getOrInsert",
190
+ "getOrInsertComputed",
191
+ "emplace"
192
+ ]) if (typeof Map.prototype[operation] === "function") extras.push([operation, rejectMutation(operation)]);
193
+ return extras;
194
+ });
195
+ }
196
+ function readonlySetView(raw) {
197
+ return createReadonlyCollectionView(raw, readonlySetViews, Set.prototype, "Set", (target, view, rejectMutation) => ({
198
+ size: {
199
+ enumerable: false,
200
+ configurable: false,
201
+ get: () => target.size
202
+ },
203
+ has: {
204
+ enumerable: false,
205
+ configurable: false,
206
+ value: (value) => target.has(value)
207
+ },
208
+ entries: {
209
+ enumerable: false,
210
+ configurable: false,
211
+ value: () => Set.prototype.entries.call(target)
212
+ },
213
+ keys: {
214
+ enumerable: false,
215
+ configurable: false,
216
+ value: () => Set.prototype.keys.call(target)
217
+ },
218
+ values: {
219
+ enumerable: false,
220
+ configurable: false,
221
+ value: () => Set.prototype.values.call(target)
222
+ },
223
+ forEach: {
224
+ enumerable: false,
225
+ configurable: false,
226
+ value: (callback, thisArg) => {
227
+ Set.prototype.forEach.call(target, (value) => callback.call(thisArg, value, value, view));
228
+ }
229
+ },
230
+ [Symbol.iterator]: {
231
+ enumerable: false,
232
+ configurable: false,
233
+ value: () => Set.prototype.values.call(target)
234
+ },
235
+ add: {
236
+ enumerable: false,
237
+ configurable: false,
238
+ value: rejectMutation("add")
239
+ },
240
+ delete: {
241
+ enumerable: false,
242
+ configurable: false,
243
+ value: rejectMutation("delete")
244
+ },
245
+ clear: {
246
+ enumerable: false,
247
+ configurable: false,
248
+ value: rejectMutation("clear")
249
+ }
250
+ }), (target) => {
251
+ const extras = [];
252
+ const forwardSetOperation = (operation) => (other) => {
253
+ const method = Set.prototype[operation];
254
+ if (typeof method !== "function") throw new TypeError(`Set#${operation}() is unavailable in this JavaScript engine`);
255
+ const collection = isObjectLike(other) ? readonlyCollectionViewToRaw.get(other) ?? other : other;
256
+ return Reflect.apply(method, target, [collection]);
257
+ };
258
+ for (const operation of [
259
+ "union",
260
+ "intersection",
261
+ "difference",
262
+ "symmetricDifference",
263
+ "isSubsetOf",
264
+ "isSupersetOf",
265
+ "isDisjointFrom"
266
+ ]) if (typeof Set.prototype[operation] === "function") extras.push([operation, forwardSetOperation(operation)]);
267
+ return extras;
268
+ });
269
+ }
270
+ function assertRootValue(value) {
271
+ if (!isPlainObjectOrArray(value)) throw new TypeError("deepSignal() only accepts a plain object or array root");
272
+ }
273
+ function assertExtensible(value) {
274
+ if (!Object.isExtensible(value)) throw new TypeError("deepSignal() cannot proxy a non-extensible object or array");
275
+ }
276
+ function assertDataProperties(value) {
277
+ for (const key of Reflect.ownKeys(value)) {
278
+ const descriptor = Reflect.getOwnPropertyDescriptor(value, key);
279
+ if (descriptor !== void 0 && !("value" in descriptor)) throw new TypeError("deepSignal() does not support accessor properties");
280
+ }
281
+ }
282
+ function assertDeepDataGraph(value) {
283
+ if (!isObjectLike(value)) return false;
284
+ const seen = /* @__PURE__ */ new WeakMap();
285
+ const pending = [{
286
+ value,
287
+ insideOpaque: false
288
+ }];
289
+ let needsNormalization = false;
290
+ while (pending.length > 0) {
291
+ const { value: current, insideOpaque } = pending.pop();
292
+ if (toRaw(current) !== void 0) {
293
+ if (insideOpaque) throw new TypeError(proxyToRaw.has(current) ? "deepSignal() cannot store a deep proxy inside an opaque value" : "deepSignal() cannot store a deep collection view inside an opaque value");
294
+ needsNormalization = true;
295
+ continue;
296
+ }
297
+ const raw = current;
298
+ const visitedAs = seen.get(raw) ?? 0;
299
+ const visitFlag = insideOpaque ? 2 : 1;
300
+ if ((visitedAs & visitFlag) !== 0) continue;
301
+ seen.set(raw, visitedAs | visitFlag);
302
+ if (!insideOpaque && isIntrinsicPrototype(raw)) throw new TypeError("deepSignal() cannot store a built-in prototype object in deep state");
303
+ if (raw instanceof Map) {
304
+ for (const [key, entry] of raw) {
305
+ if (isObjectLike(key)) pending.push({
306
+ value: key,
307
+ insideOpaque: true
308
+ });
309
+ if (isObjectLike(entry)) pending.push({
310
+ value: entry,
311
+ insideOpaque: true
312
+ });
313
+ }
314
+ continue;
315
+ }
316
+ if (raw instanceof Set) {
317
+ for (const entry of raw) if (isObjectLike(entry)) pending.push({
318
+ value: entry,
319
+ insideOpaque: true
320
+ });
321
+ continue;
322
+ }
323
+ if (!isPlainObjectOrArray(raw)) {
324
+ for (const key of Reflect.ownKeys(raw)) {
325
+ const descriptor = Reflect.getOwnPropertyDescriptor(raw, key);
326
+ if (descriptor !== void 0 && "value" in descriptor && isObjectLike(descriptor.value)) pending.push({
327
+ value: descriptor.value,
328
+ insideOpaque: true
329
+ });
330
+ }
331
+ continue;
332
+ }
333
+ if (!insideOpaque) {
334
+ assertExtensible(raw);
335
+ assertDataProperties(raw);
336
+ }
337
+ for (const key of Reflect.ownKeys(raw)) {
338
+ const descriptor = Reflect.getOwnPropertyDescriptor(raw, key);
339
+ if (descriptor !== void 0 && "value" in descriptor) {
340
+ const child = descriptor.value;
341
+ if (isObjectLike(child)) pending.push({
342
+ value: child,
343
+ insideOpaque
344
+ });
345
+ }
346
+ }
347
+ }
348
+ return needsNormalization;
349
+ }
350
+ function matchesNormalizedGraph(source, normalized) {
351
+ const sources = /* @__PURE__ */ new WeakMap();
352
+ const targets = /* @__PURE__ */ new WeakMap();
353
+ const pending = [[source, normalized]];
354
+ while (pending.length > 0) {
355
+ const [currentSource, currentTarget] = pending.pop();
356
+ if (typeof currentSource !== "object" || currentSource === null) {
357
+ if (!Object.is(currentSource, currentTarget)) return false;
358
+ continue;
359
+ }
360
+ const wrapperRaw = toRaw(currentSource);
361
+ if (wrapperRaw !== void 0) {
362
+ if (wrapperRaw !== currentTarget) return false;
363
+ continue;
364
+ }
365
+ if (!isPlainObjectOrArray(currentSource)) {
366
+ if (currentSource !== currentTarget) return false;
367
+ continue;
368
+ }
369
+ if (typeof currentTarget !== "object" || currentTarget === null || !isPlainObjectOrArray(currentTarget)) return false;
370
+ const knownTarget = sources.get(currentSource);
371
+ if (knownTarget !== void 0) {
372
+ if (knownTarget !== currentTarget) return false;
373
+ continue;
374
+ }
375
+ const knownSource = targets.get(currentTarget);
376
+ if (knownSource !== void 0 && knownSource !== currentSource) return false;
377
+ sources.set(currentSource, currentTarget);
378
+ targets.set(currentTarget, currentSource);
379
+ if (Array.isArray(currentSource) !== Array.isArray(currentTarget) || Object.getPrototypeOf(currentSource) !== Object.getPrototypeOf(currentTarget)) return false;
380
+ const sourceKeys = Reflect.ownKeys(currentSource);
381
+ const targetKeys = Reflect.ownKeys(currentTarget);
382
+ if (sourceKeys.length !== targetKeys.length) return false;
383
+ for (const [index, key] of sourceKeys.entries()) {
384
+ if (key !== targetKeys[index]) return false;
385
+ const sourceDescriptor = Reflect.getOwnPropertyDescriptor(currentSource, key);
386
+ const targetDescriptor = Reflect.getOwnPropertyDescriptor(currentTarget, key);
387
+ if (sourceDescriptor === void 0 || targetDescriptor === void 0 || !("value" in sourceDescriptor) || !("value" in targetDescriptor) || sourceDescriptor.configurable !== targetDescriptor.configurable || sourceDescriptor.enumerable !== targetDescriptor.enumerable || sourceDescriptor.writable !== targetDescriptor.writable) return false;
388
+ pending.push([sourceDescriptor.value, targetDescriptor.value]);
389
+ }
390
+ }
391
+ return true;
392
+ }
393
+ /**
394
+ * An array of `length` with no own index properties — holes, not `undefined`s.
395
+ * `Array.from({ length })` would materialize every index as an own property,
396
+ * so cloning a sparse carrier produced an array with strictly more own keys
397
+ * than its source. `matchesNormalizedGraph` compares own keys one-for-one, so
398
+ * that clone could never match its source again: the carrier cache missed on
399
+ * every re-assignment, breaking the documented guarantee that assigning the
400
+ * same carrier twice preserves identity and aliasing.
401
+ */
402
+ function emptyArrayOfLength(length) {
403
+ const result = [];
404
+ result.length = length;
405
+ return result;
406
+ }
407
+ function cloneWithoutProxies(value) {
408
+ const wrapperRaw = toRaw(value);
409
+ if (wrapperRaw !== void 0) return wrapperRaw;
410
+ if (!isObjectLike(value)) return value;
411
+ const cachedRoot = normalizedProxyCarriers.get(value);
412
+ if (cachedRoot !== void 0 && matchesNormalizedGraph(value, cachedRoot)) return cachedRoot;
413
+ const clones = /* @__PURE__ */ new WeakMap();
414
+ const created = [];
415
+ const pending = [];
416
+ const resolve = (current) => {
417
+ const resolvedRaw = toRaw(current);
418
+ if (resolvedRaw !== void 0) return resolvedRaw;
419
+ if (!isPlainObjectOrArray(current)) return current;
420
+ const local = clones.get(current);
421
+ if (local !== void 0) return local;
422
+ const result = Array.isArray(current) ? emptyArrayOfLength(current.length) : Object.create(Object.getPrototypeOf(current));
423
+ clones.set(current, result);
424
+ created.push([current, result]);
425
+ pending.push(current);
426
+ return result;
427
+ };
428
+ const root = resolve(value);
429
+ while (pending.length > 0) {
430
+ const source = pending.pop();
431
+ const target = clones.get(source);
432
+ for (const key of Reflect.ownKeys(source)) {
433
+ if (Array.isArray(source) && key === "length") continue;
434
+ const descriptor = Reflect.getOwnPropertyDescriptor(source, key);
435
+ if (descriptor === void 0) continue;
436
+ if (!("value" in descriptor)) throw new TypeError("deepSignal() does not support accessor properties");
437
+ descriptor.value = resolve(descriptor.value);
438
+ Reflect.defineProperty(target, key, descriptor);
439
+ }
440
+ }
441
+ for (const [source, target] of created) normalizedProxyCarriers.set(source, target);
442
+ return root;
443
+ }
444
+ function prepareDeepValue(value) {
445
+ const wrapperRaw = toRaw(value);
446
+ if (wrapperRaw !== void 0) return wrapperRaw;
447
+ return assertDeepDataGraph(value) ? cloneWithoutProxies(value) : value;
448
+ }
449
+ function isArrayIndex(key) {
450
+ if (typeof key !== "string" || key === "") return false;
451
+ const index = Number(key);
452
+ return Number.isInteger(index) && index >= 0 && index < 4294967295 && String(index) === key;
453
+ }
454
+ const unwrap = (value) => {
455
+ return prepareDeepValue(value);
456
+ };
457
+ const OBJECT_PROTOTYPE_KEYS = new Set(Reflect.ownKeys(Object.prototype));
458
+ const ARRAY_PROTOTYPE_KEYS = /* @__PURE__ */ new Set([...Reflect.ownKeys(Object.prototype), ...Reflect.ownKeys(Array.prototype)]);
459
+ const isInheritedPrototypeMember = (target, key) => {
460
+ if (Object.prototype.hasOwnProperty.call(target, key)) return false;
461
+ const prototype = Object.getPrototypeOf(target);
462
+ if (prototype === Object.prototype) return OBJECT_PROTOTYPE_KEYS.has(key);
463
+ if (prototype === Array.prototype) return ARRAY_PROTOTYPE_KEYS.has(key);
464
+ return false;
465
+ };
466
+ const getVersion = (versions, key) => {
467
+ let version = versions.get(key);
468
+ if (version === void 0) {
469
+ version = registerSignal(new SignalImpl(0));
470
+ versions.set(key, version);
471
+ }
472
+ return version;
473
+ };
474
+ const track = (target, versions, indices, key) => {
475
+ if (getActiveSub() === void 0 && !hasActiveRenderCollector()) return;
476
+ if (isInheritedPrototypeMember(target, key)) return;
477
+ if (isArrayIndex(key)) indices.add(Number(key));
478
+ const version = getVersion(versions, key);
479
+ version.markWatched();
480
+ version.value;
481
+ };
482
+ const notify = (versions, key) => {
483
+ const version = versions.get(key);
484
+ if (version !== void 0) version.value += 1;
485
+ };
486
+ /**
487
+ * Queues `key`'s version signals for removal once nothing depends on them.
488
+ * Only called for a key that has just been removed *and* notified, which is
489
+ * what makes the eventual removal safe: the notification forces every
490
+ * subscriber to re-run, so one that still cares re-reads the key (re-arming
491
+ * `markWatched`) and one that does not has already been unlinked.
492
+ */
493
+ const markPrunable = (metadata, key) => {
494
+ if (!metadata.properties.has(key) && !metadata.existence.has(key)) return;
495
+ (metadata.prunable ??= /* @__PURE__ */ new Set()).add(key);
496
+ };
497
+ /**
498
+ * Drops the version signals of removed keys once they have no subscribers
499
+ * left, so a virtualized or repeatedly-spliced list stops accumulating one
500
+ * entry per key that ever existed.
501
+ *
502
+ * Runs only at batch depth 0, where the write that queued these keys has
503
+ * already flushed its effects; before that, `hasSubscribers()` would still
504
+ * report the pre-flush picture. A key that is still subscribed (typically a
505
+ * React store whose re-render has not committed yet) stays queued and is
506
+ * reconsidered on the next sweep rather than being dropped early.
507
+ */
508
+ const sweepPrunedKeys = (metadata, target) => {
509
+ const prunable = metadata.prunable;
510
+ if (prunable === void 0 || prunable.size === 0) return;
511
+ if (getBatchDepth() !== 0) return;
512
+ for (const key of prunable) {
513
+ if (Object.prototype.hasOwnProperty.call(target, key)) {
514
+ prunable.delete(key);
515
+ continue;
516
+ }
517
+ const property = metadata.properties.get(key);
518
+ const existence = metadata.existence.get(key);
519
+ if (property?.hasSubscribers() === true || existence?.hasSubscribers() === true) continue;
520
+ metadata.properties.delete(key);
521
+ metadata.existence.delete(key);
522
+ if (isArrayIndex(key)) {
523
+ const index = Number(key);
524
+ metadata.propertyIndices.delete(index);
525
+ metadata.existenceIndices.delete(index);
526
+ }
527
+ prunable.delete(key);
528
+ }
529
+ if (prunable.size === 0) metadata.prunable = void 0;
530
+ };
531
+ const trackIteration = (metadata) => {
532
+ if (getActiveSub() === void 0 && !hasActiveRenderCollector()) return;
533
+ metadata.iteration ??= signal$1(0);
534
+ metadata.iteration.value;
535
+ };
536
+ const notifyIteration = (metadata) => {
537
+ if (metadata.iteration !== void 0) metadata.iteration.value += 1;
538
+ };
539
+ const notifyTruncatedIndices = (metadata, currentLength, oldLength) => {
540
+ const truncatedCount = oldLength - currentLength;
541
+ const trackedCount = metadata.propertyIndices.size + metadata.existenceIndices.size;
542
+ if (trackedCount === 0) return;
543
+ if (truncatedCount <= trackedCount) {
544
+ for (let index = currentLength; index < oldLength; index++) {
545
+ const key = String(index);
546
+ notify(metadata.properties, key);
547
+ notify(metadata.existence, key);
548
+ markPrunable(metadata, key);
549
+ }
550
+ return;
551
+ }
552
+ for (const index of metadata.propertyIndices) if (index >= currentLength && index < oldLength) {
553
+ const key = String(index);
554
+ notify(metadata.properties, key);
555
+ markPrunable(metadata, key);
556
+ }
557
+ for (const index of metadata.existenceIndices) if (index >= currentLength && index < oldLength) {
558
+ const key = String(index);
559
+ notify(metadata.existence, key);
560
+ markPrunable(metadata, key);
561
+ }
562
+ };
563
+ /**
564
+ * Whether `wrap()` would hand back something other than `value` itself — a
565
+ * deep proxy for a plain object/array, or a readonly view for a Map/Set. The
566
+ * `get` trap uses this to reject a non-configurable, non-writable property
567
+ * whose value cannot be substituted, before `wrap()` performs the swap.
568
+ */
569
+ const isWrappedByValue = (value) => isPlainObjectOrArray(value) || value instanceof Map || value instanceof Set;
570
+ const wrap = (value) => {
571
+ const rawValue = toRaw(value) ?? value;
572
+ if (rawValue instanceof Map) return readonlyMapView(rawValue);
573
+ if (rawValue instanceof Set) return readonlySetView(rawValue);
574
+ if (!isPlainObjectOrArray(rawValue)) return rawValue;
575
+ assertExtensible(rawValue);
576
+ const cached = rawToMetadata.get(rawValue);
577
+ if (cached !== void 0) return cached.proxy;
578
+ assertDataProperties(rawValue);
579
+ const metadata = {
580
+ properties: /* @__PURE__ */ new Map(),
581
+ existence: /* @__PURE__ */ new Map(),
582
+ propertyIndices: /* @__PURE__ */ new Set(),
583
+ existenceIndices: /* @__PURE__ */ new Set(),
584
+ arrayMethods: /* @__PURE__ */ new Map(),
585
+ proxy: void 0
586
+ };
587
+ const proxy = new Proxy(rawValue, {
588
+ get(target, key, receiver) {
589
+ if (key === SIGNAL_BRAND && !Object.prototype.hasOwnProperty.call(target, key)) return;
590
+ track(target, metadata.properties, metadata.propertyIndices, key);
591
+ const result = Reflect.get(target, key, receiver);
592
+ if (Array.isArray(target) && ARRAY_MUTATORS.has(key) && typeof result === "function") {
593
+ const cachedMethod = metadata.arrayMethods.get(key);
594
+ if (cachedMethod?.method === result) return cachedMethod.wrapper;
595
+ const method = result;
596
+ const wrapper = function(...args) {
597
+ try {
598
+ return batch(() => Reflect.apply(method, this, args));
599
+ } finally {
600
+ sweepPrunedKeys(metadata, target);
601
+ }
602
+ };
603
+ metadata.arrayMethods.set(key, {
604
+ method,
605
+ wrapper
606
+ });
607
+ return wrapper;
608
+ }
609
+ if (isWrappedByValue(result)) {
610
+ const descriptor = Reflect.getOwnPropertyDescriptor(target, key);
611
+ if (descriptor !== void 0 && "value" in descriptor && descriptor.configurable === false && descriptor.writable === false) throw new TypeError("deepSignal() cannot wrap a non-configurable, non-writable object property");
612
+ }
613
+ return wrap(result);
614
+ },
615
+ set(target, key, nextValue) {
616
+ if (key === "__proto__") throw new TypeError("deepSignal() does not support prototype mutation");
617
+ if (key === SIGNAL_BRAND) throw new TypeError("deepSignal() does not support branding state as a signal");
618
+ const oldValue = Reflect.get(target, key, target);
619
+ const existed = Reflect.has(target, key);
620
+ const owned = Object.prototype.hasOwnProperty.call(target, key);
621
+ const oldLength = Array.isArray(target) ? target.length : void 0;
622
+ const rawNextValue = unwrap(nextValue);
623
+ if (!Reflect.set(target, key, rawNextValue, target)) return false;
624
+ const currentValue = Reflect.get(target, key, target);
625
+ const existsNow = Reflect.has(target, key);
626
+ const ownedNow = Object.prototype.hasOwnProperty.call(target, key);
627
+ batch(() => {
628
+ if (!Object.is(oldValue, currentValue) || owned !== ownedNow) notify(metadata.properties, key);
629
+ if (existed !== existsNow) notify(metadata.existence, key);
630
+ if (owned !== ownedNow) notifyIteration(metadata);
631
+ if (Array.isArray(target) && oldLength !== void 0) {
632
+ const currentLength = target.length;
633
+ if (key !== "length" && oldLength !== currentLength) notify(metadata.properties, "length");
634
+ if (key === "length" && currentLength < oldLength) {
635
+ notifyTruncatedIndices(metadata, currentLength, oldLength);
636
+ notifyIteration(metadata);
637
+ }
638
+ }
639
+ });
640
+ sweepPrunedKeys(metadata, target);
641
+ return true;
642
+ },
643
+ deleteProperty(target, key) {
644
+ const existed = Reflect.has(target, key);
645
+ const owned = Object.prototype.hasOwnProperty.call(target, key);
646
+ const succeeded = Reflect.deleteProperty(target, key);
647
+ if (!succeeded || !owned) return succeeded;
648
+ batch(() => {
649
+ notify(metadata.properties, key);
650
+ if (existed !== Reflect.has(target, key)) notify(metadata.existence, key);
651
+ notifyIteration(metadata);
652
+ markPrunable(metadata, key);
653
+ });
654
+ sweepPrunedKeys(metadata, target);
655
+ return true;
656
+ },
657
+ getOwnPropertyDescriptor(target, key) {
658
+ track(target, metadata.properties, metadata.propertyIndices, key);
659
+ const descriptor = Reflect.getOwnPropertyDescriptor(target, key);
660
+ if (descriptor === void 0 || !("value" in descriptor)) return descriptor;
661
+ if (descriptor.configurable === false && descriptor.writable === false) return descriptor;
662
+ descriptor.value = wrap(descriptor.value);
663
+ return descriptor;
664
+ },
665
+ has(target, key) {
666
+ track(target, metadata.existence, metadata.existenceIndices, key);
667
+ return Reflect.has(target, key);
668
+ },
669
+ ownKeys(target) {
670
+ trackIteration(metadata);
671
+ return Reflect.ownKeys(target);
672
+ },
673
+ defineProperty() {
674
+ throw new TypeError("deepSignal() does not support property descriptors");
675
+ },
676
+ preventExtensions() {
677
+ throw new TypeError("deepSignal() state must remain extensible");
678
+ },
679
+ setPrototypeOf() {
680
+ throw new TypeError("deepSignal() does not support prototype mutation");
681
+ }
682
+ });
683
+ metadata.proxy = proxy;
684
+ rawToMetadata.set(rawValue, metadata);
685
+ proxyToRaw.set(proxy, rawValue);
686
+ return proxy;
687
+ };
688
+ var DeepSignalImpl = class {
689
+ #source;
690
+ constructor(initialValue) {
691
+ this.#source = new SignalImpl(initialValue);
692
+ }
693
+ get value() {
694
+ return wrap(this.#source.value);
695
+ }
696
+ set value(nextValue) {
697
+ const rawValue = unwrap(nextValue);
698
+ assertRootValue(rawValue);
699
+ this.#source.value = rawValue;
700
+ }
701
+ peek() {
702
+ return this.#source.peek();
703
+ }
704
+ };
705
+ /**
706
+ * Creates a signal that lazily tracks nested plain-object and array properties.
707
+ * Mutations must go through `.value`; changes made through the original raw
708
+ * object are intentionally not observable.
709
+ */
710
+ function deepSignal(initialValue) {
711
+ const rawInitialValue = prepareDeepValue(initialValue);
712
+ assertRootValue(rawInitialValue);
713
+ wrap(rawInitialValue);
714
+ return registerSignal(new DeepSignalImpl(rawInitialValue));
715
+ }
716
+ //#endregion
717
+ //#region src/react/hooks.ts
718
+ const EMPTY_DEPENDENCIES = [];
719
+ function assertSignalSnapshot(value) {
720
+ if (typeof value === "object" && value !== null || typeof value === "function") throw new TypeError("useDeepSignalValue selector must return a primitive snapshot; objects, Proxies, and functions are not supported");
721
+ }
722
+ /**
723
+ * Shared `useSyncExternalStore` subscription wiring for a signal-backed
724
+ * store. On `subscribe`, starts an `effect()` that reruns `onEvaluate` on
725
+ * every relevant signal write and notifies React exactly when it reports a
726
+ * change; returns that effect's own dispose as the unsubscribe. The very
727
+ * first synchronous run — made while `subscribe` itself is still
728
+ * establishing the effect — never notifies, matching how each caller here
729
+ * already behaved: `useSyncExternalStore` performs its own "did the
730
+ * snapshot change since render" check independently of anything a `notify`
731
+ * call does, so a store's own subscribe-time run does not need to report on
732
+ * that window too.
733
+ *
734
+ * `getSnapshot` is intentionally not this helper's concern — the caller
735
+ * wires its own, whether that means recomputing fresh each call
736
+ * (`useSignalValue`) or returning a cached result (the deep-selector store).
737
+ *
738
+ * An exception thrown by `onEvaluate` is swallowed here and treated as a
739
+ * change worth notifying about. This mirrors `useSignalValue`'s prior
740
+ * inline behavior: a computed that starts failing must still trigger a
741
+ * re-render so `getSnapshot`'s own unguarded read can rethrow into an Error
742
+ * Boundary, rather than leaving this background effect's throw to escape
743
+ * into whatever write triggered the re-run. Callers whose `onEvaluate`
744
+ * already captures errors into its own return value (the deep-selector
745
+ * store) never hit this path.
746
+ */
747
+ function createSignalStore(onEvaluate) {
748
+ return (notify) => {
749
+ let isInitialRun = true;
750
+ return effect$1(() => {
751
+ let changed;
752
+ try {
753
+ changed = onEvaluate();
754
+ } catch {
755
+ changed = true;
756
+ }
757
+ if (isInitialRun) {
758
+ isInitialRun = false;
759
+ return;
760
+ }
761
+ if (changed) notify();
762
+ });
763
+ };
764
+ }
765
+ /**
766
+ * Holds a selector result outside the reactive graph. In particular, a
767
+ * selector error must not escape from the signal write that caused a reactive
768
+ * re-evaluation: React needs to observe it during its next render so an Error
769
+ * Boundary can handle it. Unlike `useSignalValue`, errors are modeled as an
770
+ * explicit `SelectorResult` union rather than swallowed and re-thrown from a
771
+ * fresh read: `getSnapshot` below must return the same cached result the
772
+ * subscribed effect last settled on, not re-run `selector` on every render.
773
+ */
774
+ function createDeepSelectorStore(source, selector) {
775
+ const evaluate = () => {
776
+ try {
777
+ const value = untrackedRender(() => selector(source.value));
778
+ assertSignalSnapshot(value);
779
+ return {
780
+ kind: "value",
781
+ value
782
+ };
783
+ } catch (error) {
784
+ return {
785
+ kind: "error",
786
+ error
787
+ };
788
+ }
789
+ };
790
+ let result = evaluate();
791
+ const hasChanged = (next) => {
792
+ if (result.kind !== next.kind) return true;
793
+ return result.kind === "value" && next.kind === "value" ? !Object.is(result.value, next.value) : result.kind === "error" && next.kind === "error" ? !Object.is(result.error, next.error) : false;
794
+ };
795
+ return {
796
+ subscribe: createSignalStore(() => {
797
+ const next = evaluate();
798
+ if (!hasChanged(next)) return false;
799
+ result = next;
800
+ return true;
801
+ }),
802
+ getSnapshot() {
803
+ if (result.kind === "error") throw result.error;
804
+ return result.value;
805
+ }
806
+ };
807
+ }
808
+ /** Creates a signal whose identity is stable for the lifetime of this component. */
809
+ function useSignal(initialValue) {
810
+ const signalRef = useRef(void 0);
811
+ if (signalRef.current === void 0) signalRef.current = signal$1(initialValue);
812
+ return signalRef.current;
813
+ }
814
+ /**
815
+ * Creates a deep signal whose identity is stable for the component lifetime.
816
+ * A factory is evaluated only while initializing a mounted component instance;
817
+ * it must remain pure because React Strict Mode may replay initial rendering.
818
+ */
819
+ function useDeepSignal(initialValue) {
820
+ const signalRef = useRef(void 0);
821
+ if (signalRef.current === void 0) signalRef.current = deepSignal(typeof initialValue === "function" ? initialValue() : initialValue);
822
+ return signalRef.current;
823
+ }
824
+ /**
825
+ * Selects a property-level primitive snapshot from a deep signal.
826
+ *
827
+ * Every non-signal value captured by `selector` must be listed in
828
+ * `dependencies`. Object and proxy results are intentionally rejected because
829
+ * mutable snapshots cannot satisfy `useSyncExternalStore` identity semantics.
830
+ *
831
+ * `dependencies` must keep a fixed length across this component's lifetime,
832
+ * matching `useMemo`'s own rule (its length feeds a `useMemo` deps array
833
+ * below via `[source, ...dependencies]`). A length change is caught and
834
+ * thrown as a clear error rather than left to degrade into React's silent
835
+ * "changed size between renders" dev warning, mirroring `useComputed`'s
836
+ * dependency-mode-switch guard.
837
+ */
838
+ function useDeepSignalValue(source, selector, dependencies) {
839
+ const initialDependencyLengthRef = useRef(void 0);
840
+ initialDependencyLengthRef.current ??= dependencies.length;
841
+ if (dependencies.length !== initialDependencyLengthRef.current) {
842
+ const error = /* @__PURE__ */ new Error(`useDeepSignalValue: the \`dependencies\` array length changed between renders (from ${initialDependencyLengthRef.current} to ${dependencies.length}) for this call site. Keep \`dependencies\` a fixed length across the component's lifetime, matching useMemo's rules.`);
843
+ error.name = "UseDeepSignalValueDependencyLengthChangeError";
844
+ throw error;
845
+ }
846
+ const store = useMemo(() => createDeepSelectorStore(source, selector), [source, ...dependencies]);
847
+ return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
848
+ }
849
+ /**
850
+ * Creates a computed signal with a stable identity.
851
+ *
852
+ * When `dependencies` is omitted, the getter must only read signals. Its initial
853
+ * closure is retained for the component lifetime, so props, state, and other
854
+ * non-signal values must not be captured in that mode.
855
+ *
856
+ * When the getter captures props, state, or any other non-signal value, list all
857
+ * of those values in `dependencies`. React memoization then creates a separate
858
+ * computed when they change, rather than replacing the getter of an existing
859
+ * computed during render. An abandoned render therefore cannot change the
860
+ * closure used by the previously committed computed. Choose one mode for a
861
+ * component's lifetime.
862
+ */
863
+ function useComputed(getValue, dependencies) {
864
+ const dependencyComputed = useMemo(() => dependencies === void 0 ? void 0 : computed$1(getValue), dependencies ?? EMPTY_DEPENDENCIES);
865
+ const initialModeRef = useRef(void 0);
866
+ const signalOnlyComputedRef = useRef(void 0);
867
+ const mode = dependencies === void 0 ? "without a dependency array" : "with a dependency array";
868
+ initialModeRef.current ??= mode;
869
+ if (initialModeRef.current !== mode) {
870
+ const error = /* @__PURE__ */ new Error(`useComputed: the dependency-array mode changed between renders (from ${initialModeRef.current} to ${mode}) for this call site. Keep passing deps consistently, matching useMemo's rules.`);
871
+ error.name = "UseComputedModeChangeError";
872
+ throw error;
873
+ }
874
+ if (dependencyComputed !== void 0) return dependencyComputed;
875
+ if (signalOnlyComputedRef.current === void 0) signalOnlyComputedRef.current = computed$1(getValue);
876
+ return signalOnlyComputedRef.current;
877
+ }
878
+ /**
879
+ * Runs a reactive effect after this component has committed, disposing it when
880
+ * the component unmounts (and during React Strict Mode's development replay).
881
+ *
882
+ * When `dependencies` is omitted, the callback must only capture signals. Its
883
+ * initial closure is retained for the component lifetime, so unrelated React
884
+ * renders do not restart the effect.
885
+ *
886
+ * When the callback captures props, state, or any other non-signal value, list
887
+ * all of those values in `dependencies`. The effect is then reconnected after
888
+ * those dependencies change. Choose one mode for a component's lifetime.
889
+ */
890
+ function useSignalEffect(callback, dependencies) {
891
+ useEffect(() => effect$1(callback), dependencies ?? EMPTY_DEPENDENCIES);
892
+ }
893
+ /**
894
+ * Reads a signal and subscribes the component to subsequent changes.
895
+ *
896
+ * The immediate run made by the public `effect` API establishes dependency
897
+ * tracking only. It intentionally does not notify React: `useSyncExternalStore`
898
+ * owns the initial consistency check after subscribing. See `createSignalStore`
899
+ * for the shared subscribe/notify wiring, including why a read that throws
900
+ * (a computed whose cached error `.value` rethrows) is swallowed in the
901
+ * background effect and left for `getSnapshot` to rethrow instead.
902
+ */
903
+ function useSignalValue(source) {
904
+ const subscribe = useMemo(() => createSignalStore(() => {
905
+ source.value;
906
+ return true;
907
+ }), [source]);
908
+ const getSnapshot = useCallback(() => untracked(() => source.value), [source]);
909
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
910
+ }
911
+ //#endregion
912
+ export { useSignalEffect as a, useSignal as i, useDeepSignal as n, useSignalValue as o, useDeepSignalValue as r, deepSignal as s, useComputed as t };
913
+
914
+ //# sourceMappingURL=hooks-BFIWh7AD.js.map