memorio 4.9.8 → 4.9.30

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/index.js CHANGED
@@ -642,24 +642,29 @@ var init_store = __esm({
642
642
  }, "value")
643
643
  },
644
644
  /**
645
- * Calculates total size of all memorio items (characters).
645
+ * Calculates total size of all memorio items in kilobytes.
646
+ * Counts each prefixed key and its JSON-serialized value, summing their
647
+ * byte lengths and converting to whole kilobytes (Math.ceil). Replaces
648
+ * the previous character-only total which did not express real usage.
649
+ * @returns Total size in kilobytes (0 when empty / no memorio items).
646
650
  */
647
651
  size: {
648
652
  value: /* @__PURE__ */ __name(() => {
649
- let totalSize = 0;
653
+ let totalBytes = 0;
650
654
  if (_hasStorage) {
651
- Object.keys(_localStorage).forEach((key) => {
652
- if (key.startsWith(_getPrefix())) {
655
+ for (let i = 0; i < _localStorage.length; i++) {
656
+ const key = _localStorage.key(i);
657
+ if (key && key.startsWith(_getPrefix())) {
653
658
  const item = _localStorage.getItem(key);
654
- if (item) totalSize += item.length;
659
+ if (item) totalBytes += key.length + item.length;
655
660
  }
656
- });
661
+ }
657
662
  } else {
658
- _getFallbackStorage().forEach((value) => {
659
- totalSize += value.length;
663
+ _getFallbackStorage().forEach((value, key) => {
664
+ totalBytes += key.length + value.length;
660
665
  });
661
666
  }
662
- return totalSize;
667
+ return totalBytes === 0 ? 0 : Math.ceil(totalBytes / 1024);
663
668
  }, "value")
664
669
  },
665
670
  /**
@@ -1730,30 +1735,51 @@ var dispatch = {
1730
1735
  }, "set"),
1731
1736
  ///
1732
1737
  /**
1733
- * Listens for the specified event names and executes the callback when the event is triggered.
1738
+ * Subscribes a callback to an event name. Maintains a *list* of handlers per
1739
+ * name, so multiple subscribers on the same path (e.g. several
1740
+ * `useObserver` calls observing `state.report`) each keep firing and each
1741
+ * get an independent unsubscribe. The previous single-slot-per-name
1742
+ * behaviour silently dropped the second subscriber and let any unsubscribe
1743
+ * cancel every other subscriber on that path.
1734
1744
  * @param name The name of the event to listen for.
1735
1745
  * @param cb The callback function to execute when the event is triggered.
1746
+ * @returns An unsubscribe function for this subscriber, or void if no callback.
1736
1747
  */
1737
1748
  listen: /* @__PURE__ */ __name((name, cb = null, _flag) => {
1738
- if (globalThis.events?.[name]) return;
1739
- const exec = cb ? (e) => {
1749
+ if (!cb) return;
1750
+ const wrapped = /* @__PURE__ */ __name((e) => {
1740
1751
  const target = e;
1741
- return globalThis.queueMicrotask ? globalThis.queueMicrotask(() => cb(target)) : Promise.resolve().then(() => cb(target));
1742
- } : void 0;
1743
- if (exec) globalThis.addEventListener(name, exec);
1744
- if (exec) globalThis.events[name] = exec;
1752
+ if (globalThis.queueMicrotask) {
1753
+ globalThis.queueMicrotask(() => cb(target));
1754
+ } else {
1755
+ Promise.resolve().then(() => cb(target));
1756
+ }
1757
+ }, "wrapped");
1758
+ globalThis.addEventListener(name, wrapped);
1759
+ const list = globalThis.events[name] = globalThis.events[name] ?? [];
1760
+ list.push(wrapped);
1761
+ return () => {
1762
+ globalThis.removeEventListener(name, wrapped);
1763
+ const idx = list.indexOf(wrapped);
1764
+ if (idx > -1) list.splice(idx, 1);
1765
+ if (!list.length) delete globalThis.events[name];
1766
+ };
1745
1767
  }, "listen"),
1746
1768
  ///
1747
1769
  /**
1748
- * Removes the event listener for the specified event names.
1749
- * @param name The name of the event to remove the listener for.
1770
+ * Removes ALL handlers for the specified event name (imperative bulk
1771
+ * removal). For per-subscriber teardown, use the unsubscribe function
1772
+ * returned by `listen`.
1773
+ * @param name The name of the event to remove the listeners for.
1750
1774
  */
1751
1775
  remove: /* @__PURE__ */ __name((name) => {
1752
- const listener = globalThis.events?.[name];
1753
- if (listener) {
1754
- globalThis.removeEventListener(name, listener);
1755
- delete globalThis.events[name];
1776
+ const entry = globalThis.events?.[name];
1777
+ if (Array.isArray(entry)) {
1778
+ entry.forEach((h) => globalThis.removeEventListener(name, h));
1779
+ } else if (typeof entry === "function") {
1780
+ globalThis.removeEventListener(name, entry);
1756
1781
  }
1782
+ delete globalThis.events[name];
1757
1783
  }, "remove")
1758
1784
  };
1759
1785
 
@@ -1817,6 +1843,14 @@ var _validateValue = /* @__PURE__ */ __name((value, schema2, path2) => {
1817
1843
  }
1818
1844
  }
1819
1845
  }
1846
+ if (schema2.type === "array" && Array.isArray(value) && schema2.items) {
1847
+ for (let i = 0; i < value.length; i++) {
1848
+ const itemResult = _validateValue(value[i], schema2.items, `${path2}[${i}]`);
1849
+ if (!itemResult.valid) {
1850
+ errors.push(...itemResult.errors || []);
1851
+ }
1852
+ }
1853
+ }
1820
1854
  return errors.length === 0 ? { valid: true } : { valid: false, errors };
1821
1855
  }, "_validateValue");
