mutts 1.0.13 → 1.0.14

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 (54) hide show
  1. package/BROWSER_ASYNC_POLYFILL.md +79 -0
  2. package/README.md +2 -2
  3. package/dist/browser.cjs +145 -26
  4. package/dist/browser.cjs.map +1 -1
  5. package/dist/browser.d.ts +42 -9
  6. package/dist/browser.dev.cjs +12 -2
  7. package/dist/browser.dev.cjs.map +1 -1
  8. package/dist/browser.dev.d.ts +2 -2
  9. package/dist/browser.dev.esm.js +2 -2
  10. package/dist/browser.esm.js +137 -28
  11. package/dist/browser.esm.js.map +1 -1
  12. package/dist/chunks/{index-CAdnMJev.cjs → index-BnTNC9eC.cjs} +158 -90
  13. package/dist/chunks/index-BnTNC9eC.cjs.map +1 -0
  14. package/dist/chunks/{index-XsYTUhHx.esm.js → index-CAWVZL7P.esm.js} +156 -88
  15. package/dist/chunks/index-CAWVZL7P.esm.js.map +1 -0
  16. package/dist/chunks/node-Df_5r_WA.cjs +187 -0
  17. package/dist/chunks/node-Df_5r_WA.cjs.map +1 -0
  18. package/dist/chunks/node-DuIduHw3.esm.js +185 -0
  19. package/dist/chunks/node-DuIduHw3.esm.js.map +1 -0
  20. package/dist/chunks/{proxy-BtmPFjSr.esm.js → proxy-C2lnvvbx.esm.js} +652 -222
  21. package/dist/chunks/proxy-C2lnvvbx.esm.js.map +1 -0
  22. package/dist/chunks/{proxy-DBHj3kGK.cjs → proxy-HA_QQnd5.cjs} +662 -223
  23. package/dist/chunks/proxy-HA_QQnd5.cjs.map +1 -0
  24. package/dist/debug.cjs +37 -10
  25. package/dist/debug.cjs.map +1 -1
  26. package/dist/debug.esm.js +37 -10
  27. package/dist/debug.esm.js.map +1 -1
  28. package/dist/mutts.umd.js +4086 -3469
  29. package/dist/mutts.umd.js.map +1 -1
  30. package/dist/mutts.umd.min.js +1 -1
  31. package/dist/mutts.umd.min.js.map +1 -1
  32. package/dist/node.cjs +13 -3
  33. package/dist/node.cjs.map +1 -1
  34. package/dist/node.d.ts +2 -2
  35. package/dist/node.dev.cjs +13 -3
  36. package/dist/node.dev.cjs.map +1 -1
  37. package/dist/node.dev.d.ts +2 -2
  38. package/dist/node.dev.esm.js +3 -3
  39. package/dist/node.esm.js +3 -3
  40. package/dist/types.d.ts +30 -15
  41. package/docs/ai/api-reference.md +3 -1
  42. package/docs/ai/manual.md +17 -5
  43. package/docs/reactive/advanced.md +169 -10
  44. package/docs/reactive/debugging.md +15 -13
  45. package/docs/reactive.md +2 -1
  46. package/package.json +12 -7
  47. package/dist/chunks/index-CAdnMJev.cjs.map +0 -1
  48. package/dist/chunks/index-XsYTUhHx.esm.js.map +0 -1
  49. package/dist/chunks/node-DrrphEPf.cjs +0 -98
  50. package/dist/chunks/node-DrrphEPf.cjs.map +0 -1
  51. package/dist/chunks/node-NEZvVo4M.esm.js +0 -96
  52. package/dist/chunks/node-NEZvVo4M.esm.js.map +0 -1
  53. package/dist/chunks/proxy-BtmPFjSr.esm.js.map +0 -1
  54. package/dist/chunks/proxy-DBHj3kGK.cjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var proxy = require('./proxy-DBHj3kGK.cjs');
3
+ var proxy = require('./proxy-HA_QQnd5.cjs');
4
4
 
