mutts 1.0.0 → 1.0.1

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.
Files changed (53) hide show
  1. package/dist/chunks/{decorator-BXsign4Z.js → decorator-8qjFb7dw.js} +2 -2
  2. package/dist/chunks/decorator-8qjFb7dw.js.map +1 -0
  3. package/dist/chunks/{decorator-CPbZNnsX.esm.js → decorator-AbRkXM5O.esm.js} +2 -2
  4. package/dist/chunks/decorator-AbRkXM5O.esm.js.map +1 -0
  5. package/dist/decorator.d.ts +1 -1
  6. package/dist/decorator.esm.js +1 -1
  7. package/dist/decorator.js +1 -1
  8. package/dist/destroyable.esm.js +1 -1
  9. package/dist/destroyable.js +1 -1
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.esm.js +2 -2
  12. package/dist/index.js +2 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/mutts.umd.js +1 -1
  15. package/dist/mutts.umd.js.map +1 -1
  16. package/dist/mutts.umd.min.js +1 -1
  17. package/dist/mutts.umd.min.js.map +1 -1
  18. package/dist/reactive.d.ts +4 -3
  19. package/dist/reactive.esm.js +61 -57
  20. package/dist/reactive.esm.js.map +1 -1
  21. package/dist/reactive.js +61 -56
  22. package/dist/reactive.js.map +1 -1
  23. package/dist/std-decorators.esm.js +1 -1
  24. package/dist/std-decorators.js +1 -1
  25. package/docs/reactive.md +616 -0
  26. package/package.json +1 -2
  27. package/dist/chunks/decorator-BXsign4Z.js.map +0 -1
  28. package/dist/chunks/decorator-CPbZNnsX.esm.js.map +0 -1
  29. package/src/decorator.test.ts +0 -495
  30. package/src/decorator.ts +0 -205
  31. package/src/destroyable.test.ts +0 -155
  32. package/src/destroyable.ts +0 -158
  33. package/src/eventful.test.ts +0 -380
  34. package/src/eventful.ts +0 -69
  35. package/src/index.ts +0 -7
  36. package/src/indexable.test.ts +0 -388
  37. package/src/indexable.ts +0 -124
  38. package/src/promiseChain.test.ts +0 -201
  39. package/src/promiseChain.ts +0 -99
  40. package/src/reactive/array.test.ts +0 -923
  41. package/src/reactive/array.ts +0 -352
  42. package/src/reactive/core.test.ts +0 -1663
  43. package/src/reactive/core.ts +0 -866
  44. package/src/reactive/index.ts +0 -28
  45. package/src/reactive/interface.test.ts +0 -1477
  46. package/src/reactive/interface.ts +0 -231
  47. package/src/reactive/map.test.ts +0 -866
  48. package/src/reactive/map.ts +0 -162
  49. package/src/reactive/set.test.ts +0 -289
  50. package/src/reactive/set.ts +0 -142
  51. package/src/std-decorators.test.ts +0 -679
  52. package/src/std-decorators.ts +0 -182
  53. package/src/utils.ts +0 -52
package/dist/reactive.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var decorator = require('./chunks/decorator-BXsign4Z.js');
3
+ var decorator = require('./chunks/decorator-8qjFb7dw.js');
4
4
  var indexable = require('./indexable.js');
5
5
 
6
6
  // biome-ignore-all lint/suspicious/noConfusingVoidType: Type 'void' is not assignable to type 'ScopedCallback | undefined'.
@@ -21,6 +21,7 @@ const deepWatchers = new WeakMap();
21
21
  const effectToDeepWatchedObjects = new WeakMap();
22
22
  // Track objects that should never be reactive and cannot be modified
23
23
  const nonReactiveObjects = new WeakSet();
24
+ const absent = Symbol('absent');
24
25
  /**
25
26
  * Converts an iterator to a generator that yields reactive values
26
27
  */
@@ -142,7 +143,7 @@ function raiseDeps(objectWatchers, ...keyChains) {
142
143
  const deps = objectWatchers.get(key);
143
144
  if (deps)
144
145
  for (const effect of Array.from(deps))
145
- hasEffect(effect);
146
+ atomicEffect(effect);
146
147
  }
147
148
  }
