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
@@ -1,9 +1,9 @@
1
- import { LegacyClassDecorator, ModernClassDecorator, LegacyPropertyDecorator, ModernGetterDecorator, ModernAccessorDecorator, GenericClassDecorator } from './decorator.js';
1
+ import { LegacyPropertyDecorator, ModernMethodDecorator, LegacyClassDecorator, ModernClassDecorator, ModernGetterDecorator, ModernAccessorDecorator, GenericClassDecorator } from './decorator.js';
2
2
 
3
3
  type DependencyFunction = <T>(cb: () => T) => T;
4
4
  type ScopedCallback = () => void;
5
5
  type PropEvolution = {
6
- type: 'set' | 'del' | 'add';
6
+ type: 'set' | 'del' | 'add' | 'invalidate';
7
7
  prop: any;
8
8
  };
9
9
  type BunchEvolution = {
@@ -60,6 +60,7 @@ declare const options: {
60
60
  warn: (...args: any[]) => void;
61
61
  };
62
62
  declare function getState(obj: any): State;
63
+ declare const atomic: LegacyPropertyDecorator<any> & ModernMethodDecorator<any> & (<Args extends any[], Return>(original: (...args: Args) => Return) => (...args: Args) => Return);
63
64
  declare class ReactiveBase {
64
65
  constructor();
65
66
  }
@@ -110,5 +111,5 @@ declare function unreactiveApplication<T extends object>(...args: (keyof T)[]):
110
111
  declare function unreactiveApplication<T extends object>(obj: T): T;
111
112
  declare const unreactive: LegacyClassDecorator<new (...args: any[]) => any> & ModernClassDecorator<new (...args: any[]) => any> & typeof unreactiveApplication;
112
113
 
113
- export { ReactiveBase, ReactiveError, computed, effect, getState, immutables, invalidateComputed, isNonReactive, isReactive, profileInfo, reactive, options as reactiveOptions, unreactive, untracked, unwrap, watch };
114
+ export { ReactiveBase, ReactiveError, atomic, computed, effect, getState, immutables, invalidateComputed, isNonReactive, isReactive, profileInfo, reactive, options as reactiveOptions, unreactive, untracked, unwrap, watch };
114
115
  export type { ScopedCallback };
@@ -1,4 +1,4 @@
1
- import { d as decorator, r as renamed } from './chunks/decorator-CPbZNnsX.esm.js';
1
+ import { d as decorator, r as renamed } from './chunks/decorator-AbRkXM5O.esm.js';
2
2
  import { Indexable } from './indexable.esm.js';
3
3
 
4
4
  // biome-ignore-all lint/suspicious/noConfusingVoidType: Type 'void' is not assignable to type 'ScopedCallback | undefined'.
@@ -19,6 +19,7 @@ const deepWatchers = new WeakMap();
19
19
  const effectToDeepWatchedObjects = new WeakMap();
20
20
  // Track objects that should never be reactive and cannot be modified
21
21
  const nonReactiveObjects = new WeakSet();
22
+ const absent = Symbol('absent');
22
23
  /**
23
24
  * Converts an iterator to a generator that yields reactive values
24
25
  */
@@ -140,7 +141,7 @@ function raiseDeps(objectWatchers, ...keyChains) {
140
141
  const deps = objectWatchers.get(key);
141
142
  if (deps)
142
143
  for (const effect of Array.from(deps))
143
- hasEffect(effect);
144
+ atomicEffect(effect);
144
145
  }
145
146
  }