5
5
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
6
6
  // Integrated with `using` statement via Symbol.dispose
@@ -896,22 +896,26 @@ const lift = proxy.captioned(function lift(cb) {
896
896
  */
897
897
  function morphArray(source, fn, options) {
898
898
  if (typeof source !== 'function' && !proxy.isReactive(source) && options?.pure === true) {
899
- return source.map((i) => fn(i));
899
+ return source.map((i) => fn(i, { index: 0 }));
900
900
  }
901
901
  let track;
902
902
  const itemEffects = new Map();
903
903
  const cache = proxy.tag(`morph:${fn.name || 'anonymous'}`, []);
904
904
  let input = [];
905
+ function currentCleanupReason() {
906
+ const activeEffect = proxy.getActiveEffect();
907
+ if (!activeEffect)
908
+ return undefined;
909
+ const node = proxy.getEffectNode(activeEffect);
910
+ return node.currentReason;
911
+ }
912
+ function stopEntry(entry) {
913
+ entry.stop(proxy.chainExternalReason({ type: 'stopped', chain: currentCleanupReason() }));
914
+ }
905
915
  function stopItem(key) {
906
916
  const entry = itemEffects.get(key);
907
917
  if (entry) {
908
- const activeEffect = proxy.getActiveEffect();
909
- let chain;
910
- if (activeEffect) {
911
- const node = proxy.getEffectNode(activeEffect);
912
- chain = node.currentReason;
913
- }
914
- entry.stop(proxy.chainExternalReason({ type: 'stopped', chain }));
918
+ stopEntry(entry);
915
919
  itemEffects.delete(key);
916
920
  }
917
921
  }
@@ -919,15 +923,14 @@ function morphArray(source, fn, options) {
919
923
  const isPure = options?.pure === true || (typeof options?.pure === 'function' && options.pure(input));
920
924
  if (isPure) {
921
925
  track(() => {
922
- cache[key] = fn(input);
926
+ cache[key] = fn(input, { index: key });
923
927
  });
924
928
  }
925
929
  else {
926
- const indexRef = { value: key };
930
+ const position = proxy.reactive({ index: key });
927
931
  const stop = track(() => proxy.effect.opaque `morph:${fn.name}:${key}`((access) => {
928
- cache[indexRef.value] = fn(input, access);
929
- return (reason) => {
930
- delete cache[indexRef.value];
932
+ if (access.reaction) {
933
+ delete cache[position.index];
931
934
  proxy.touched1(cache, { type: 'invalidate', prop: 'morph' }, String(key));
932
935
  const activeEffect = proxy.getActiveEffect();
933
936
  let chain;
@@ -937,12 +940,16 @@ function morphArray(source, fn, options) {
937
940
  }
938
941
  stop?.({
939
942
  type: 'invalidate',
940
- cause: proxy.chainExternalReason(reason ?? { type: 'stopped', chain }),
943
+ cause: proxy.chainExternalReason(!access.reaction || access.reaction === true
944
+ ? { type: 'stopped', chain }
945
+ : access.reaction),
941
946
  chain: proxy.chainExternalReason(chain),
942
947
  });
943
- };
948
+ }
949
+ else
950
+ cache[position.index] = fn(input, position, access);
944
951
  }));
945
- itemEffects.set(key, { stop, index: indexRef });
952
+ itemEffects.set(key, { stop, position });
946
953
  }
947
954
  }
948
955
  const proxy$1 = proxy.reactive(cache, {
@@ -963,35 +970,59 @@ function morphArray(source, fn, options) {
963
970
  const newInput = [...(typeof source === 'function' ? source() : source)];
964
971
  const diffs = arrayDiff(input, newInput).toSorted((a, b) => b.indexA - a.indexA);
965
972
  if (diffs.length > 0) {
973
+ const reusable = new Map();
974
+ for (const [index, entry] of itemEffects) {
975
+ const value = input[index];
976
+ const entries = reusable.get(value);
977
+ if (entries)
978
+ entries.push({ index, entry });
979
+ else
980
+ reusable.set(value, [{ index, entry }]);
981
+ }
982
+ const nextEffects = new Map();
983
+ const reused = new Set();
984
+ const nextCache = new Map();
985
+ for (let index = 0; index < newInput.length; index++) {
986
+ const entries = reusable.get(newInput[index]);
987
+ const reusedEntry = entries?.shift();
988
+ if (!reusedEntry)
989
+ continue;
990
+ const { index: previousIndex, entry } = reusedEntry;
991
+ reused.add(previousIndex);
992
+ nextEffects.set(index, entry);
993
+ if (entry.position.index !== index)
994
+ entry.position.index = index;
995
+ else if (Object.hasOwn(cache, previousIndex))
996
+ nextCache.set(index, cache[previousIndex]);
997
+ }
998
+ for (const [index, entry] of itemEffects) {
999
+ if (!reused.has(index))
1000
+ stopEntry(entry);
1001
+ }
1002
+ itemEffects.clear();
1003
+ for (const [index, entry] of nextEffects)
1004
+ itemEffects.set(index, entry);
1005
+ const previousLength = cache.length;
1006
+ cache.length = newInput.length;
1007
+ for (let i = 0; i < Math.max(previousLength, newInput.length); i++)
1008
+ delete cache[i];
1009
+ for (const [index, value] of nextCache)
1010
+ cache[index] = value;
1011
+ const eagerIndices = new Set();
966
1012
  for (const diff of diffs) {
967
- // Stop items in removed range
968
- for (let i = diff.indexA; i < diff.indexA + diff.sliceA.length; i++)
969
- stopItem(i);
970
- // Shift existing itemEffects in the Map to match the new indices
971
- const shift = diff.sliceB.length - diff.sliceA.length;
972
- if (shift !== 0) {
973
- // We need to move entries in the Map.
974
- const entries = Array.from(itemEffects.entries()).sort((a, b) => a[0] - b[0]);
975
- // Remove entries that will be shifted
976
- for (const [idx, _entry] of entries) {
977
- if (idx >= diff.indexA + diff.sliceA.length) {
978
- itemEffects.delete(idx);
979
- }
980
- }
981
- // Re-add them with shifted indices
982
- for (const [idx, entry] of entries) {
983
- if (idx >= diff.indexA + diff.sliceA.length) {
984
- const newIdx = idx + shift;
985
- entry.index.value = newIdx;
986
- itemEffects.set(newIdx, entry);
987
- }
988
- }
989
- }
990
- // Splice the cache
991
- cache.splice(diff.indexA, diff.sliceA.length, ...new Array(diff.sliceB.length).fill(undefined));
992
- // Make holes for lazy computation
993
- for (let i = diff.indexA; i < diff.indexA + diff.sliceB.length; i++)
994
- delete cache[i];
1013
+ const max = Math.max(diff.sliceA.length, diff.sliceB.length);
1014
+ for (let i = 0; i < max; i++)
1015
+ eagerIndices.add(diff.indexA + i);
1016
+ }
1017
+ for (const index of eagerIndices) {
1018
+ if (index < 0 || index >= newInput.length || Object.hasOwn(cache, index))
1019
+ continue;
1020
+ const value = newInput[index];
1021
+ const isPure = options?.pure === true || (typeof options?.pure === 'function' && options.pure(value));
1022
+ if (isPure)
1023
+ continue;
1024
+ stopItem(index);
1025
+ computeItem(index, value);
995
1026
  }
996
1027
  const invalidates = new Set([proxy.keysOf]);
997
1028
  if (input.length !== newInput.length)
@@ -1425,6 +1456,9 @@ function memoizeFunction(fn, opts) {
1425
1456
  for (const arg of args) {
1426
1457
  node = getBranch(node, arg);
1427
1458
  }
1459
+ if (proxy.inertDepth > 0) {
1460
+ return fn.apply(this, args);
1461
+ }
1428
1462
  proxy.dependant(node, 'memoize');
1429
1463
  if ('result' in node) {
1430
1464
  if (proxy.options.onMemoizationDiscrepancy) {
@@ -2063,14 +2097,28 @@ let ReactiveArrayWrapper = (() => {
2063
2097
  }, thisArg);
2064
2098
  }
2065
2099
  includes(searchElement, fromIndex) {
2066
- return arguments.length > 1
2067
- ? super.includes(proxy.unwrap(searchElement), fromIndex)
2068
- : super.includes(proxy.unwrap(searchElement));
2100
+ const target = proxy.unwrap(searchElement);
2101
+ const length = this.length;
2102
+ let start = fromIndex ?? 0;
2103
+ if (start < 0)
2104
+ start = Math.max(length + start, 0);
2105
+ for (let i = start; i < length; i++) {
2106
+ const value = proxy.unwrap(this[i]);
2107
+ if (value === target)
2108
+ return true;
2109
+ }
2110
+ return false;
2069
2111
  }
2070
2112
  indexOf(searchElement, fromIndex) {
2071
- return arguments.length > 1
2072
- ? super.indexOf(proxy.unwrap(searchElement), fromIndex)
2073
- : super.indexOf(proxy.unwrap(searchElement));
2113
+ const target = proxy.unwrap(searchElement);
2114
+ const length = this.length;
2115
+ let start = fromIndex ?? 0;
2116
+ if (start < 0)
2117
+ start = Math.max(length + start, 0);
2118
+ for (let i = start; i < length; i++)
2119
+ if (proxy.unwrap(this[i]) === target)
2120
+ return i;
2121
+ return -1;
2074
2122
  }
2075
2123
  join(separator) {
2076
2124
  return super.join(separator);
@@ -2080,9 +2128,17 @@ let ReactiveArrayWrapper = (() => {
2080
2128
  return super.keys();
2081
2129
  }
2082
2130
  lastIndexOf(searchElement, fromIndex) {
2083
- return arguments.length > 1
2084
- ? super.lastIndexOf(proxy.unwrap(searchElement), fromIndex)
2085
- : super.lastIndexOf(proxy.unwrap(searchElement));
2131
+ const target = proxy.unwrap(searchElement);
2132
+ const length = this.length;
2133
+ let start = fromIndex ?? length - 1;
2134
+ if (start < 0)
2135
+ start = length + start;
2136
+ if (start >= length)
2137
+ start = length - 1;
2138
+ for (let i = start; i >= 0; i--)
2139
+ if (proxy.unwrap(this[i]) === target)
2140
+ return i;
2141
+ return -1;
2086
2142
  }
2087
2143
  map(callbackfn, thisArg) {
2088
2144
  return proxy.reactive(super.map((v, i, a) => proxy.unwrap(callbackfn.call(thisArg, proxy.reactive(v), i, a)), thisArg));
@@ -2122,13 +2178,25 @@ let ReactiveArrayWrapper = (() => {
2122
2178
  return super.sort(wrappedCompare);
2123
2179
  }
2124
2180
  splice(start, deleteCount, ...items) {
2181
+ const length = this.length;
2182
+ const normalizedStart = start < 0 ? Math.max(length + start, 0) : Math.min(start, length);
2183
+ const actualDeleteCount = arguments.length === 1
2184
+ ? length - normalizedStart
2185
+ : Math.min(Math.max(deleteCount, 0), length - normalizedStart);
2186
+ const structural = actualDeleteCount > 0 || items.length > 0;
2187
+ let result;
2125
2188
  if (arguments.length > 2)
2126
- return proxy.reactive(super.splice(start, deleteCount, ...items.map(proxy.unwrap)));
2127
- if (arguments.length === 2)
2128
- return proxy.reactive(super.splice(start, deleteCount));
2129
- if (arguments.length === 1)
2130
- return proxy.reactive(super.splice(start));
2131
- return proxy.reactive([]);
2189
+ result = super.splice(start, deleteCount, ...items.map(proxy.unwrap));
2190
+ else if (arguments.length === 2)
2191
+ result = super.splice(start, deleteCount);
2192
+ else if (arguments.length === 1)
2193
+ result = super.splice(start);
2194
+ else
2195
+ result = [];
2196
+ if (structural && this.length === length) {
2197
+ proxy.touched(this, { type: 'set', prop: 'length' }, ['length']);
2198
+ }
2199
+ return proxy.reactive(result);
2132
2200
  }
2133
2201
  unshift(...items) {
2134
2202
  return super.unshift(...items.map(proxy.unwrap));
@@ -2183,6 +2251,12 @@ let ReactiveArrayWrapper = (() => {
2183
2251
  _a;
2184
2252
  })();
2185
2253
 
2254
+ // These abstract classes are prototype tables for the reactive proxy, not classes
2255
+ // that are instantiated directly. `this` is the original Map/WeakMap target when
2256
+ // a method is reached through the proxy meta-prototype path, so calls like
2257
+ // `this.get()` or `this.entries()` intentionally re-enter the proxy machinery.
2258
+ // Do not mechanically rewrite them to `super.*` as if these were real subclass
2259
+ // instances; that would bypass parts of the designed reactive dispatch.
2186
2260
  /**
2187
2261
  * Reactive wrapper around JavaScript's WeakMap class
2188
2262
  * Only tracks individual key operations, no size tracking (WeakMap limitation)
@@ -2231,10 +2305,8 @@ class ReactiveMap extends Map {
2231
2305
  if (hadEntries) {
2232
2306
  const evolution = { type: 'bunch', method: 'clear' };
2233
2307
  // Clear triggers all effects since all keys are affected
2234
- proxy.batch(() => {
2235
- proxy.touched1(this, evolution, 'size');
2236
- proxy.touched(proxy.contentRef(this), evolution);
2237
- });
2308
+ proxy.touched1(this, evolution, 'size');
2309
+ proxy.touched(proxy.contentRef(this), evolution);
2238
2310
  }
2239
2311
  }
2240
2312
  entries() {
@@ -2272,10 +2344,8 @@ class ReactiveMap extends Map {
2272
2344
  const result = super.delete(key);
2273
2345
  if (hadKey) {
2274
2346
  const evolution = { type: 'del', prop: key };
2275
- proxy.batch(() => {
2276
- proxy.touched1(proxy.contentRef(this), evolution, key);
2277
- proxy.touched1(this, evolution, 'size');
2278
- });
2347
+ proxy.touched1(proxy.contentRef(this), evolution, key);
2348
+ proxy.touched1(this, evolution, 'size');
2279
2349
  }
2280
2350
  return result;
2281
2351
  }
@@ -2293,17 +2363,21 @@ class ReactiveMap extends Map {
2293
2363
  const reactiveValue = proxy.reactive(value);
2294
2364
  super.set(key, reactiveValue);
2295
2365
  if (!hadKey || oldValue !== reactiveValue) {
2296
- proxy.batch(() => {
2297
- proxy.notifyPropertyChange(proxy.contentRef(this), key, oldValue, reactiveValue, hadKey);
2298
- // Also notify size change for Map (WeakMap doesn't track size)
2299
- const evolution = { type: hadKey ? 'set' : 'add', prop: key };
2300
- proxy.touched1(this, evolution, 'size');
2301
- });
2366
+ proxy.notifyPropertyChange(proxy.contentRef(this), key, oldValue, reactiveValue, hadKey);
2367
+ // Also notify size change for Map (WeakMap doesn't track size)
2368
+ const evolution = { type: hadKey ? 'set' : 'add', prop: key };
2369
+ proxy.touched1(this, evolution, 'size');
2302
2370
  }
2303
2371
  return this;
2304
2372
  }
2305
2373
  }
2306
2374
 
2375
+ // These abstract classes are prototype tables for the reactive proxy, not classes
2376
+ // that are instantiated directly. `this` is the original Set/WeakSet target when
2377
+ // a method is reached through the proxy meta-prototype path, so calls like
2378
+ // `this.has()` or `this.entries()` intentionally re-enter the proxy machinery.
2379
+ // Do not mechanically rewrite them to `super.*` as if these were real subclass
2380
+ // instances; that would bypass parts of the designed reactive dispatch.
2307
2381
  /**
2308
2382
  * Reactive wrapper around JavaScript's WeakSet class
2309
2383
  * Only tracks individual value operations, no size tracking (WeakSet limitation)
@@ -2348,10 +2422,8 @@ class ReactiveSet extends Set {
2348
2422
  if (!had) {
2349
2423
  const evolution = { type: 'add', prop: reactiveValue };
2350
2424
  // touch for value-specific and aggregate dependencies
2351
- proxy.batch(() => {
2352
- proxy.touched1(proxy.contentRef(this), evolution, reactiveValue);
2353
- proxy.touched1(this, evolution, 'size');
2354
- });
2425
+ proxy.touched1(proxy.contentRef(this), evolution, reactiveValue);
2426
+ proxy.touched1(this, evolution, 'size');
2355
2427
  }
2356
2428
  return this;
2357
2429
  }
@@ -2360,10 +2432,8 @@ class ReactiveSet extends Set {
2360
2432
  super.clear();
2361
2433
  if (hadEntries) {
2362
2434
  const evolution = { type: 'bunch', method: 'clear' };
2363
- proxy.batch(() => {
2364
- proxy.touched1(this, evolution, 'size');
2365
- proxy.touched(proxy.contentRef(this), evolution);
2366
- });
2435
+ proxy.touched1(this, evolution, 'size');
2436
+ proxy.touched(proxy.contentRef(this), evolution);
2367
2437
  }
2368
2438
  }
2369
2439
  delete(value) {
@@ -2371,10 +2441,8 @@ class ReactiveSet extends Set {
2371
2441
  const res = super.delete(value);
2372
2442
  if (had) {
2373
2443
  const evolution = { type: 'del', prop: value };
2374
- proxy.batch(() => {
2375
- proxy.touched1(proxy.contentRef(this), evolution, value);
2376
- proxy.touched1(this, evolution, 'size');
2377
- });
2444
+ proxy.touched1(proxy.contentRef(this), evolution, value);
2445
+ proxy.touched1(this, evolution, 'size');
2378
2446
  }
2379
2447
  return res;
2380
2448
  }
@@ -2662,7 +2730,7 @@ function throttle(delay) {
2662
2730
  });
2663
2731
  }
2664
2732
 
2665
- var version$1 = "1.0.13";
2733
+ var version$1 = "1.0.14";
2666
2734
  var pkg = {
2667
2735
  version: version$1};
2668
2736
 
@@ -2681,8 +2749,8 @@ if (globalScope) {
2681
2749
  try {
2682
2750
  if (runtimeGlobals.__filename)
2683
2751
  source = runtimeGlobals.__filename;
2684
- else if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunks/index-CAdnMJev.cjs', document.baseURI).href)) }) !== 'undefined' && (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunks/index-CAdnMJev.cjs', document.baseURI).href))) {
2685
- source = (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunks/index-CAdnMJev.cjs', document.baseURI).href));
2752
+ else if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunks/index-BnTNC9eC.cjs', document.baseURI).href)) }) !== 'undefined' && (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunks/index-BnTNC9eC.cjs', document.baseURI).href))) {
2753
+ source = (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunks/index-BnTNC9eC.cjs', document.baseURI).href));
2686
2754
  }
2687
2755
  }
2688
2756
  catch (_e) { }
@@ -2732,4 +2800,4 @@ exports.throttle = throttle;
2732
2800
  exports.unreactive = unreactive;
2733
2801
  exports.watch = watch;
2734
2802
  exports.when = when;
2735
- //# sourceMappingURL=index-CAdnMJev.cjs.map
2803
+ //# sourceMappingURL=index-BnTNC9eC.cjs.map