mutts 1.0.4 → 1.0.5
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/chunks/{index-GRBSx0mB.js → index-Cvxdw6Ax.js} +164 -12
- package/dist/chunks/index-Cvxdw6Ax.js.map +1 -0
- package/dist/chunks/{index-79Kk8D6e.esm.js → index-qiWwozOc.esm.js} +163 -13
- package/dist/chunks/index-qiWwozOc.esm.js.map +1 -0
- package/dist/destroyable.esm.js.map +1 -1
- package/dist/destroyable.js.map +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.js +3 -1
- package/dist/index.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/reactive.d.ts +29 -1
- package/dist/reactive.esm.js +1 -1
- package/dist/reactive.js +3 -1
- package/dist/reactive.js.map +1 -1
- package/dist/std-decorators.esm.js.map +1 -1
- package/dist/std-decorators.js.map +1 -1
- package/docs/reactive/core.md +16 -16
- package/docs/reactive.md +7 -0
- package/package.json +1 -1
- package/src/destroyable.ts +2 -2
- package/src/reactive/array.ts +3 -5
- package/src/reactive/change.ts +6 -2
- package/src/reactive/effects.ts +70 -1
- package/src/reactive/index.ts +2 -1
- package/src/reactive/interface.ts +1 -1
- package/src/reactive/map.ts +6 -6
- package/src/reactive/mapped.ts +2 -3
- package/src/reactive/project.ts +103 -6
- package/src/reactive/set.ts +6 -6
- package/src/reactive/types.ts +22 -0
- package/src/reactive/zone.ts +1 -1
- package/src/std-decorators.ts +1 -1
- package/dist/chunks/index-79Kk8D6e.esm.js.map +0 -1
- package/dist/chunks/index-GRBSx0mB.js.map +0 -1
- /package/{src/reactive/project.project.md → docs/reactive/project.md} +0 -0
|
@@ -352,6 +352,10 @@ const prototypeForwarding = Symbol('prototype-forwarding');
|
|
|
352
352
|
* Symbol representing all properties in reactive tracking
|
|
353
353
|
*/
|
|
354
354
|
const allProps = Symbol('all-props');
|
|
355
|
+
/**
|
|
356
|
+
* Symbol for accessing projection information on reactive objects
|
|
357
|
+
*/
|
|
358
|
+
const projectionInfo = Symbol('projection-info');
|
|
355
359
|
// Symbol to mark functions with their root function
|
|
356
360
|
const rootFunction = Symbol('root-function');
|
|
357
361
|
/**
|
|
@@ -426,6 +430,12 @@ const options = {
|
|
|
426
430
|
* @default 100
|
|
427
431
|
*/
|
|
428
432
|
maxEffectChain: 100,
|
|
433
|
+
/**
|
|
434
|
+
* Maximum number of times an effect can be triggered by the same cause in a single batch
|
|
435
|
+
* Used to detect aggressive re-computation or infinite loops
|
|
436
|
+
* @default 10
|
|
437
|
+
*/
|
|
438
|
+
maxTriggerPerBatch: 10,
|
|
429
439
|
/**
|
|
430
440
|
* Debug purpose: maximum effect reaction (like call stack max depth)
|
|
431
441
|
* Used to prevent infinite loops
|
|
@@ -1258,6 +1268,50 @@ function formatRoots(roots, limit = 20) {
|
|
|
1258
1268
|
const end = names.slice(-10);
|
|
1259
1269
|
return `${start.join(' → ')} ... (${names.length - 15} more) ... ${end.join(' → ')}`;
|
|
1260
1270
|
}
|
|
1271
|
+
// Nested map structure for efficient counting and batch cleanup
|
|
1272
|
+
// batchId -> effect root -> obj -> prop -> count
|
|
1273
|
+
let activationRegistry;
|
|
1274
|
+
const activationLog = new Array(100);
|
|
1275
|
+
function getActivationLog() {
|
|
1276
|
+
return activationLog;
|
|
1277
|
+
}
|
|
1278
|
+
function recordActivation(effect, obj, evolution, prop) {
|
|
1279
|
+
const root = getRoot(effect);
|
|
1280
|
+
if (!activationRegistry)
|
|
1281
|
+
return;
|
|
1282
|
+
let effectData = activationRegistry.get(root);
|
|
1283
|
+
if (!effectData) {
|
|
1284
|
+
effectData = new Map();
|
|
1285
|
+
activationRegistry.set(root, effectData);
|
|
1286
|
+
}
|
|
1287
|
+
let objData = effectData.get(obj);
|
|
1288
|
+
if (!objData) {
|
|
1289
|
+
objData = new Map();
|
|
1290
|
+
effectData.set(obj, objData);
|
|
1291
|
+
}
|
|
1292
|
+
const count = (objData.get(prop) ?? 0) + 1;
|
|
1293
|
+
objData.set(prop, count);
|
|
1294
|
+
// Keep a limited history for diagnostics
|
|
1295
|
+
activationLog.unshift({
|
|
1296
|
+
effect,
|
|
1297
|
+
obj,
|
|
1298
|
+
evolution,
|
|
1299
|
+
prop,
|
|
1300
|
+
});
|
|
1301
|
+
activationLog.pop();
|
|
1302
|
+
if (count >= options.maxTriggerPerBatch) {
|
|
1303
|
+
const effectName = root?.name || 'anonymous';
|
|
1304
|
+
const message = `Aggressive trigger detected: effect "${effectName}" triggered ${count} times in the batch by the same cause.`;
|
|
1305
|
+
if (options.maxEffectReaction === 'throw') {
|
|
1306
|
+
throw new ReactiveError(message, {
|
|
1307
|
+
code: ReactiveErrorCode.MaxReactionExceeded,
|
|
1308
|
+
count,
|
|
1309
|
+
effect: effectName,
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
options.warn(`[reactive] ${message}`);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1261
1315
|
/**
|
|
1262
1316
|
* Registers a debug callback that is called when the current effect is triggered by a dependency change
|
|
1263
1317
|
*
|
|
@@ -1547,6 +1601,9 @@ function cleanupEffectFromGraph(effect) {
|
|
|
1547
1601
|
// Track currently executing effects to prevent re-execution
|
|
1548
1602
|
// These are all the effects triggered under `activeEffect`
|
|
1549
1603
|
let batchQueue;
|
|
1604
|
+
function hasBatched(effect) {
|
|
1605
|
+
return batchQueue?.all.has(getRoot(effect));
|
|
1606
|
+
}
|
|
1550
1607
|
const batchCleanups = new Set();
|
|
1551
1608
|
/**
|
|
1552
1609
|
* Computes and caches in-degrees for all effects in the batch
|
|
@@ -1972,6 +2029,10 @@ function batch(effect, immediate) {
|
|
|
1972
2029
|
}
|
|
1973
2030
|
else {
|
|
1974
2031
|
// New batch - initialize
|
|
2032
|
+
if (!activationRegistry)
|
|
2033
|
+
activationRegistry = new Map();
|
|
2034
|
+
else
|
|
2035
|
+
throw new Error('Batch already in progress');
|
|
1975
2036
|
options.beginChain(roots);
|
|
1976
2037
|
batchQueue = {
|
|
1977
2038
|
all: new Map(),
|
|
@@ -2050,6 +2111,7 @@ function batch(effect, immediate) {
|
|
|
2050
2111
|
return firstReturn.value;
|
|
2051
2112
|
}
|
|
2052
2113
|
finally {
|
|
2114
|
+
activationRegistry = undefined;
|
|
2053
2115
|
batchQueue = undefined;
|
|
2054
2116
|
options.endChain();
|
|
2055
2117
|
}
|
|
@@ -2120,6 +2182,7 @@ function batch(effect, immediate) {
|
|
|
2120
2182
|
return firstReturn.value;
|
|
2121
2183
|
}
|
|
2122
2184
|
finally {
|
|
2185
|
+
activationRegistry = undefined;
|
|
2123
2186
|
batchQueue = undefined;
|
|
2124
2187
|
options.endChain();
|
|
2125
2188
|
}
|
|
@@ -2489,7 +2552,11 @@ function collectEffects(obj, evolution, effects, objectWatchers, ...keyChains) {
|
|
|
2489
2552
|
options.skipRunningEffect(effect, runningChain);
|
|
2490
2553
|
continue;
|
|
2491
2554
|
}
|
|
2492
|
-
effects.
|
|
2555
|
+
if (!effects.has(effect)) {
|
|
2556
|
+
effects.add(effect);
|
|
2557
|
+
if (!hasBatched(effect))
|
|
2558
|
+
recordActivation(effect, obj, evolution, key);
|
|
2559
|
+
}
|
|
2493
2560
|
const trackers = effectTrackers.get(effect);
|
|
2494
2561
|
recordTriggerLink(sourceEffect, effect, obj, key, evolution);
|
|
2495
2562
|
if (trackers) {
|
|
@@ -2557,6 +2624,7 @@ function touchedOpaque(obj, evolution, prop) {
|
|
|
2557
2624
|
continue;
|
|
2558
2625
|
}
|
|
2559
2626
|
effects.add(effect);
|
|
2627
|
+
recordActivation(effect, obj, evolution, prop);
|
|
2560
2628
|
const trackers = effectTrackers.get(effect);
|
|
2561
2629
|
recordTriggerLink(sourceEffect, effect, obj, prop, evolution);
|
|
2562
2630
|
if (trackers) {
|
|
@@ -3345,7 +3413,6 @@ function* makeReactiveEntriesIterator(iterator) {
|
|
|
3345
3413
|
const native$2 = Symbol('native');
|
|
3346
3414
|
const isArray = Array.isArray;
|
|
3347
3415
|
Array.isArray = ((value) => isArray(value) ||
|
|
3348
|
-
// biome-ignore lint/suspicious/useIsArray: We are defining it
|
|
3349
3416
|
(value &&
|
|
3350
3417
|
typeof value === 'object' &&
|
|
3351
3418
|
prototypeForwarding in value &&
|
|
@@ -4241,6 +4308,17 @@ function register(keyFn, initial) {
|
|
|
4241
4308
|
return new RegisterClass(keyFn, initial);
|
|
4242
4309
|
}
|
|
4243
4310
|
|
|
4311
|
+
/**
|
|
4312
|
+
* Maps projection effects (item effects) to their projection context
|
|
4313
|
+
*/
|
|
4314
|
+
const effectProjectionMetadata = new WeakMap();
|
|
4315
|
+
/**
|
|
4316
|
+
* Returns the projection context of the currently running effect, if any.
|
|
4317
|
+
*/
|
|
4318
|
+
function getActiveProjection() {
|
|
4319
|
+
const active = getActiveEffect();
|
|
4320
|
+
return active ? effectProjectionMetadata.get(active) : undefined;
|
|
4321
|
+
}
|
|
4244
4322
|
function defineAccessValue(access) {
|
|
4245
4323
|
Object.defineProperty(access, 'value', {
|
|
4246
4324
|
get: access.get,
|
|
@@ -4249,7 +4327,15 @@ function defineAccessValue(access) {
|
|
|
4249
4327
|
enumerable: true,
|
|
4250
4328
|
});
|
|
4251
4329
|
}
|
|
4252
|
-
function makeCleanup(target, effectMap, onDispose) {
|
|
4330
|
+
function makeCleanup(target, effectMap, onDispose, metadata) {
|
|
4331
|
+
if (metadata) {
|
|
4332
|
+
Object.defineProperty(target, projectionInfo, {
|
|
4333
|
+
value: metadata,
|
|
4334
|
+
writable: false,
|
|
4335
|
+
enumerable: false,
|
|
4336
|
+
configurable: true,
|
|
4337
|
+
});
|
|
4338
|
+
}
|
|
4253
4339
|
return cleanedBy(target, () => {
|
|
4254
4340
|
onDispose();
|
|
4255
4341
|
for (const stop of effectMap.values())
|
|
@@ -4272,6 +4358,8 @@ function projectArray(source, apply) {
|
|
|
4272
4358
|
Reflect.deleteProperty(target, index);
|
|
4273
4359
|
}
|
|
4274
4360
|
}
|
|
4361
|
+
const parent = getActiveProjection();
|
|
4362
|
+
const depth = parent ? parent.depth + 1 : 0;
|
|
4275
4363
|
const cleanupLength = effect(function projectArrayLengthEffect({ ascend }) {
|
|
4276
4364
|
const length = observedSource.length;
|
|
4277
4365
|
normalizeTargetLength(length);
|
|
@@ -4294,6 +4382,14 @@ function projectArray(source, apply) {
|
|
|
4294
4382
|
const produced = apply(accessBase, target);
|
|
4295
4383
|
target[index] = produced;
|
|
4296
4384
|
});
|
|
4385
|
+
setEffectName(stop, `project[${depth}]:${index}`);
|
|
4386
|
+
effectProjectionMetadata.set(stop, {
|
|
4387
|
+
source: observedSource,
|
|
4388
|
+
key: index,
|
|
4389
|
+
target,
|
|
4390
|
+
depth,
|
|
4391
|
+
parent,
|
|
4392
|
+
});
|
|
4297
4393
|
indexEffects.set(i, stop);
|
|
4298
4394
|
});
|
|
4299
4395
|
}
|
|
@@ -4301,7 +4397,13 @@ function projectArray(source, apply) {
|
|
|
4301
4397
|
if (index >= length)
|
|
4302
4398
|
disposeIndex(index);
|
|
4303
4399
|
});
|
|
4304
|
-
return makeCleanup(target, indexEffects, () => cleanupLength()
|
|
4400
|
+
return makeCleanup(target, indexEffects, () => cleanupLength(), {
|
|
4401
|
+
source: observedSource,
|
|
4402
|
+
target,
|
|
4403
|
+
apply,
|
|
4404
|
+
depth,
|
|
4405
|
+
parent,
|
|
4406
|
+
});
|
|
4305
4407
|
}
|
|
4306
4408
|
function projectRegister(source, apply) {
|
|
4307
4409
|
const observedSource = reactive(source);
|
|
@@ -4316,6 +4418,8 @@ function projectRegister(source, apply) {
|
|
|
4316
4418
|
target.delete(key);
|
|
4317
4419
|
}
|
|
4318
4420
|
}
|
|
4421
|
+
const parent = getActiveProjection();
|
|
4422
|
+
const depth = parent ? parent.depth + 1 : 0;
|
|
4319
4423
|
const cleanupKeys = effect(function projectRegisterEffect({ ascend }) {
|
|
4320
4424
|
const keys = new Set();
|
|
4321
4425
|
for (const key of observedSource.mapKeys())
|
|
@@ -4340,6 +4444,14 @@ function projectRegister(source, apply) {
|
|
|
4340
4444
|
const produced = apply(accessBase, target);
|
|
4341
4445
|
target.set(key, produced);
|
|
4342
4446
|
});
|
|
4447
|
+
setEffectName(stop, `project[${depth}]:${String(key)}`);
|
|
4448
|
+
effectProjectionMetadata.set(stop, {
|
|
4449
|
+
source: observedSource,
|
|
4450
|
+
key,
|
|
4451
|
+
target,
|
|
4452
|
+
depth,
|
|
4453
|
+
parent,
|
|
4454
|
+
});
|
|
4343
4455
|
keyEffects.set(key, stop);
|
|
4344
4456
|
});
|
|
4345
4457
|
}
|
|
@@ -4347,7 +4459,13 @@ function projectRegister(source, apply) {
|
|
|
4347
4459
|
if (!keys.has(key))
|
|
4348
4460
|
disposeKey(key);
|
|
4349
4461
|
});
|
|
4350
|
-
return makeCleanup(target, keyEffects, () => cleanupKeys()
|
|
4462
|
+
return makeCleanup(target, keyEffects, () => cleanupKeys(), {
|
|
4463
|
+
source: observedSource,
|
|
4464
|
+
target,
|
|
4465
|
+
apply,
|
|
4466
|
+
depth,
|
|
4467
|
+
parent,
|
|
4468
|
+
});
|
|
4351
4469
|
}
|
|
4352
4470
|
function projectRecord(source, apply) {
|
|
4353
4471
|
const observedSource = reactive(source);
|
|
@@ -4361,6 +4479,8 @@ function projectRecord(source, apply) {
|
|
|
4361
4479
|
Reflect.deleteProperty(target, key);
|
|
4362
4480
|
}
|
|
4363
4481
|
}
|
|
4482
|
+
const parent = getActiveProjection();
|
|
4483
|
+
const depth = parent ? parent.depth + 1 : 0;
|
|
4364
4484
|
const cleanupKeys = effect(function projectRecordEffect({ ascend }) {
|
|
4365
4485
|
const keys = new Set();
|
|
4366
4486
|
for (const key in observedSource)
|
|
@@ -4386,6 +4506,14 @@ function projectRecord(source, apply) {
|
|
|
4386
4506
|
const produced = apply(accessBase, target);
|
|
4387
4507
|
target[sourceKey] = produced;
|
|
4388
4508
|
});
|
|
4509
|
+
setEffectName(stop, `project[${depth}]:${String(key)}`);
|
|
4510
|
+
effectProjectionMetadata.set(stop, {
|
|
4511
|
+
source: observedSource,
|
|
4512
|
+
key,
|
|
4513
|
+
target,
|
|
4514
|
+
depth,
|
|
4515
|
+
parent,
|
|
4516
|
+
});
|
|
4389
4517
|
keyEffects.set(key, stop);
|
|
4390
4518
|
});
|
|
4391
4519
|
}
|
|
@@ -4393,7 +4521,13 @@ function projectRecord(source, apply) {
|
|
|
4393
4521
|
if (!keys.has(key))
|
|
4394
4522
|
disposeKey(key);
|
|
4395
4523
|
});
|
|
4396
|
-
return makeCleanup(target, keyEffects, () => cleanupKeys()
|
|
4524
|
+
return makeCleanup(target, keyEffects, () => cleanupKeys(), {
|
|
4525
|
+
source: observedSource,
|
|
4526
|
+
target,
|
|
4527
|
+
apply,
|
|
4528
|
+
depth,
|
|
4529
|
+
parent,
|
|
4530
|
+
});
|
|
4397
4531
|
}
|
|
4398
4532
|
function projectMap(source, apply) {
|
|
4399
4533
|
const observedSource = reactive(source);
|
|
@@ -4408,6 +4542,8 @@ function projectMap(source, apply) {
|
|
|
4408
4542
|
target.delete(key);
|
|
4409
4543
|
}
|
|
4410
4544
|
}
|
|
4545
|
+
const parent = getActiveProjection();
|
|
4546
|
+
const depth = parent ? parent.depth + 1 : 0;
|
|
4411
4547
|
const cleanupKeys = effect(function projectMapEffect({ ascend }) {
|
|
4412
4548
|
const keys = new Set();
|
|
4413
4549
|
for (const key of observedSource.keys())
|
|
@@ -4432,6 +4568,14 @@ function projectMap(source, apply) {
|
|
|
4432
4568
|
const produced = apply(accessBase, target);
|
|
4433
4569
|
target.set(key, produced);
|
|
4434
4570
|
});
|
|
4571
|
+
setEffectName(stop, `project[${depth}]:${String(key)}`);
|
|
4572
|
+
effectProjectionMetadata.set(stop, {
|
|
4573
|
+
source: observedSource,
|
|
4574
|
+
key,
|
|
4575
|
+
target,
|
|
4576
|
+
depth,
|
|
4577
|
+
parent,
|
|
4578
|
+
});
|
|
4435
4579
|
keyEffects.set(key, stop);
|
|
4436
4580
|
});
|
|
4437
4581
|
}
|
|
@@ -4439,7 +4583,13 @@ function projectMap(source, apply) {
|
|
|
4439
4583
|
if (!keys.has(key))
|
|
4440
4584
|
disposeKey(key);
|
|
4441
4585
|
});
|
|
4442
|
-
return makeCleanup(target, keyEffects, () => cleanupKeys()
|
|
4586
|
+
return makeCleanup(target, keyEffects, () => cleanupKeys(), {
|
|
4587
|
+
source: observedSource,
|
|
4588
|
+
target,
|
|
4589
|
+
apply,
|
|
4590
|
+
depth,
|
|
4591
|
+
parent,
|
|
4592
|
+
});
|
|
4443
4593
|
}
|
|
4444
4594
|
function projectCore(source, apply) {
|
|
4445
4595
|
if (Array.isArray(source))
|
|
@@ -4587,7 +4737,7 @@ class ReactiveWeakMap {
|
|
|
4587
4737
|
Object.defineProperties(this, {
|
|
4588
4738
|
[native$1]: { value: original },
|
|
4589
4739
|
[prototypeForwarding]: { value: original },
|
|
4590
|
-
content: { value: Symbol('
|
|
4740
|
+
content: { value: Symbol('WeakMapContent') },
|
|
4591
4741
|
[Symbol.toStringTag]: { value: 'ReactiveWeakMap' },
|
|
4592
4742
|
});
|
|
4593
4743
|
}
|
|
@@ -4627,7 +4777,7 @@ class ReactiveMap {
|
|
|
4627
4777
|
Object.defineProperties(this, {
|
|
4628
4778
|
[native$1]: { value: original },
|
|
4629
4779
|
[prototypeForwarding]: { value: original },
|
|
4630
|
-
content: { value: Symbol('
|
|
4780
|
+
content: { value: Symbol('MapContent') },
|
|
4631
4781
|
[Symbol.toStringTag]: { value: 'ReactiveMap' },
|
|
4632
4782
|
});
|
|
4633
4783
|
}
|
|
@@ -4722,7 +4872,7 @@ class ReactiveWeakSet {
|
|
|
4722
4872
|
Object.defineProperties(this, {
|
|
4723
4873
|
[native]: { value: original },
|
|
4724
4874
|
[prototypeForwarding]: { value: original },
|
|
4725
|
-
content: { value: Symbol('
|
|
4875
|
+
content: { value: Symbol('WeakSetContent') },
|
|
4726
4876
|
[Symbol.toStringTag]: { value: 'ReactiveWeakSet' },
|
|
4727
4877
|
});
|
|
4728
4878
|
}
|
|
@@ -4757,7 +4907,7 @@ class ReactiveSet {
|
|
|
4757
4907
|
Object.defineProperties(this, {
|
|
4758
4908
|
[native]: { value: original },
|
|
4759
4909
|
[prototypeForwarding]: { value: original },
|
|
4760
|
-
content: { value: Symbol('
|
|
4910
|
+
content: { value: Symbol('SetContent') },
|
|
4761
4911
|
[Symbol.toStringTag]: { value: 'ReactiveSet' },
|
|
4762
4912
|
});
|
|
4763
4913
|
}
|
|
@@ -4853,5 +5003,5 @@ const profileInfo = {
|
|
|
4853
5003
|
nonReactiveObjects,
|
|
4854
5004
|
};
|
|
4855
5005
|
|
|
4856
|
-
export {
|
|
4857
|
-
//# sourceMappingURL=index-
|
|
5006
|
+
export { cleanup as A, derived as B, unreactive as C, watch as D, mapped as E, reduced as F, memoize as G, immutables as H, IterableWeakMap as I, isNonReactive as J, registerNativeReactivity as K, getActiveProjection as L, project as M, isReactive as N, ReactiveBase as O, reactive as P, unwrap as Q, ReadOnlyError as R, organize as S, organized as T, Register as U, register as V, options as W, ReactiveError as X, isZoneEnabled as Y, setZoneEnabled as Z, IterableWeakSet as a, touched1 as b, buildReactivityGraph as c, registerObjectForDebug as d, enableDevTools as e, setObjectName as f, getState as g, deepWatch as h, isDevtoolsEnabled as i, addBatchCleanup as j, atomic as k, batch as l, mixin as m, biDi as n, defer as o, profileInfo as p, effect as q, registerEffectForDebug as r, setEffectName as s, touched as t, getActivationLog as u, getActiveEffect as v, root as w, trackEffect as x, untracked as y, cleanedBy as z };
|
|
5007
|
+
//# sourceMappingURL=index-qiWwozOc.esm.js.map
|