1822
1856
  var validate = /* @__PURE__ */ __name((path2, value) => {
@@ -1877,7 +1911,8 @@ var buildProxy = /* @__PURE__ */ __name((obj, callback, tree = []) => {
1877
1911
  const event = /* @__PURE__ */ __name((name) => {
1878
1912
  try {
1879
1913
  dispatch.set(name, { detail: { name } });
1880
- } catch (_) {
1914
+ } catch (err) {
1915
+ if (DEV) console.warn("[memorio state] event dispatch failed:", err);
1881
1916
  }
1882
1917
  return;
1883
1918
  }, "event");
@@ -1896,7 +1931,7 @@ var buildProxy = /* @__PURE__ */ __name((obj, callback, tree = []) => {
1896
1931
  }
1897
1932
  if (prop === "list") {
1898
1933
  message_default('Some state could be hidden when you use "state.list". Use "state" to see the complete list ');
1899
- return JSON.parse(JSON.stringify(target));
1934
+ return deepRaw(target, /* @__PURE__ */ new WeakSet());
1900
1935
  }
1901
1936
  if (typeof prop === "string") {
1902
1937
  const path2 = tree.length > 0 ? "state." + tree.concat(prop).join(".") : "state." + prop;
@@ -2032,7 +2067,8 @@ var buildProxy = /* @__PURE__ */ __name((obj, callback, tree = []) => {
2032
2067
  message_default(`State '${key}' is now unlocked.`);
2033
2068
  }, "value")
2034
2069
  });
2035
- } catch (_) {
2070
+ } catch (err) {
2071
+ if (DEV) console.warn("[memorio state] lock/unlock defineProperty failed:", err);
2036
2072
  }
2037
2073
  }
2038
2074
  }
@@ -2272,27 +2308,28 @@ if (globalThis[SESSION_INSTANCE_KEY]) {
2272
2308
  }, "value")
2273
2309
  },
2274
2310
  /**
2275
- * Gets total size of all sessionStorage items.
2276
- * @returns Total size in characters.
2311
+ * Calculates total size of all memorio session items in kilobytes.
2312
+ * Counts each prefixed key and its JSON-serialized value, summing their
2313
+ * byte lengths and converting to whole kilobytes (Math.ceil).
2314
+ * @returns Total size in kilobytes (0 when empty / no memorio items).
2277
2315
  */
