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/README.md CHANGED
@@ -135,14 +135,79 @@ observer('state.user.name', callback)
135
135
  `dispatch` is the event mechanism underneath it, if you need to hook in lower-level:
136
136
 
137
137
  ```ts
138
- memorio.dispatch.listen('state.user', event => console.debug(event.detail))
138
+ const off = memorio.dispatch.listen('state.user', event => console.debug(event.detail))
139
139
  memorio.dispatch.set('state.user', { detail: { name: 'Sara' } })
140
+
141
+ off() // remove only this subscription
142
+ memorio.dispatch.remove('state.user') // remove all subscriptions on a name
140
143
  ```
141
144
 
145
+ Multiple subscribers on the same path each keep firing — each `listen` returns
146
+ its own unsubscribe. Prefer the returned `off()` over `remove()` for per-caller
147
+ teardown, since `remove()` clears *every* listener registered under that name.
148
+
142
149
  `observer` paths are runtime strings — for compiler-checked access, see [typed state](#typed-state--schema-validation).
143
150
 
144
151
  React apps get a dedicated hook, `useObserver` — see [React integration](#react-integration).
145
152
 
153
+ ## Redux / state-manager integration
154
+
155
+ Memorio ships its Redux integration as a **separate, optional entrypoint** (`memorio/redux`). It is
156
+ *not* part of core and is **never imported by `import memorio from 'memorio'`** — so applications
157
+ that don't use Redux pay nothing. Redux is neither a dependency of Memorio nor of the adapter: the
158
+ adapter talks to any store that satisfies a tiny structural shape (`getState` / `subscribe` /
159
+ `dispatch`), so `redux`, Redux Toolkit, Zustand, NgRx, etc. are all accepted without importing the
160
+ `redux` types.
161
+
162
+ > **Ownership rule:** *Redux owns application state. Memorio owns memory.* The adapter only writes
163
+ > **selected** values (declared opt-in mappings) into Memorio memory, and only recalls them once,
164
+ > at bootstrap. It never mirrors the whole Redux tree and never two-way syncs.
165
+
166
+ ```ts
167
+ import memorio from 'memorio'
168
+ import { createMemorioReduxBridge } from 'memorio/redux'
169
+
170
+ type AppState = { user: { name: string; preferences?: { theme?: string } } }
171
+
172
+ const memorioRedux = createMemorioReduxBridge<AppState>({
173
+ // Opt-in: only these paths become durable memory.
174
+ mappings: {
175
+ 'user.preferences': {
176
+ selector: (s) => s.user.preferences,
177
+ type: 'preference',
178
+ scope: 'local', // never 'hot' for Redux mappings
179
+ tags: ['app', 'user']
180
+ },
181
+ 'user.name': (s) => s.user.name // a bare selector is also accepted
182
+ },
183
+ // Performance: only consider the action types that can mutate mapped state.
184
+ whitelist: ['user/preferencesChanged', 'user/nameChanged'],
185
+ // Diagnostics
186
+ debug: true
187
+ })
188
+
189
+ const store = createStore(reducer, applyMiddleware(memorioRedux.middleware))
190
+
191
+ // Bootstrap hydration (Memorio -> Redux, one-shot). The bridge arms a
192
+ // re-entrancy guard so the restore dispatch does not echo back to Memorio.
193
+ await memorioRedux.hydrate(store, {
194
+ onHydration: (values) => store.dispatch(restorePreferences(values))
195
+ })
196
+ ```
197
+
198
+ Directional flow, no loops:
199
+
200
+ | Direction | Mechanism | Ownership |
201
+ | --- | --- | --- |
202
+ | Redux -> Memorio | middleware persists only `mappings` whose selector changed **and** whose action type isn't blacklisted (#30) | Memorio is the durable owner (#31) |
203
+ | Memorio -> Redux | `hydrate()` recalls once at bootstrap; the app dispatches its own restore actions | Redux owns runtime state |
204
+
205
+ - **No store mirroring:** only mapped selectors are persisted — never `store.getState()` wholesale.
206
+ - **No synchronization loops:** hydration arms `setHydrating(true)` + seeds the change-detection cache, so the restore dispatch is seen as "no change" and is not re-persisted.
207
+ - **Async is off the reducer path:** middleware defers `memorio.memory.remember` to a microtask and coerces a burst of actions into a single flush.
208
+ - **Failure-isolated:** Memorio errors are swallowed by default (`failSoft: true`, default) and routed to `onError`; Redux dispatch always completes normally.
209
+ - **Production-safe:** nothing in the adapter exposes Memorio's database on `globalThis` / DevTools.
210
+
146
211
  ## The layers
147
212
 
148
213
  ### `store` — persistent key/value
@@ -285,6 +350,18 @@ memorio.registerSchema('user', {
285
350
  state.user = { name: 'Sara' } // rejected — missing required "email"
286
351
  ```
287
352
 
353
+ Array item validation is supported per-element with the `items` schema field:
354
+
355
+ ```ts
356
+ memorio.registerSchema('tags', {
357
+ type: 'array',
358
+ items: { type: 'string' },
359
+ })
360
+
361
+ state.tags = ['a', 'b'] // accepted
362
+ state.tags = [1, 2, 3] // rejected — items must be strings
363
+ ```
364
+
288
365
  ## Local-first sync
289
366
 
290
367
  The local application owns its data; the cloud is optional transport.
@@ -302,7 +379,9 @@ memorio.memory.configure({
302
379
  })
303
380
  ```
304
381
 
305
- Conflict resolution defaults to *higher confidence wins, then more recent `lastConfirmedAt`* — override it when you need a different rule:
382
+ When `replay()` pulls remote operations, entries are ordered causally by HLC timestamp. For
383
+ *concurrent* writes to the same key (an HLC tie) the **remote entry wins by default**; provide a
384
+ `resolveConflict` to override:
306
385
 
307
386
  ```ts
308
387
  memorio.memory.configure({
@@ -313,7 +392,9 @@ memorio.memory.configure({
313
392
  })
314
393
  ```
315
394
 
316
- This resolver only settles *client-side* divergence between what memorio has seen locally — the provider is still responsible for the final server-side policy.
395
+ When no resolver is supplied, Memorio logs a warn (dev only) and applies the remote entry on a
396
+ tie. The resolver only settles *client-side* divergence between what memorio has seen locally —
397
+ the provider is still responsible for the final server-side policy.
317
398
 
318
399
  ## Cross-platform support
319
400
 
package/examples/basic.ts CHANGED
@@ -81,7 +81,7 @@ console.debug('Username:', store.get('username'))
81
81
  console.debug('Preferences:', store.get('preferences'))
82
82
 
83
83
  // Get storage size
84
- console.debug('Storage size:', store.size(), 'bytes')
84
+ console.debug('Storage size:', store.size(), 'kilobytes')
85
85
 
86
86
  // ============================================
87
87
  // SESSION - Temporary session storage
@@ -73,7 +73,7 @@ console.debug('Cart total:', total)
73
73
  // ============================================
74
74
 
75
75
  // Get session storage size
76
- console.debug('Session size:', session.size(), 'bytes')
76
+ console.debug('Session size:', session.size(), 'kilobytes')
77
77
 
78
78
  // ============================================
79
79
  // CLEANUP
@@ -51,7 +51,7 @@ if (savedPrefs) {
51
51
 
52
52
  // Get storage size
53
53
  const currentSize = store.size()
54
- console.debug('Current storage size:', currentSize, 'bytes')
54
+ console.debug('Current storage size:', currentSize, 'kilobytes')
55
55
 
56
56
  // ============================================
57
57
  // ALIAS METHODS
package/index.cjs CHANGED
@@ -645,24 +645,29 @@ var init_store = __esm({
645
645
  }, "value")
646
646
  },
647
647
  /**
648
- * Calculates total size of all memorio items (characters).
648
+ * Calculates total size of all memorio items in kilobytes.
649
+ * Counts each prefixed key and its JSON-serialized value, summing their
650
+ * byte lengths and converting to whole kilobytes (Math.ceil). Replaces
651
+ * the previous character-only total which did not express real usage.
652
+ * @returns Total size in kilobytes (0 when empty / no memorio items).
649
653
  */
650
654
  size: {
651
655
  value: /* @__PURE__ */ __name(() => {
652
- let totalSize = 0;
656
+ let totalBytes = 0;
653
657
  if (_hasStorage) {
654
- Object.keys(_localStorage).forEach((key) => {
655
- if (key.startsWith(_getPrefix())) {
658
+ for (let i = 0; i < _localStorage.length; i++) {
659
+ const key = _localStorage.key(i);
660
+ if (key && key.startsWith(_getPrefix())) {
656
661
  const item = _localStorage.getItem(key);
657
- if (item) totalSize += item.length;
662
+ if (item) totalBytes += key.length + item.length;
658
663
  }
659
- });
664
+ }
660
665
  } else {
661
- _getFallbackStorage().forEach((value) => {
662
- totalSize += value.length;
666
+ _getFallbackStorage().forEach((value, key) => {
667
+ totalBytes += key.length + value.length;
663
668
  });
664
669
  }
665
- return totalSize;
670
+ return totalBytes === 0 ? 0 : Math.ceil(totalBytes / 1024);
666
671
  }, "value")
667
672
  },
668
673
  /**
@@ -1733,30 +1738,51 @@ var dispatch = {
1733
1738
  }, "set"),
1734
1739
  ///
1735
1740
  /**
1736
- * Listens for the specified event names and executes the callback when the event is triggered.
1741
+ * Subscribes a callback to an event name. Maintains a *list* of handlers per
1742
+ * name, so multiple subscribers on the same path (e.g. several
1743
+ * `useObserver` calls observing `state.report`) each keep firing and each
1744
+ * get an independent unsubscribe. The previous single-slot-per-name
1745
+ * behaviour silently dropped the second subscriber and let any unsubscribe
1746
+ * cancel every other subscriber on that path.
1737
1747
  * @param name The name of the event to listen for.
1738
1748
  * @param cb The callback function to execute when the event is triggered.
1749
+ * @returns An unsubscribe function for this subscriber, or void if no callback.
1739
1750
  */
1740
1751
  listen: /* @__PURE__ */ __name((name, cb = null, _flag) => {
1741
- if (globalThis.events?.[name]) return;
1742
- const exec = cb ? (e) => {
1752
+ if (!cb) return;
1753
+ const wrapped = /* @__PURE__ */ __name((e) => {
1743
1754
  const target = e;
1744
- return globalThis.queueMicrotask ? globalThis.queueMicrotask(() => cb(target)) : Promise.resolve().then(() => cb(target));
1745
- } : void 0;
1746
- if (exec) globalThis.addEventListener(name, exec);
1747
- if (exec) globalThis.events[name] = exec;
1755
+ if (globalThis.queueMicrotask) {
1756
+ globalThis.queueMicrotask(() => cb(target));
1757
+ } else {
1758
+ Promise.resolve().then(() => cb(target));
1759
+ }
1760
+ }, "wrapped");
1761
+ globalThis.addEventListener(name, wrapped);
1762
+ const list = globalThis.events[name] = globalThis.events[name] ?? [];
1763
+ list.push(wrapped);
1764
+ return () => {
1765
+ globalThis.removeEventListener(name, wrapped);
1766
+ const idx = list.indexOf(wrapped);
1767
+ if (idx > -1) list.splice(idx, 1);
1768
+ if (!list.length) delete globalThis.events[name];
1769
+ };
1748
1770
  }, "listen"),
1749
1771
  ///
1750
1772
  /**
1751
- * Removes the event listener for the specified event names.
1752
- * @param name The name of the event to remove the listener for.
1773
+ * Removes ALL handlers for the specified event name (imperative bulk
1774
+ * removal). For per-subscriber teardown, use the unsubscribe function
1775
+ * returned by `listen`.
1776
+ * @param name The name of the event to remove the listeners for.
1753
1777
  */
1754
1778
  remove: /* @__PURE__ */ __name((name) => {
1755
- const listener = globalThis.events?.[name];
1756
- if (listener) {
1757
- globalThis.removeEventListener(name, listener);
1758
- delete globalThis.events[name];
1779
+ const entry = globalThis.events?.[name];
1780
+ if (Array.isArray(entry)) {
1781
+ entry.forEach((h) => globalThis.removeEventListener(name, h));
1782
+ } else if (typeof entry === "function") {
1783
+ globalThis.removeEventListener(name, entry);
1759
1784
  }
1785
+ delete globalThis.events[name];
1760
1786
  }, "remove")
1761
1787
  };
1762
1788
 
@@ -1820,6 +1846,14 @@ var _validateValue = /* @__PURE__ */ __name((value, schema2, path) => {
1820
1846
  }
1821
1847
  }
1822
1848
  }
1849
+ if (schema2.type === "array" && Array.isArray(value) && schema2.items) {
1850
+ for (let i = 0; i < value.length; i++) {
1851
+ const itemResult = _validateValue(value[i], schema2.items, `${path}[${i}]`);
1852
+ if (!itemResult.valid) {
1853
+ errors.push(...itemResult.errors || []);
1854
+ }
1855
+ }
1856
+ }
1823
1857
  return errors.length === 0 ? { valid: true } : { valid: false, errors };
1824
1858
  }, "_validateValue");
1825
1859
  var validate = /* @__PURE__ */ __name((path, value) => {
@@ -1880,7 +1914,8 @@ var buildProxy = /* @__PURE__ */ __name((obj, callback, tree = []) => {
1880
1914
  const event = /* @__PURE__ */ __name((name) => {
1881
1915
  try {
1882
1916
  dispatch.set(name, { detail: { name } });
1883
- } catch (_) {
1917
+ } catch (err) {
1918
+ if (exports.DEV) console.warn("[memorio state] event dispatch failed:", err);
1884
1919
  }
1885
1920
  return;
1886
1921
  }, "event");
@@ -1899,7 +1934,7 @@ var buildProxy = /* @__PURE__ */ __name((obj, callback, tree = []) => {
1899
1934
  }
1900
1935
  if (prop === "list") {
1901
1936
  message_default('Some state could be hidden when you use "state.list". Use "state" to see the complete list ');
1902
- return JSON.parse(JSON.stringify(target));
1937
+ return deepRaw(target, /* @__PURE__ */ new WeakSet());
1903
1938
  }
1904
1939
  if (typeof prop === "string") {
1905
1940
  const path = tree.length > 0 ? "state." + tree.concat(prop).join(".") : "state." + prop;
@@ -2035,7 +2070,8 @@ var buildProxy = /* @__PURE__ */ __name((obj, callback, tree = []) => {
2035
2070
  message_default(`State '${key}' is now unlocked.`);
2036
2071
  }, "value")
2037
2072
  });
2038
- } catch (_) {
2073
+ } catch (err) {
2074
+ if (exports.DEV) console.warn("[memorio state] lock/unlock defineProperty failed:", err);
2039
2075
  }
2040
2076
  }
2041
2077
  }
@@ -2275,27 +2311,28 @@ if (globalThis[SESSION_INSTANCE_KEY]) {
2275
2311
  }, "value")
2276
2312
  },
2277
2313
  /**
2278
- * Gets total size of all sessionStorage items.
2279
- * @returns Total size in characters.
2314
+ * Calculates total size of all memorio session items in kilobytes.
2315
+ * Counts each prefixed key and its JSON-serialized value, summing their
2316
+ * byte lengths and converting to whole kilobytes (Math.ceil).
2317
+ * @returns Total size in kilobytes (0 when empty / no memorio items).
2280
2318
  */
2281
2319
  size: {
2282
2320
  value: /* @__PURE__ */ __name(() => {
2283
- let totalSize = 0;
2321
+ let totalBytes = 0;
2284
2322
  if (_hasStorage2) {
2285
- Object.keys(_sessionStorage).forEach(
2286
- (key) => {
2287
- if (key.startsWith(_getPrefix2())) {
2288
- const item = _sessionStorage.getItem(key);
2289
- if (item) totalSize += item.length;
2290
- }
2323
+ for (let i = 0; i < _sessionStorage.length; i++) {
2324
+ const key = _sessionStorage.key(i);
2325
+ if (key && key.startsWith(_getPrefix2())) {
2326
+ const item = _sessionStorage.getItem(key);
2327
+ if (item) totalBytes += key.length + item.length;
2291
2328
  }
2292
- );
2329
+ }
2293
2330
  } else {
2294
- _getFallbackStorage2().forEach((value) => {
2295
- totalSize += value.length;
2331
+ _getFallbackStorage2().forEach((value, key) => {
2332
+ totalBytes += key.length + value.length;
2296
2333
  });
2297
2334
  }
2298
- return totalSize;
2335
+ return totalBytes === 0 ? 0 : Math.ceil(totalBytes / 1024);
2299
2336
  }, "value")
2300
2337
  },
2301
2338
  /**
@@ -2514,6 +2551,7 @@ var useObserver = /* @__PURE__ */ __name(((func, deps = []) => {
2514
2551
  };
2515
2552
  }
2516
2553
  const handlers = [];
2554
+ const unsubs = [];
2517
2555
  const depList = Array.isArray(deps) && !deps.__path ? deps : [deps];
2518
2556
  try {
2519
2557
  depList.forEach((dep) => {
@@ -2527,8 +2565,11 @@ var useObserver = /* @__PURE__ */ __name(((func, deps = []) => {
2527
2565
  if (t) {
2528
2566
  t = t.replace(/^(\(\)\s*=>\s*)/, "").replace(/^globalThis\./, "").replace(/^state\./, "");
2529
2567
  const path = t.startsWith("store.") || t.startsWith("state.") ? t : "state." + t;
2530
- dispatch.listen(path, () => func());
2531
- handlers.push(path);
2568
+ if (!handlers.includes(path)) {
2569
+ handlers.push(path);
2570
+ const off = dispatch.listen(path, () => func());
2571
+ if (typeof off === "function") unsubs.push({ path, off });
2572
+ }
2532
2573
  } else {
2533
2574
  exports.internal.clearTrackedPaths();
2534
2575
  exports.internal.tracking = true;
@@ -2540,8 +2581,9 @@ var useObserver = /* @__PURE__ */ __name(((func, deps = []) => {
2540
2581
  exports.internal.trackedPaths.forEach((path) => {
2541
2582
  if (!handlers.includes(path)) {
2542
2583
  message_default(`useObserver: [Auto-Discovery] -> "${path}"`);
2543
- dispatch.listen(path, () => func());
2544
2584
  handlers.push(path);
2585
+ const off = dispatch.listen(path, () => func());
2586
+ if (typeof off === "function") unsubs.push({ path, off });
2545
2587
  }
2546
2588
  });
2547
2589
  }
@@ -2552,11 +2594,9 @@ var useObserver = /* @__PURE__ */ __name(((func, deps = []) => {
2552
2594
  console.warn(`useObserver: No paths resolved for monitoring. Check your dependencies!`);
2553
2595
  }
2554
2596
  return () => {
2555
- handlers.forEach((path) => {
2556
- if (typeof dispatch.remove === "function") {
2557
- dispatch.remove(path);
2558
- message_default(`useObserver: Stopped monitoring "${path}"`);
2559
- }
2597
+ unsubs.forEach(({ path, off }) => {
2598
+ off();
2599
+ message_default(`useObserver: Stopped monitoring "${path}"`);
2560
2600
  });
2561
2601
  };
2562
2602
  } catch (err) {
@@ -3263,7 +3303,7 @@ init_internal();
3263
3303
 
3264
3304
  // package.json
3265
3305
  var package_default = {
3266
- version: "4.9.8"};
3306
+ version: "4.9.30"};
3267
3307
 
3268
3308
  // core/global.ts
3269
3309
  init_env();
@@ -3282,20 +3322,20 @@ __name(typed, "typed");
3282
3322
  // functions/history/index.ts
3283
3323
  init_cjs_shims();
3284
3324
  init_internal();
3325
+ init_env();
3285
3326
  init_message();
3286
3327
  function _clone(value) {
3287
3328
  if (value === null || typeof value !== "object") return value;
3288
- const ctor = value.constructor?.name;
3289
- if (ctor !== "Object" && ctor !== "Array") return value;
3290
3329
  try {
3291
- return JSON.parse(JSON.stringify(value));
3292
- } catch {
3330
+ return deepRaw(value, /* @__PURE__ */ new WeakSet());
3331
+ } catch (err) {
3332
+ if (exports.DEV) console.warn("[memorio history] deep clone failed:", err);
3293
3333
  return value;
3294
3334
  }
3295
3335
  }
3296
3336
  __name(_clone, "_clone");
3297
3337
  var snapshot = /* @__PURE__ */ __name(() => {
3298
- return _clone(deepRaw(exports.state));
3338
+ return deepRaw(exports.state, /* @__PURE__ */ new WeakSet());
3299
3339
  }, "snapshot");
3300
3340
  var diff = /* @__PURE__ */ __name((snap) => {
3301
3341
  const changes = [];
@@ -3445,9 +3485,11 @@ var clearRedo = /* @__PURE__ */ __name(() => {
3445
3485
  init_cjs_shims();
3446
3486
  init_store();
3447
3487
  init_idb();
3488
+ init_env();
3448
3489
 
3449
3490
  // functions/memory/journal.ts
3450
3491
  init_cjs_shims();
3492
+ init_env();
3451
3493
  init_store();
3452
3494
  var g2 = globalThis;
3453
3495
  function namespace() {
@@ -3468,7 +3510,8 @@ function parseEntry(raw) {
3468
3510
  try {
3469
3511
  if (typeof raw === "string") return JSON.parse(raw);
3470
3512
  return raw;
3471
- } catch {
3513
+ } catch (err) {
3514
+ if (exports.DEV) console.warn("[memorio memory] failed to parse journal entry:", err);
3472
3515
  return null;
3473
3516
  }
3474
3517
  }
@@ -3552,10 +3595,12 @@ var journal = {
3552
3595
  * local operations, then optionally pulls newer remote operations and applies
3553
3596
  * them. No-op (empty ack) when no provider is configured.
3554
3597
  *
3555
- * When pulling remote entries, HLC timestamps are used to order operations
3556
- * causally. Concurrent writes to the same key are resolved via the provider's
3557
- * `resolveConflict` method (if provided), falling back to HLC timestamp
3558
- * comparison.
3598
+ * When pulling remote entries, HLC timestamps are used to order operations
3599
+ * causally. Concurrent writes to the same key (HLC tie) are resolved via the
3600
+ * provider's `resolveConflict` method (if provided), otherwise the REMOTE
3601
+ * entry wins by default (logged at warn level in dev). Provide a resolver on
3602
+ * the sync provider to override this behaviour and prevent silent local data
3603
+ * loss.
3559
3604
  */
3560
3605
  async replay() {
3561
3606
  const ack = { synced: [], resolved: [] };
@@ -3591,7 +3636,11 @@ var journal = {
3591
3636
  if (ha && hb) {
3592
3637
  const cmp = hlc.compare(ha, hb);
3593
3638
  if (cmp === 0) {
3594
- const resolution = typeof provider.resolveConflict === "function" ? await provider.resolveConflict(localEntry, e) : "remote";
3639
+ const hasResolver = typeof provider.resolveConflict === "function";
3640
+ const resolution = hasResolver ? await provider.resolveConflict(localEntry, e) : "remote";
3641
+ if (!hasResolver && exports.DEV) {
3642
+ console.warn(`[memorio memory] replay() conflict for "${e.id}": no resolveConflict handler provided, defaulting to remote-wins`);
3643
+ }
3595
3644
  ack.resolved?.push({ id: e.id, resolution });
3596
3645
  if (resolution === "local") continue;
3597
3646
  } else if (cmp > 0) {
@@ -3602,7 +3651,8 @@ var journal = {
3602
3651
  await this.append(e, e.operation || "confirm");
3603
3652
  await this.markSynced([e.id]);
3604
3653
  }
3605
- } catch (_) {
3654
+ } catch (err) {
3655
+ if (exports.DEV) console.warn("[memorio memory] sync pull failed:", err);
3606
3656
  }
3607
3657
  }
3608
3658
  return ack;
@@ -3641,7 +3691,8 @@ async function recordJournal(entry, operation) {
3641
3691
  if (entry.scope === "hot") return;
3642
3692
  try {
3643
3693
  await journal_default.append(entry, operation);
3644
- } catch (_) {
3694
+ } catch (err) {
3695
+ if (exports.DEV) console.warn("[memorio memory] journal append failed:", err);
3645
3696
  }
3646
3697
  }
3647
3698
  __name(recordJournal, "recordJournal");
@@ -3804,7 +3855,8 @@ async function loadFromScope(id, scope, isSuperseded = false) {
3804
3855
  const entry = JSON.parse(raw);
3805
3856
  entry.id = entry.id || id;
3806
3857
  return entry;
3807
- } catch {
3858
+ } catch (err) {
3859
+ if (exports.DEV) console.warn("[memorio memory] failed to parse memory entry:", err);
3808
3860
  return null;
3809
3861
  }
3810
3862
  }
@@ -3850,7 +3902,8 @@ async function getAllIndexes() {
3850
3902
  const raw = exports.store.get(MEMORY_INDEX_KEY);
3851
3903
  if (!raw) return {};
3852
3904
  return JSON.parse(raw);
3853
- } catch {
3905
+ } catch (err) {
3906
+ if (exports.DEV) console.warn("[memorio memory] failed to parse memory index:", err);
3854
3907
  return {};
3855
3908
  }
3856
3909
  }
@@ -4121,6 +4174,7 @@ var memory = {
4121
4174
 
4122
4175
  // functions/inspect/index.ts
4123
4176
  init_cjs_shims();
4177
+ init_env();
4124
4178
  var stateKeys = /* @__PURE__ */ __name(() => {
4125
4179
  const raw = deepRaw(exports.state);
4126
4180
  return Object.keys(raw).filter(
@@ -4187,11 +4241,10 @@ function _getValue(path) {
4187
4241
  __name(_getValue, "_getValue");
4188
4242
  function _clone2(value) {
4189
4243
  if (value === null || typeof value !== "object") return value;
4190
- const ctor = value.constructor?.name;
4191
- if (ctor !== "Object" && ctor !== "Array") return value;
4192
4244
  try {
4193
- return JSON.parse(JSON.stringify(value));
4194
- } catch {
4245
+ return deepRaw(value, /* @__PURE__ */ new WeakSet());
4246
+ } catch (err) {
4247
+ if (exports.DEV) console.warn("[memorio inspect] deep clone failed:", err);
4195
4248
  return value;
4196
4249
  }
4197
4250
  }
@@ -4218,15 +4271,18 @@ if (!("memorio" in globalThis)) {
4218
4271
  enumerable: false
4219
4272
  }
4220
4273
  );
4221
- Object.defineProperty(
4222
- globalThis,
4223
- "events",
4224
- {
4225
- value: {},
4226
- writable: true,
4227
- enumerable: false
4228
- }
4229
- );
4274
+ if (!("events" in globalThis)) {
4275
+ Object.defineProperty(
4276
+ globalThis,
4277
+ "events",
4278
+ {
4279
+ value: {},
4280
+ writable: true,
4281
+ enumerable: false,
4282
+ configurable: true
4283
+ }
4284
+ );
4285
+ }
4230
4286
  if (typeof globalThis.addEventListener === "function") {
4231
4287
  dispatch.listen("memorio:memory:cleanup", async () => memory.forgetExpired?.());
4232
4288
  }