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 +84 -3
- package/examples/basic.ts +1 -1
- package/examples/session-advanced.ts +1 -1
- package/examples/store-advanced.ts +1 -1
- package/index.cjs +131 -75
- package/index.js +131 -75
- package/llms.txt +13 -5
- package/modules/redux.cjs +2677 -0
- package/modules/redux.cjs.map +1 -0
- package/modules/redux.d.ts +119 -0
- package/modules/redux.js +2669 -0
- package/modules/redux.js.map +1 -0
- package/package.json +9 -3
- package/types/memorio.d.ts +1 -1
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
|
-
|
|
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
|
-
|
|
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(), '
|
|
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(), '
|
|
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, '
|
|
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
|
|
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
|
|
656
|
+
let totalBytes = 0;
|
|
653
657
|
if (_hasStorage) {
|
|
654
|
-
|
|
655
|
-
|
|
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)
|
|
662
|
+
if (item) totalBytes += key.length + item.length;
|
|
658
663
|
}
|
|
659
|
-
}
|
|
664
|
+
}
|
|
660
665
|
} else {
|
|
661
|
-
_getFallbackStorage().forEach((value) => {
|
|
662
|
-
|
|
666
|
+
_getFallbackStorage().forEach((value, key) => {
|
|
667
|
+
totalBytes += key.length + value.length;
|
|
663
668
|
});
|
|
664
669
|
}
|
|
665
|
-
return
|
|
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
|
-
*
|
|
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 (
|
|
1742
|
-
const
|
|
1752
|
+
if (!cb) return;
|
|
1753
|
+
const wrapped = /* @__PURE__ */ __name((e) => {
|
|
1743
1754
|
const target = e;
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
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
|
|
1752
|
-
*
|
|
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
|
|
1756
|
-
if (
|
|
1757
|
-
globalThis.removeEventListener(name,
|
|
1758
|
-
|
|
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
|
|
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
|
-
*
|
|
2279
|
-
*
|
|
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
|
|
2321
|
+
let totalBytes = 0;
|
|
2284
2322
|
if (_hasStorage2) {
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
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
|
-
|
|
2331
|
+
_getFallbackStorage2().forEach((value, key) => {
|
|
2332
|
+
totalBytes += key.length + value.length;
|
|
2296
2333
|
});
|
|
2297
2334
|
}
|
|
2298
|
-
return
|
|
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
|
-
|
|
2531
|
-
|
|
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
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
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
|
}
|