2278
2316
  size: {
2279
2317
  value: /* @__PURE__ */ __name(() => {
2280
- let totalSize = 0;
2318
+ let totalBytes = 0;
2281
2319
  if (_hasStorage2) {
2282
- Object.keys(_sessionStorage).forEach(
2283
- (key) => {
2284
- if (key.startsWith(_getPrefix2())) {
2285
- const item = _sessionStorage.getItem(key);
2286
- if (item) totalSize += item.length;
2287
- }
2320
+ for (let i = 0; i < _sessionStorage.length; i++) {
2321
+ const key = _sessionStorage.key(i);
2322
+ if (key && key.startsWith(_getPrefix2())) {
2323
+ const item = _sessionStorage.getItem(key);
2324
+ if (item) totalBytes += key.length + item.length;
2288
2325
  }
2289
- );
2326
+ }
2290
2327
  } else {
2291
- _getFallbackStorage2().forEach((value) => {
2292
- totalSize += value.length;
2328
+ _getFallbackStorage2().forEach((value, key) => {
2329
+ totalBytes += key.length + value.length;
2293
2330
  });
2294
2331
  }
2295
- return totalSize;
2332
+ return totalBytes === 0 ? 0 : Math.ceil(totalBytes / 1024);
2296
2333
  }, "value")
2297
2334
  },
2298
2335
  /**
@@ -2511,6 +2548,7 @@ var useObserver = /* @__PURE__ */ __name(((func, deps = []) => {
2511
2548
  };
2512
2549
  }
2513
2550
  const handlers = [];
2551
+ const unsubs = [];
2514
2552
  const depList = Array.isArray(deps) && !deps.__path ? deps : [deps];
2515
2553
  try {
2516
2554
  depList.forEach((dep) => {
@@ -2524,8 +2562,11 @@ var useObserver = /* @__PURE__ */ __name(((func, deps = []) => {
2524
2562
  if (t) {
2525
2563
  t = t.replace(/^(\(\)\s*=>\s*)/, "").replace(/^globalThis\./, "").replace(/^state\./, "");
2526
2564
  const path2 = t.startsWith("store.") || t.startsWith("state.") ? t : "state." + t;
2527
- dispatch.listen(path2, () => func());
2528
- handlers.push(path2);
2565
+ if (!handlers.includes(path2)) {
2566
+ handlers.push(path2);
2567
+ const off = dispatch.listen(path2, () => func());
2568
+ if (typeof off === "function") unsubs.push({ path: path2, off });
2569
+ }
2529
2570
  } else {
2530
2571
  internal_default.clearTrackedPaths();
2531
2572
  internal_default.tracking = true;
@@ -2537,8 +2578,9 @@ var useObserver = /* @__PURE__ */ __name(((func, deps = []) => {
2537
2578
  internal_default.trackedPaths.forEach((path2) => {
2538
2579
  if (!handlers.includes(path2)) {
2539
2580
  message_default(`useObserver: [Auto-Discovery] -> "${path2}"`);
2540
- dispatch.listen(path2, () => func());
2541
2581
  handlers.push(path2);
2582
+ const off = dispatch.listen(path2, () => func());
2583
+ if (typeof off === "function") unsubs.push({ path: path2, off });
2542
2584
  }
2543
2585
  });
2544
2586
  }
@@ -2549,11 +2591,9 @@ var useObserver = /* @__PURE__ */ __name(((func, deps = []) => {
2549
2591
  console.warn(`useObserver: No paths resolved for monitoring. Check your dependencies!`);
2550
2592
  }
2551
2593
  return () => {
2552
- handlers.forEach((path2) => {
2553
- if (typeof dispatch.remove === "function") {
2554
- dispatch.remove(path2);
2555
- message_default(`useObserver: Stopped monitoring "${path2}"`);
2556
- }
2594
+ unsubs.forEach(({ path: path2, off }) => {
2595
+ off();
2596
+ message_default(`useObserver: Stopped monitoring "${path2}"`);
2557
2597
  });
2558
2598
  };
2559
2599
  } catch (err) {
@@ -3260,7 +3300,7 @@ init_internal();
3260
3300
 
3261
3301
  // package.json
3262
3302
  var package_default = {
3263
- version: "4.9.8"};
3303
+ version: "4.9.30"};
3264
3304
 
3265
3305
  // core/global.ts
3266
3306
  init_env();
@@ -3279,20 +3319,20 @@ __name(typed, "typed");
3279
3319
  // functions/history/index.ts
3280
3320
  init_esm_shims();
3281
3321
  init_internal();
3322
+ init_env();
3282
3323
  init_message();
3283
3324
  function _clone(value) {
3284
3325
  if (value === null || typeof value !== "object") return value;
3285
- const ctor = value.constructor?.name;
3286
- if (ctor !== "Object" && ctor !== "Array") return value;
3287
3326
  try {
3288
- return JSON.parse(JSON.stringify(value));
3289
- } catch {
3327
+ return deepRaw(value, /* @__PURE__ */ new WeakSet());
3328
+ } catch (err) {
3329
+ if (DEV) console.warn("[memorio history] deep clone failed:", err);
3290
3330
  return value;
3291
3331
  }
3292
3332
  }
3293
3333
  __name(_clone, "_clone");
3294
3334
  var snapshot = /* @__PURE__ */ __name(() => {
3295
- return _clone(deepRaw(state2));
3335
+ return deepRaw(state2, /* @__PURE__ */ new WeakSet());
3296
3336
  }, "snapshot");
3297
3337
  var diff = /* @__PURE__ */ __name((snap) => {
3298
3338
  const changes = [];
@@ -3442,9 +3482,11 @@ var clearRedo = /* @__PURE__ */ __name(() => {
3442
3482
  init_esm_shims();
3443
3483
  init_store();
3444
3484
  init_idb();
3485
+ init_env();
3445
3486
 
3446
3487
  // functions/memory/journal.ts
3447
3488
  init_esm_shims();
3489
+ init_env();
3448
3490
  init_store();
3449
3491
  var g2 = globalThis;
3450
3492
  function namespace() {
@@ -3465,7 +3507,8 @@ function parseEntry(raw) {
3465
3507
  try {
3466
3508
  if (typeof raw === "string") return JSON.parse(raw);
3467
3509
  return raw;
3468
- } catch {
3510
+ } catch (err) {
3511
+ if (DEV) console.warn("[memorio memory] failed to parse journal entry:", err);
3469
3512
  return null;
3470
3513
  }
3471
3514
  }
@@ -3549,10 +3592,12 @@ var journal = {
3549
3592
  * local operations, then optionally pulls newer remote operations and applies
3550
3593
  * them. No-op (empty ack) when no provider is configured.
3551
3594
  *
3552
- * When pulling remote entries, HLC timestamps are used to order operations
3553
- * causally. Concurrent writes to the same key are resolved via the provider's
3554
- * `resolveConflict` method (if provided), falling back to HLC timestamp
3555
- * comparison.
3595
+ * When pulling remote entries, HLC timestamps are used to order operations
3596
+ * causally. Concurrent writes to the same key (HLC tie) are resolved via the
3597
+ * provider's `resolveConflict` method (if provided), otherwise the REMOTE
3598
+ * entry wins by default (logged at warn level in dev). Provide a resolver on
3599
+ * the sync provider to override this behaviour and prevent silent local data
3600
+ * loss.
3556
3601
  */
3557
3602
  async replay() {
3558
3603
  const ack = { synced: [], resolved: [] };
@@ -3588,7 +3633,11 @@ var journal = {
3588
3633
  if (ha && hb) {
3589
3634
  const cmp = hlc.compare(ha, hb);
3590
3635
  if (cmp === 0) {
3591
- const resolution = typeof provider.resolveConflict === "function" ? await provider.resolveConflict(localEntry, e) : "remote";
3636
+ const hasResolver = typeof provider.resolveConflict === "function";
3637
+ const resolution = hasResolver ? await provider.resolveConflict(localEntry, e) : "remote";
3638
+ if (!hasResolver && DEV) {
3639
+ console.warn(`[memorio memory] replay() conflict for "${e.id}": no resolveConflict handler provided, defaulting to remote-wins`);
3640
+ }
3592
3641
  ack.resolved?.push({ id: e.id, resolution });
3593
3642
  if (resolution === "local") continue;
3594
3643
  } else if (cmp > 0) {
@@ -3599,7 +3648,8 @@ var journal = {
3599
3648
  await this.append(e, e.operation || "confirm");
3600
3649
  await this.markSynced([e.id]);
3601
3650
  }
3602
- } catch (_) {
3651
+ } catch (err) {
3652
+ if (DEV) console.warn("[memorio memory] sync pull failed:", err);
3603
3653
  }
3604
3654
  }
3605
3655
  return ack;
@@ -3638,7 +3688,8 @@ async function recordJournal(entry, operation) {
3638
3688
  if (entry.scope === "hot") return;
3639
3689
  try {
3640
3690
  await journal_default.append(entry, operation);
3641
- } catch (_) {
3691
+ } catch (err) {
3692
+ if (DEV) console.warn("[memorio memory] journal append failed:", err);
3642
3693
  }
3643
3694
  }
3644
3695
  __name(recordJournal, "recordJournal");
@@ -3801,7 +3852,8 @@ async function loadFromScope(id, scope, isSuperseded = false) {
3801
3852
  const entry = JSON.parse(raw);
3802
3853
  entry.id = entry.id || id;
3803
3854
  return entry;
3804
- } catch {
3855
+ } catch (err) {
3856
+ if (DEV) console.warn("[memorio memory] failed to parse memory entry:", err);
3805
3857
  return null;
3806
3858
  }
3807
3859
  }
@@ -3847,7 +3899,8 @@ async function getAllIndexes() {
3847
3899
  const raw = store.get(MEMORY_INDEX_KEY);
3848
3900
  if (!raw) return {};
3849
3901
  return JSON.parse(raw);
3850
- } catch {
3902
+ } catch (err) {
3903
+ if (DEV) console.warn("[memorio memory] failed to parse memory index:", err);
3851
3904
  return {};
3852
3905
  }
3853
3906
  }
@@ -4118,6 +4171,7 @@ var memory = {
4118
4171
 
4119
4172
  // functions/inspect/index.ts
4120
4173
  init_esm_shims();
4174
+ init_env();
4121
4175
  var stateKeys = /* @__PURE__ */ __name(() => {
4122
4176
  const raw = deepRaw(state2);
4123
4177
  return Object.keys(raw).filter(
@@ -4184,11 +4238,10 @@ function _getValue(path2) {
4184
4238
  __name(_getValue, "_getValue");
4185
4239
  function _clone2(value) {
4186
4240
  if (value === null || typeof value !== "object") return value;
4187
- const ctor = value.constructor?.name;
4188
- if (ctor !== "Object" && ctor !== "Array") return value;
4189
4241
  try {
4190
- return JSON.parse(JSON.stringify(value));
4191
- } catch {
4242
+ return deepRaw(value, /* @__PURE__ */ new WeakSet());
4243
+ } catch (err) {
4244
+ if (DEV) console.warn("[memorio inspect] deep clone failed:", err);
4192
4245
  return value;
4193
4246
  }
4194
4247
  }
@@ -4215,15 +4268,18 @@ if (!("memorio" in globalThis)) {
4215
4268
  enumerable: false
4216
4269
  }
4217
4270
  );
4218
- Object.defineProperty(
4219
- globalThis,
4220
- "events",
4221
- {
4222
- value: {},
4223
- writable: true,
4224
- enumerable: false
4225
- }
4226
- );
4271
+ if (!("events" in globalThis)) {
4272
+ Object.defineProperty(
4273
+ globalThis,
4274
+ "events",
4275
+ {
4276
+ value: {},
4277
+ writable: true,
4278
+ enumerable: false,
4279
+ configurable: true
4280
+ }
4281
+ );
4282
+ }
4227
4283
  if (typeof globalThis.addEventListener === "function") {
4228
4284
  dispatch.listen("memorio:memory:cleanup", async () => memory.forgetExpired?.());
4229
4285
  }
package/llms.txt CHANGED
@@ -100,7 +100,7 @@ store.removeAll()
100
100
  store.clearAll() // alias
101
101
 
102
102
  // Check size
103
- console.debug(store.size(), 'chars stored')
103
+ console.debug(store.size(), 'kilobytes')
104
104
 
105
105
  // Check if persistent (real localStorage vs memory fallback)
106
106
  console.debug(store.isPersistent) // true → real localStorage
@@ -134,7 +134,7 @@ session.removeAll()
134
134
  session.clearAll() // alias
135
135
 
136
136
  // Check size
137
- console.debug(session.size(), 'chars stored')
137
+ console.debug(session.size(), 'kilobytes')
138
138
 
139
139
  // Check if persistent
140
140
  console.debug(session.isPersistent)
@@ -466,15 +466,23 @@ MIT © Dario Passariello (BigLogic Inc Canada)
466
466
 
467
467
  Internal event system used by state changes; also usable directly for custom events.
468
468
 
469
+ Listeners are stored as a *list* per event name, so multiple subscribers on the
470
+ same name each keep firing. `dispatch.listen` returns an unsubscribe function
471
+ for that specific subscriber — call it to remove only that one listener without
472
+ affecting others registered under the same name.
473
+
469
474
  ```javascript
470
475
  // Dispatch a custom event
471
476
  memorio.dispatch.set('custom:event', { detail: { data: 'value' } })
472
477
 
473
- // Listen for an event
474
- memorio.dispatch.listen('custom:event', (e) => {
478
+ // Listen for an event (returns an unsubscribe function)
479
+ const off = memorio.dispatch.listen('custom:event', (e) => {
475
480
  console.debug('Event triggered:', e.detail)
476
481
  })
477
482
 
478
- // Remove listener
483
+ // Remove only this listener via the returned unsubscribe
484
+ off()
485
+
486
+ // Remove *all* listeners for a name (imperative bulk removal)
479
487
  memorio.dispatch.remove('custom:event')
480
488
  ```