148
149
  function touched1(obj, evolution, prop) {
@@ -182,7 +183,6 @@ function getState(obj) {
182
183
  return state;
183
184
  }
184
185
  function dependant(obj, prop = allProps) {
185
- // TODO: avoid depending on property get?
186
186
  obj = unwrap(obj);
187
187
  if (activeEffect && (typeof prop !== 'symbol' || prop === allProps)) {
188
188
  let objectWatchers = watchers.get(obj);
@@ -214,29 +214,53 @@ let parentEffect;
214
214
  let batchedEffects;
215
215
  // Track which sub-effects have been executed to prevent infinite loops
216
216
  // These are all the effects triggered under `activeEffect` and all their sub-effects
217
- function hasEffect(effect) {
217
+ function atomicEffect(effect, immediate) {
218
218
  const root = getRoot(effect);
219
219
  options?.chain(getRoot(effect), getRoot(activeEffect));
220
- if (batchedEffects)
220
+ if (batchedEffects) {
221
221
  batchedEffects.set(root, effect);
222
+ if (immediate)
223
+ try {
224
+ return effect();
225
+ }
226
+ finally {
227
+ batchedEffects.delete(root);
228
+ }
229
+ }
222
230
  else {
223
231
  const runEffects = [];
224
232
  batchedEffects = new Map([[root, effect]]);
233
+ const firstReturn = {};
225
234
  try {
226
235
  while (batchedEffects.size) {
227
236
  if (runEffects.length > options.maxEffectChain)
228
237
  throw new ReactiveError('[reactive] Max effect chain reached');
229
238
  const [root, effect] = batchedEffects.entries().next().value;
230
239
  runEffects.push(root);
231
- effect();
240
+ const rv = effect();
241
+ if (!('value' in firstReturn))
242
+ firstReturn.value = rv;
232
243
  batchedEffects.delete(root);
233
244
  }
245
+ return firstReturn.value;
234
246
  }
235
247
  finally {
236
248
  batchedEffects = undefined;
237
249
  }
238
250
  }
239
251
  }
252
+ const atomic = decorator.decorator({
253
+ method(original) {
254
+ return function (...args) {
255
+ return atomicEffect(markWithRoot(() => original.apply(this, args), original), 'immediate');
256
+ };
257
+ },
258
+ default(original) {
259
+ return function (...args) {
260
+ return atomicEffect(markWithRoot(() => original.apply(this, args), original), 'immediate');
261
+ };
262
+ },
263
+ });
240
264
  function withEffect(effect, fn, keepParent) {
241
265
  if (getRoot(effect) === getRoot(activeEffect))
242
266
  return fn();
@@ -311,7 +335,7 @@ function bubbleUpChange(changedObject, evolution) {
311
335
  const parentDeepWatchers = deepWatchers.get(parent);
312
336
  if (parentDeepWatchers)
313
337
  for (const watcher of parentDeepWatchers)
314
- hasEffect(watcher);
338
+ atomicEffect(watcher);
315
339
  // Continue bubbling up
316
340
  bubbleUpChange(parent);
317
341
  }
@@ -340,9 +364,8 @@ const reactiveHandlers = {
340
364
  // Check if this property is marked as unreactive
341
365
  if (obj[unreactiveProperties]?.has(prop) || typeof prop === 'symbol')
342
366
  return Reflect.get(obj, prop, receiver);
343
- const absent = !(prop in obj);
344
367
  // Depend if...
345
- if (!options.instanceMembers || Object.hasOwn(receiver, prop) || absent)
368
+ if (!options.instanceMembers || Object.hasOwn(receiver, prop) || !Reflect.has(receiver, prop))
346
369
  dependant(obj, prop);
347
370
  const value = Reflect.get(obj, prop, receiver);
348
371
  if (typeof value === 'object' && value !== null) {
@@ -369,13 +392,12 @@ const reactiveHandlers = {
369
392
  obj[prop] = newValue;
370
393
  return true;
371
394
  }
372
- const oldVal = obj[prop];
373
- const oldPresent = prop in obj;
395
+ const oldVal = Reflect.has(receiver, prop) ? Reflect.get(obj, prop, receiver) : absent;
374
396
  track1(obj, prop, oldVal, newValue);
375
397
  if (oldVal !== newValue) {
376
398
  Reflect.set(obj, prop, newValue, receiver);
377
399
  // try to find a "generic" way to express that
378
- touched1(obj, { type: oldPresent ? 'set' : 'add', prop }, prop);
400
+ touched1(obj, { type: oldVal !== absent ? 'set' : 'add', prop }, prop);
379
401
  }
380
402
  return true;
381
403
  },
@@ -519,25 +541,7 @@ function effect(fn, ...args) {
519
541
  }
520
542
  // Mark the runEffect callback with the original function as its root
521
543
  markWithRoot(runEffect, fn);
522
- // Run the effect immediately
523
- if (!batchedEffects) {
524
- hasEffect(runEffect);
525
- }
526
- else {
527
- const oldBatchedEffects = batchedEffects;
528
- try {
529
- // Simulate a hasEffect who batches, but do not execute the batch, give it back to the parent batch,
530
- // Only the immediate effect has to be executed, the sub-effects will be executed by the parent batch
531
- batchedEffects = new Map([[fn, runEffect]]);
532
- runEffect();
533
- batchedEffects.delete(fn);
534
- for (const [root, effect] of batchedEffects)
535
- oldBatchedEffects.set(root, effect);
536
- }
537
- finally {
538
- batchedEffects = oldBatchedEffects;
539
- }
540
- }
544
+ atomicEffect(runEffect, 'immediate');
541
545
  const mainCleanup = () => {
542
546
  if (effectStopped)
543
547
  return;
@@ -558,7 +562,6 @@ function effect(fn, ...args) {
558
562
  fr.register(callIfCollected, mainCleanup, callIfCollected);
559
563
  return callIfCollected;
560
564
  }
561
- // TODO: parentEffect = last non-undefined activeEffect
562
565
  // Register this effect to be cleaned up with the parent effect
563
566
  let children = effectChildren.get(parentEffect);
564
567
  if (!children) {
@@ -788,28 +791,30 @@ function computedFunction(getter) {
788
791
  dependant(computedCache, key);
789
792
  if (computedCache.has(key))
790
793
  return computedCache.get(key);
791
- withEffect(undefined, () => {
792
- const stop = effect(markWithRoot((dep) => {
793
- const oldCI = computedInvalidations;
794
- if (computedCache.has(key)) {
795
- // This should *not* be called in the cleanup chain, as its effects would be lost and cleaned-up
796
- for (const cb of invalidations)
797
- cb();
798
- invalidations = [];
799
- computedCache.delete(key);
800
- touched1(computedCache, { type: 'set', prop: key }, key);
801
- stop();
794
+ let stopped = false;
795
+ const stop = effect(markWithRoot((dep) => {
796
+ if (stopped)
797
+ return;
798
+ const oldCI = computedInvalidations;
799
+ if (computedCache.has(key)) {
800
+ // This should *not* be called in the cleanup chain, as its effects would be lost and cleaned-up
801
+ for (const cb of invalidations)
802
+ cb();
803
+ invalidations = [];
804
+ computedCache.delete(key);
805
+ touched1(computedCache, { type: 'invalidate', prop: key }, key);
806
+ stop();
807
+ stopped = true;
808
+ }
809
+ else
810
+ try {
811
+ computedInvalidations = invalidations;
812
+ computedCache.set(key, getter(dep));
802
813
  }
803
- else
804
- try {
805
- computedInvalidations = invalidations;
806
- computedCache.set(key, getter(dep));
807
- }
808
- finally {
809
- computedInvalidations = oldCI;
810
- }
811
- }, getter));
812
- });
814
+ finally {
815
+ computedInvalidations = oldCI;
816
+ }
817
+ }, getter));
813
818
  return computedCache.get(key);
814
819
  }
815
820
  /**
@@ -1199,7 +1204,8 @@ class ReactiveArray extends indexable.Indexable(ReactiveBaseArray, {
1199
1204
  dependant(this);
1200
1205
  this[native$2].forEach(callbackfn, thisArg);
1201
1206
  }
1202
- // TODO: re-implement for fun dependencies? (eg - every only check the first ones until it find some)
1207
+ // TODO: re-implement for fun dependencies? (eg - every only check the first ones until it find some),
1208
+ // no need to make it dependant on indexes after the found one
1203
1209
  every(callbackfn, thisArg) {
1204
1210
  dependant(this);
1205
1211
  return this[native$2].every(callbackfn, thisArg);
@@ -1211,7 +1217,6 @@ class ReactiveArray extends indexable.Indexable(ReactiveBaseArray, {
1211
1217
  }
1212
1218
 
1213
1219
  const native$1 = Symbol('native');
1214
- // TODO: [prototypeForwarding]
1215
1220
  class ReactiveWeakMap {
1216
1221
  constructor(original) {
1217
1222
  Object.defineProperties(this, {
@@ -1334,7 +1339,6 @@ class ReactiveMap {
1334
1339
  }
1335
1340
 
1336
1341
  const native = Symbol('native');
1337
- // TODO: [prototypeForwarding]
1338
1342
  class ReactiveWeakSet {
1339
1343
  constructor(original) {
1340
1344
  Object.defineProperties(this, {
@@ -1455,6 +1459,7 @@ registerNativeReactivity(Array, ReactiveArray);
1455
1459
 
1456
1460
  exports.ReactiveBase = ReactiveBase;
1457
1461
  exports.ReactiveError = ReactiveError;
1462
+ exports.atomic = atomic;
1458
1463
  exports.computed = computed;
1459
1464
  exports.effect = effect;
1460
1465
  exports.getState = getState;