146
147
  function touched1(obj, evolution, prop) {
@@ -180,7 +181,6 @@ function getState(obj) {
180
181
  return state;
181
182
  }
182
183
  function dependant(obj, prop = allProps) {
183
- // TODO: avoid depending on property get?
184
184
  obj = unwrap(obj);
185
185
  if (activeEffect && (typeof prop !== 'symbol' || prop === allProps)) {
186
186
  let objectWatchers = watchers.get(obj);
@@ -212,29 +212,53 @@ let parentEffect;
212
212
  let batchedEffects;
213
213
  // Track which sub-effects have been executed to prevent infinite loops
214
214
  // These are all the effects triggered under `activeEffect` and all their sub-effects
215
- function hasEffect(effect) {
215
+ function atomicEffect(effect, immediate) {
216
216
  const root = getRoot(effect);
217
217
  options?.chain(getRoot(effect), getRoot(activeEffect));
218
- if (batchedEffects)
218
+ if (batchedEffects) {
219
219
  batchedEffects.set(root, effect);
220
+ if (immediate)
221
+ try {
222
+ return effect();
223
+ }
224
+ finally {
225
+ batchedEffects.delete(root);
226
+ }
227
+ }
220
228
  else {
221
229
  const runEffects = [];
222
230
  batchedEffects = new Map([[root, effect]]);
231
+ const firstReturn = {};
223
232
  try {
224
233
  while (batchedEffects.size) {
225
234
  if (runEffects.length > options.maxEffectChain)
226
235
  throw new ReactiveError('[reactive] Max effect chain reached');
227
236
  const [root, effect] = batchedEffects.entries().next().value;
228
237
  runEffects.push(root);
229
- effect();
238
+ const rv = effect();
239
+ if (!('value' in firstReturn))
240
+ firstReturn.value = rv;
230
241
  batchedEffects.delete(root);
231
242
  }
243
+ return firstReturn.value;
232
244
  }
233
245
  finally {
234
246
  batchedEffects = undefined;
235
247
  }
236
248
  }
237
249
  }
250
+ const atomic = decorator({
251
+ method(original) {
252
+ return function (...args) {
253
+ return atomicEffect(markWithRoot(() => original.apply(this, args), original), 'immediate');
254
+ };
255
+ },
256
+ default(original) {
257
+ return function (...args) {
258
+ return atomicEffect(markWithRoot(() => original.apply(this, args), original), 'immediate');
259
+ };
260
+ },
261
+ });
238
262
  function withEffect(effect, fn, keepParent) {
239
263
  if (getRoot(effect) === getRoot(activeEffect))
240
264
  return fn();
@@ -309,7 +333,7 @@ function bubbleUpChange(changedObject, evolution) {
309
333
  const parentDeepWatchers = deepWatchers.get(parent);
310
334
  if (parentDeepWatchers)
311
335
  for (const watcher of parentDeepWatchers)
312
- hasEffect(watcher);
336
+ atomicEffect(watcher);
313
337
  // Continue bubbling up
314
338
  bubbleUpChange(parent);
315
339
  }
@@ -338,9 +362,8 @@ const reactiveHandlers = {
338
362
  // Check if this property is marked as unreactive
339
363
  if (obj[unreactiveProperties]?.has(prop) || typeof prop === 'symbol')
340
364
  return Reflect.get(obj, prop, receiver);
341
- const absent = !(prop in obj);
342
365
  // Depend if...
343
- if (!options.instanceMembers || Object.hasOwn(receiver, prop) || absent)
366
+ if (!options.instanceMembers || Object.hasOwn(receiver, prop) || !Reflect.has(receiver, prop))
344
367
  dependant(obj, prop);
345
368
  const value = Reflect.get(obj, prop, receiver);
346
369
  if (typeof value === 'object' && value !== null) {
@@ -367,13 +390,12 @@ const reactiveHandlers = {
367
390
  obj[prop] = newValue;
368
391
  return true;
369
392
  }
370
- const oldVal = obj[prop];
371
- const oldPresent = prop in obj;
393
+ const oldVal = Reflect.has(receiver, prop) ? Reflect.get(obj, prop, receiver) : absent;
372
394
  track1(obj, prop, oldVal, newValue);
373
395
  if (oldVal !== newValue) {
374
396
  Reflect.set(obj, prop, newValue, receiver);
375
397
  // try to find a "generic" way to express that
376
- touched1(obj, { type: oldPresent ? 'set' : 'add', prop }, prop);
398
+ touched1(obj, { type: oldVal !== absent ? 'set' : 'add', prop }, prop);
377
399
  }
378
400
  return true;
379
401
  },
@@ -517,25 +539,7 @@ function effect(fn, ...args) {
517
539
  }
518
540
  // Mark the runEffect callback with the original function as its root
519
541
  markWithRoot(runEffect, fn);
520
- // Run the effect immediately
521
- if (!batchedEffects) {
522
- hasEffect(runEffect);
523
- }
524
- else {
525
- const oldBatchedEffects = batchedEffects;
526
- try {
527
- // Simulate a hasEffect who batches, but do not execute the batch, give it back to the parent batch,
528
- // Only the immediate effect has to be executed, the sub-effects will be executed by the parent batch
529
- batchedEffects = new Map([[fn, runEffect]]);
530
- runEffect();
531
- batchedEffects.delete(fn);
532
- for (const [root, effect] of batchedEffects)
533
- oldBatchedEffects.set(root, effect);
534
- }
535
- finally {
536
- batchedEffects = oldBatchedEffects;
537
- }
538
- }
542
+ atomicEffect(runEffect, 'immediate');
539
543
  const mainCleanup = () => {
540
544
  if (effectStopped)
541
545
  return;
@@ -556,7 +560,6 @@ function effect(fn, ...args) {
556
560
  fr.register(callIfCollected, mainCleanup, callIfCollected);
557
561
  return callIfCollected;
558
562
  }
559
- // TODO: parentEffect = last non-undefined activeEffect
560
563
  // Register this effect to be cleaned up with the parent effect
561
564
  let children = effectChildren.get(parentEffect);
562
565
  if (!children) {
@@ -786,28 +789,30 @@ function computedFunction(getter) {
786
789
  dependant(computedCache, key);
787
790
  if (computedCache.has(key))
788
791
  return computedCache.get(key);
789
- withEffect(undefined, () => {
790
- const stop = effect(markWithRoot((dep) => {
791
- const oldCI = computedInvalidations;
792
- if (computedCache.has(key)) {
793
- // This should *not* be called in the cleanup chain, as its effects would be lost and cleaned-up
794
- for (const cb of invalidations)
795
- cb();
796
- invalidations = [];
797
- computedCache.delete(key);
798
- touched1(computedCache, { type: 'set', prop: key }, key);
799
- stop();
792
+ let stopped = false;
793
+ const stop = effect(markWithRoot((dep) => {
794
+ if (stopped)
795
+ return;
796
+ const oldCI = computedInvalidations;
797
+ if (computedCache.has(key)) {
798
+ // This should *not* be called in the cleanup chain, as its effects would be lost and cleaned-up
799
+ for (const cb of invalidations)
800
+ cb();
801
+ invalidations = [];
802
+ computedCache.delete(key);
803
+ touched1(computedCache, { type: 'invalidate', prop: key }, key);
804
+ stop();
805
+ stopped = true;
806
+ }
807
+ else
808
+ try {
809
+ computedInvalidations = invalidations;
810
+ computedCache.set(key, getter(dep));
800
811
  }
801
- else
802
- try {
803
- computedInvalidations = invalidations;
804
- computedCache.set(key, getter(dep));
805
- }
806
- finally {
807
- computedInvalidations = oldCI;
808
- }
809
- }, getter));
810
- });
812
+ finally {
813
+ computedInvalidations = oldCI;
814
+ }
815
+ }, getter));
811
816
  return computedCache.get(key);
812
817
  }
813
818
  /**
@@ -1197,7 +1202,8 @@ class ReactiveArray extends Indexable(ReactiveBaseArray, {
1197
1202
  dependant(this);
1198
1203
  this[native$2].forEach(callbackfn, thisArg);
1199
1204
  }
1200
- // TODO: re-implement for fun dependencies? (eg - every only check the first ones until it find some)
1205
+ // TODO: re-implement for fun dependencies? (eg - every only check the first ones until it find some),
1206
+ // no need to make it dependant on indexes after the found one
1201
1207
  every(callbackfn, thisArg) {
1202
1208
  dependant(this);
1203
1209
  return this[native$2].every(callbackfn, thisArg);
@@ -1209,7 +1215,6 @@ class ReactiveArray extends Indexable(ReactiveBaseArray, {
1209
1215
  }
1210
1216
 
1211
1217
  const native$1 = Symbol('native');
1212
- // TODO: [prototypeForwarding]
1213
1218
  class ReactiveWeakMap {
1214
1219
  constructor(original) {
1215
1220
  Object.defineProperties(this, {
@@ -1332,7 +1337,6 @@ class ReactiveMap {
1332
1337
  }
1333
1338
 
1334
1339
  const native = Symbol('native');
1335
- // TODO: [prototypeForwarding]
1336
1340
  class ReactiveWeakSet {
1337
1341
  constructor(original) {
1338
1342
  Object.defineProperties(this, {
@@ -1451,5 +1455,5 @@ registerNativeReactivity(WeakSet, ReactiveWeakSet);
1451
1455
  registerNativeReactivity(Set, ReactiveSet);
1452
1456
  registerNativeReactivity(Array, ReactiveArray);
1453
1457
 
1454
- export { ReactiveBase, ReactiveError, computed, effect, getState, immutables, invalidateComputed, isNonReactive, isReactive, profileInfo, reactive, options as reactiveOptions, unreactive, untracked, unwrap, watch };
1458
+ export { ReactiveBase, ReactiveError, atomic, computed, effect, getState, immutables, invalidateComputed, isNonReactive, isReactive, profileInfo, reactive, options as reactiveOptions, unreactive, untracked, unwrap, watch };
1455
1459
  //# sourceMappingURL=reactive.esm.js.map