memorio 5.1.3 → 5.1.4

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/AGENTS.md CHANGED
@@ -69,7 +69,7 @@ If the code you're generating is client-side (a single browser tab, one user), `
69
69
  outside a guaranteed browser context must guard: `if (idb.db.support())` or check
70
70
  `memorio.getCapabilities().hasIndexedDB` - don't rely on the no-op warning alone reaching a log
71
71
  anyone will see.
72
- - **`sqlite` persistence uses journal + checkpoint (incremental writes, periodic full snapshots).** Batch writes and persist once after the batch per the README's guidance — calling `persist()` inside a tight loop still forces expensive checkpoints.
72
+ - **`sqlite` persistence uses journal + checkpoint (incremental writes, periodic full snapshots).** Batch writes and persist once after the batch per the README's guidance - calling `persist()` inside a tight loop still forces expensive checkpoints.
73
73
  - **`observer('state.some.path', cb)` paths are plain strings, unchecked against `state`'s actual
74
74
  shape.** A typo or a later rename fails *silently* - the observer just never fires again. Prefer
75
75
  `memorio.typed<T>()` + `registerSchema()` for anything you'd hate to have silently stop working,
package/CHANGELOG.md CHANGED
@@ -5,12 +5,15 @@ All notable changes to this project will be documented in this file.
5
5
  ## [Unreleased]
6
6
 
7
7
  ### Added
8
- - **SQLite journal + checkpoint persistence**: When `persistence: true` is set on `sqlite.db.create()` (or via `sqlite.config({ persistence: true })`), writes are recorded in an append-only journal with HLC timestamps. Periodic checkpoints (every 50 ops or 250 ms of write-idle) snapshot the full database to `store`. On reopen, the checkpoint is loaded and pending journal entries are replayed — no writes lost between checkpoints. New methods: `sqlite.db.flush()`, `sqlite.db.persist()`, `sqlite.db.journal` namespace (`append`, `entries`, `count`, `clear`, `replay`, `checkpoint`), `sqlite.db.journal` for direct journal inspection.
8
+ - **SQLite journal + checkpoint persistence**: When `persistence: true` is set on `sqlite.db.create()` (or via `sqlite.config({ persistence: true })`), writes are recorded in an append-only journal with HLC timestamps. Periodic checkpoints (every 50 ops or 250 ms of write-idle) snapshot the full database to `store`. On reopen, the checkpoint is loaded and pending journal entries are replayed - no writes lost between checkpoints. New methods: `sqlite.db.flush()`, `sqlite.db.persist()`, `sqlite.db.journal` namespace (`append`, `entries`, `count`, `clear`, `replay`, `checkpoint`), `sqlite.db.journal` for direct journal inspection.
9
+
10
+ ### Fixed
11
+ - **`package.json` `sideEffects`**: Added `./core/global.ts` to the `sideEffects` array so that esbuild/tsup no longer tree-shakes the `import './core/global'` side-effect-only import in `index.ts`. Without this fix, the `core/global.ts` initialization block (which defines `globalThis.memorio`) was eliminated from production builds, causing `memorio.global()` to throw `Cannot read properties of undefined (reading 'global')`.
9
12
 
10
13
  ## [5.1.2] - 2026-09-14
11
14
 
12
15
  ### Fixed
13
- - **`types/encryption.d.ts`**: Fixed parse error (`TS1005: ',' expected`) in `deriveKey` signature — missing comma after `alg` parameter caused all consumers' type checkers to fail, even with `skipLibCheck: true`.
16
+ - **`types/encryption.d.ts`**: Fixed parse error (`TS1005: ',' expected`) in `deriveKey` signature - missing comma after `alg` parameter caused all consumers' type checkers to fail, even with `skipLibCheck: true`.
14
17
  - **`core/platform.ts`**: `_isBun()` now checks `typeof globalAny.Bun.version === 'string'` instead of `typeof globalAny.Bun.sqlite === 'function'`, correctly detecting the Bun runtime so `hasBunSqlite()` returns `true` and the `bun:sqlite` backend loads.
15
18
  - **`functions/sqlite/tools/bun-sqlite-adapter.ts`**: Removed unused `_index` variable.
16
19
  - **`functions/security/index.ts`**: Removed redundant type re-export block that conflicted with inline `export type`/`export interface` declarations under `isolatedModules`.
@@ -18,7 +21,7 @@ All notable changes to this project will be documented in this file.
18
21
  - **`extension/vscode/esbuild.config.mjs`**: Replaced TypeScript type annotations with JSDoc `/** @type */` to fix parse errors in `.mjs` context.
19
22
 
20
23
  ### Packaging
21
- - **`package.json`**: Changed `"sideEffects": true` to `"sideEffects": ["./global.js", "./global.cjs"]` so tree-shaking works for consumers using only `state`/`store` — previously the entire engine (SQLite, memory, encryption) was bundled unconditionally.
24
+ - **`package.json`**: Changed `"sideEffects": true` to `"sideEffects": ["./global.js", "./global.cjs"]` so tree-shaking works for consumers using only `state`/`store` - previously the entire engine (SQLite, memory, encryption) was bundled unconditionally.
22
25
  - Added `types/bun-sqlite.d.ts` module declaration so `import('bun:sqlite')` resolves under TypeScript.
23
26
 
24
27
  ### Migration guide: globals change (v5 breaking change)
@@ -26,16 +29,16 @@ All notable changes to this project will be documented in this file.
26
29
  **Upgrading from 4.x:** the globals (`state`, `store`, `session`, `cache`, `idb`, `observer`, `useObserver`) are no longer installed by `import "memorio"`. Applications that use them without importing them must change one line:
27
30
 
28
31
  ```ts
29
- // 4.x — globals installed on import
32
+ // 4.x - globals installed on import
30
33
  import 'memorio'
31
34
 
32
- // 5.x — globals installed via the global entrypoint
35
+ // 5.x - globals installed via the global entrypoint
33
36
  import 'memorio/global'
34
37
  ```
35
38
 
36
39
  Nothing else changes. The named imports (`import { state } from 'memorio'`) work identically.
37
40
 
38
- **Bundler cache:** after upgrading, restart the dev server and clear `node_modules/.vite` (Vite) or `.next/cache` (Next.js), because a stale pre-bundle can mask the breakage — the app may appear to work and then fail at the next dependency re-optimisation.
41
+ **Bundler cache:** after upgrading, restart the dev server and clear `node_modules/.vite` (Vite) or `.next/cache` (Next.js), because a stale pre-bundle can mask the breakage - the app may appear to work and then fail at the next dependency re-optimisation.
39
42
 
40
43
  ## [5.1.0] - 2026-09-12
41
44
 
package/README.md CHANGED
@@ -4,7 +4,6 @@
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/memorio)](https://www.npmjs.com/package/memorio)
6
6
  [![license](https://img.shields.io/npm/l/memorio)](./LICENSE)
7
- [![bundle size](https://img.shields.io/bundlephobia/minzip/memorio)](https://bundlephobia.com/package/memorio)
8
7
  [![Socket Badge](https://socket.dev/api/badge/npm/package/memorio)](https://socket.dev/npm/package/memorio)
9
8
  [![Known Vulnerabilities](https://snyk.io/test/npm/memorio/badge.svg)](https://snyk.io/test/npm/memorio)
10
9
  [![zero deps](https://img.shields.io/badge/dependencies-0-brightgreen)](#security)
@@ -13,14 +12,10 @@
13
12
  ![Edge Workers](https://img.shields.io/badge/Edge%20Workers-compatible-gray)
14
13
  ![TypeScript](https://img.shields.io/badge/TypeScript-native-gray?logo=typescript)
15
14
  ![React](https://img.shields.io/badge/React-compatible-gray?logo=react)
16
- ![Tests](https://img.shields.io/badge/tests-500+%20passed-green)
17
15
  [![license](https://img.shields.io/npm/l/memorio.svg)](#license)
18
16
 
19
- <!--
20
- [![CI](https://img.shields.io/github/actions/workflow/status/GITHUB_ORG/GITHUB_REPO/ci.yml?branch=main)](https://github.com/GITHUB_ORG/GITHUB_REPO/actions)
21
- -->
22
17
 
23
- **The memory layer for AI agents and apps — owned by the user, not the vendor.**
18
+ **The memory layer for AI agents and apps - owned by the user, not the vendor.**
24
19
 
25
20
  ```ts
26
21
  import 'memorio/global'
@@ -41,13 +36,13 @@ Just data, available where your application needs it.
41
36
 
42
37
  That's the whole API for the simple case. No provider tree, no boilerplate.
43
38
 
44
- But real apps grow. Most AI-powered apps (and most apps in general) eventually need more than "just state" — persistence, session data, caches, structured browser storage, local SQL, application memory, history, optional sync. Usually that means pulling in a different library — and a different mental model — for each. Memorio gives them one consistent runtime, without making the simple case complicated:
39
+ But real apps grow. Most AI-powered apps (and most apps in general) eventually need more than "just state" - persistence, session data, caches, structured browser storage, local SQL, application memory, history, optional sync. Usually that means pulling in a different library - and a different mental model - for each. Memorio gives them one consistent runtime, without making the simple case complicated:
45
40
 
46
41
  ```ts
47
42
  state.value = 42
48
43
  ```
49
44
 
50
- But the reason memorio exists isn't just "fewer dependencies." It's this: **the memory an AI agent builds about a user shouldn't be locked inside a vendor's server, opaque, and impossible to inspect or move.** Memorio's memory layer is structured, inspectable, editable, and — when you turn on encryption — readable only with a key the vendor doesn't hold. It runs client-side, in the browser, on the edge, or in Node.
45
+ But the reason memorio exists isn't just "fewer dependencies." It's this: **the memory an AI agent builds about a user shouldn't be locked inside a vendor's server, opaque, and impossible to inspect or move.** Memorio's memory layer is structured, inspectable, editable, and - when you turn on encryption - readable only with a key the vendor doesn't hold. It runs client-side, in the browser, on the edge, or in Node.
51
46
 
52
47
  That said, this covers *structured* memory - preferences, decisions, facts with confidence and provenance - not semantic similarity search. If your agent needs "find things like this conversation," pair memorio with a dedicated vector store; see [`markdown/MEMORY.md`](./markdown/MEMORY.md).
53
48
 
@@ -84,17 +79,27 @@ Named imports (`import { state } from 'memorio'`) are unaffected. After upgradin
84
79
  Three things cover most apps - reactive state, persistence, and observation:
85
80
 
86
81
  ```ts
87
- import { state, persist, observer } from 'memorio'
82
+ import { state, observer } from 'memorio'
88
83
 
89
84
  state.user = { name: 'Sara' }
90
85
 
91
- persist('state.user')
86
+ const stopPersisting = state.persist('state.user')
92
87
 
93
88
  observer('state.user', user => {
94
89
  console.log(user)
95
90
  })
96
91
  ```
97
92
 
93
+ `state.persist()` creates a bidirectional mirror between `state` (live in-memory)
94
+ and `store` (durable copy). It seeds the initial value from `store` if present,
95
+ then every write to that path flows to `store` automatically. The return value is
96
+ an unsubscribe function - call it to stop the mirroring (the value already in
97
+ `store` is preserved):
98
+
99
+ ```ts
100
+ stopPersisting() // from here, state.user is in-memory only; store no longer updated
101
+ ```
102
+
98
103
  And this is what makes memorio a *memory* layer, not just a state manager - structured, inspectable facts about a user or agent, not just ephemeral UI state:
99
104
 
100
105
  ```ts
@@ -172,9 +177,9 @@ For most apps, that's enough. Everything below is an optional capability you can
172
177
  Memorio is upfront about what it doesn't do, so you don't find out the hard way:
173
178
 
174
179
  - **No encryption by default.** Data is stored in plain text unless you explicitly turn on `memorio.encryption` or pass an `encryptionKey`. See [`SECURITY.md`](./SECURITY.md).
175
- - **Namespaces/contexts aren't a security boundary.** `memorio.isolate()` gives you logical separation between tenants or sessions, not authorization or access control — enforce that at your application layer.
176
- - **SQLite persistence uses a journal + checkpoint strategy.** Individual writes are recorded in an append-only journal (incremental); periodic checkpoints snapshot the full database. This avoids serializing the entire database on every write. The `bun:sqlite` backend doesn't support `export()` but handles persistence natively — see [`markdown/SQLITE.md`](./markdown/SQLITE.md).
177
- - **`memory.context()` is rule-based, not embedding-based.** It ranks structured entries by recency, confidence, and type — it does not do semantic similarity search. Pair it with a vector store if your agent needs "find things like this."
180
+ - **Namespaces/contexts aren't a security boundary.** `memorio.isolate()` gives you logical separation between tenants or sessions, not authorization or access control - enforce that at your application layer.
181
+ - **SQLite persistence uses a journal + checkpoint strategy.** Individual writes are recorded in an append-only journal (incremental); periodic checkpoints snapshot the full database. This avoids serializing the entire database on every write. The `bun:sqlite` backend doesn't support `export()` but handles persistence natively - see [`markdown/SQLITE.md`](./markdown/SQLITE.md).
182
+ - **`memory.context()` is rule-based, not embedding-based.** It ranks structured entries by recency, confidence, and type - it does not do semantic similarity search. Pair it with a vector store if your agent needs "find things like this."
178
183
  - **Key management is your responsibility.** Memorio's encryption is client-side; it doesn't manage, rotate, or store keys for you.
179
184
 
180
185
  ---
@@ -1,60 +1,60 @@
1
- /**
2
- * sqlite-batched-writes.ts
3
- *
4
- * Scenario: importing or writing many rows into memorio's sqlite layer.
5
- *
6
- * With persistence enabled, each write is recorded in an append-only journal
7
- * (incremental). Periodic checkpoints — every 50 operations or 250 ms of
8
- * write-idle — take a full database snapshot. Calling `flush()` inside a
9
- * per-row loop triggers a checkpoint on every iteration, which is expensive.
10
- */
11
- import { memorio } from 'memorio'
12
-
13
- interface UserRow {
14
- id: number
15
- name: string
16
- role: string
17
- }
18
-
19
- export async function importUsers(users: UserRow[]) {
20
- await memorio.sqlite.ready
21
- await memorio.sqlite.db.create('app', { persistence: true })
22
-
23
- await memorio.sqlite.query.run(
24
- 'app',
25
- `CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, role TEXT)`
26
- )
27
-
28
- // Bad: this would trigger a full checkpoint after every single insert.
29
- //
30
- // for (const user of users) {
31
- // await memorio.sqlite.query.run('app', 'INSERT INTO users ...', [...])
32
- // await memorio.sqlite.db.flush('app') // <- don't do this per row
33
- // }
34
-
35
- // Good: run every insert first, flush once after the batch. Writes are
36
- // journaled incrementally (cheap); the checkpoint (full snapshot) happens
37
- // once at the end.
38
- for (const user of users) {
39
- await memorio.sqlite.query.run(
40
- 'app',
41
- `INSERT INTO users (id, name, role) VALUES (?, ?, ?)`,
42
- [user.id, user.name, user.role]
43
- )
44
- }
45
-
46
- // Flush explicitly, once, after the whole batch — check the installed
47
- // version's API for the exact flush/persist call name if it differs from
48
- // automatic persistence-on-close.
49
- const admins = await memorio.sqlite.query.select(
50
- 'app',
51
- `SELECT * FROM users WHERE role = ?`,
52
- ['admin']
53
- )
54
-
55
- return admins
56
- }
57
-
58
- // For read-heavy or scratch-space use where durability doesn't matter,
59
- // skip { persistence: true } entirely - sqlite runs in memory by default,
60
- // which avoids journal and checkpoint overhead altogether.
1
+ /**
2
+ * sqlite-batched-writes.ts
3
+ *
4
+ * Scenario: importing or writing many rows into memorio's sqlite layer.
5
+ *
6
+ * With persistence enabled, each write is recorded in an append-only journal
7
+ * (incremental). Periodic checkpoints - every 50 operations or 250 ms of
8
+ * write-idle - take a full database snapshot. Calling `flush()` inside a
9
+ * per-row loop triggers a checkpoint on every iteration, which is expensive.
10
+ */
11
+ import { memorio } from 'memorio'
12
+
13
+ interface UserRow {
14
+ id: number
15
+ name: string
16
+ role: string
17
+ }
18
+
19
+ export async function importUsers(users: UserRow[]) {
20
+ await memorio.sqlite.ready
21
+ await memorio.sqlite.db.create('app', { persistence: true })
22
+
23
+ await memorio.sqlite.query.run(
24
+ 'app',
25
+ `CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, role TEXT)`
26
+ )
27
+
28
+ // Bad: this would trigger a full checkpoint after every single insert.
29
+ //
30
+ // for (const user of users) {
31
+ // await memorio.sqlite.query.run('app', 'INSERT INTO users ...', [...])
32
+ // await memorio.sqlite.db.flush('app') // <- don't do this per row
33
+ // }
34
+
35
+ // Good: run every insert first, flush once after the batch. Writes are
36
+ // journaled incrementally (cheap); the checkpoint (full snapshot) happens
37
+ // once at the end.
38
+ for (const user of users) {
39
+ await memorio.sqlite.query.run(
40
+ 'app',
41
+ `INSERT INTO users (id, name, role) VALUES (?, ?, ?)`,
42
+ [user.id, user.name, user.role]
43
+ )
44
+ }
45
+
46
+ // Flush explicitly, once, after the whole batch - check the installed
47
+ // version's API for the exact flush/persist call name if it differs from
48
+ // automatic persistence-on-close.
49
+ const admins = await memorio.sqlite.query.select(
50
+ 'app',
51
+ `SELECT * FROM users WHERE role = ?`,
52
+ ['admin']
53
+ )
54
+
55
+ return admins
56
+ }
57
+
58
+ // For read-heavy or scratch-space use where durability doesn't matter,
59
+ // skip { persistence: true } entirely - sqlite runs in memory by default,
60
+ // which avoids journal and checkpoint overhead altogether.
package/global.cjs CHANGED
@@ -3327,19 +3327,39 @@ if (globalThis[STATE_INSTANCE_KEY]) {
3327
3327
  const initial = exports.store.get(key);
3328
3328
  if (initial !== void 0 && initial !== null) {
3329
3329
  let acc = exports.state;
3330
+ let seedOk = true;
3330
3331
  for (let i = 0; i < segments.length - 1; i++) {
3331
3332
  const seg = segments[i];
3332
- if (acc[seg] === void 0 || acc[seg] === null) acc[seg] = {};
3333
- acc = acc[seg];
3333
+ const existing = acc[seg];
3334
+ if (existing === void 0 || existing === null) {
3335
+ acc[seg] = {};
3336
+ acc = acc[seg];
3337
+ } else if (typeof existing === "object") {
3338
+ acc = existing;
3339
+ } else {
3340
+ if (exports.DEV) {
3341
+ console.warn(
3342
+ `[memorio state] state.persist('${normalized}') aborted seeding: '${seg}' already holds a primitive value (${typeof existing}), cannot nest '${segments.slice(i + 1).join(".")}' under it.`
3343
+ );
3344
+ }
3345
+ seedOk = false;
3346
+ break;
3347
+ }
3348
+ }
3349
+ if (seedOk && acc) {
3350
+ acc[segments[segments.length - 1]] = deepRaw(initial);
3334
3351
  }
3335
- if (acc) acc[segments[segments.length - 1]] = deepRaw(initial);
3336
3352
  }
3337
3353
  const handler = /* @__PURE__ */ __name(() => {
3338
3354
  let current = exports.state;
3339
3355
  for (const seg of segments) {
3340
3356
  current = current === void 0 || current === null ? void 0 : current[seg];
3341
3357
  }
3342
- exports.store.set(key, deepRaw(current));
3358
+ if (current === void 0) {
3359
+ exports.store.remove(key);
3360
+ } else {
3361
+ exports.store.set(key, deepRaw(current));
3362
+ }
3343
3363
  }, "handler");
3344
3364
  const off = dispatch.listen(normalized, handler);
3345
3365
  return typeof off === "function" ? off : () => {
@@ -3759,8 +3779,7 @@ var observerFunction = /* @__PURE__ */ __name((s, cb = null, option = true) => {
3759
3779
  }
3760
3780
  if (s && cb) {
3761
3781
  const resolvedPath = resolvePath(s);
3762
- if (!resolvedPath) return;
3763
- if (typeof s !== "string" && !(s && typeof s === "object")) {
3782
+ if (!resolvedPath) {
3764
3783
  console.error(`Observer Error: name of state need to be a 'string' like 'state.test' or a direct state value`);
3765
3784
  return;
3766
3785
  }
@@ -3810,7 +3829,7 @@ Object.defineProperties(
3810
3829
  * @returns void
3811
3830
  */
3812
3831
  removeAll: {
3813
- get: /* @__PURE__ */ __name(() => {
3832
+ value: /* @__PURE__ */ __name(() => {
3814
3833
  const obs = globalThis.observer;
3815
3834
  const list = obs?.list;
3816
3835
  if (list) {
@@ -3819,7 +3838,7 @@ Object.defineProperties(
3819
3838
  });
3820
3839
  }
3821
3840
  return true;
3822
- }, "get")
3841
+ }, "value")
3823
3842
  },
3824
3843
  ///
3825
3844
  /**
@@ -3849,40 +3868,58 @@ var useObserver = /* @__PURE__ */ __name(((func, deps = []) => {
3849
3868
  const unsubs = [];
3850
3869
  const depList = Array.isArray(deps) && !deps.__path ? deps : [deps];
3851
3870
  try {
3852
- depList.forEach((dep) => {
3853
- let t = "";
3854
- if (typeof dep === "function") {
3855
- const result = exports.propertyName(dep);
3856
- if (result) t = result;
3857
- } else if (typeof dep === "object" && dep !== null) {
3858
- t = dep.__path || (typeof dep.toString === "function" && dep.toString() !== "[object Object]" ? dep.toString() : "");
3871
+ if (depList.length === 0) {
3872
+ exports.internal.clearTrackedPaths();
3873
+ exports.internal.tracking = true;
3874
+ try {
3875
+ func();
3876
+ } catch (_) {
3859
3877
  }
3860
- if (t) {
3861
- t = t.replace(/^(\(\)\s*=>\s*)/, "").replace(/^globalThis\./, "").replace(/^state\./, "");
3862
- const path = t.startsWith("store.") || t.startsWith("state.") ? t : "state." + t;
3878
+ exports.internal.tracking = false;
3879
+ exports.internal.trackedPaths.forEach((path) => {
3863
3880
  if (!handlers.includes(path)) {
3881
+ message_default(`useObserver: [Auto-Discovery] -> "${path}"`);
3864
3882
  handlers.push(path);
3865
3883
  const off = dispatch.listen(path, () => func());
3866
3884
  if (typeof off === "function") unsubs.push({ path, off });
3867
3885
  }
3868
- } else {
3869
- exports.internal.clearTrackedPaths();
3870
- exports.internal.tracking = true;
3871
- try {
3872
- func();
3873
- } catch (_) {
3886
+ });
3887
+ } else {
3888
+ depList.forEach((dep) => {
3889
+ let t = "";
3890
+ if (typeof dep === "function") {
3891
+ const result = exports.propertyName(dep);
3892
+ if (result) t = result;
3893
+ } else if (typeof dep === "object" && dep !== null) {
3894
+ t = dep.__path || (typeof dep.toString === "function" && dep.toString() !== "[object Object]" ? dep.toString() : "");
3874
3895
  }
3875
- exports.internal.tracking = false;
3876
- exports.internal.trackedPaths.forEach((path) => {
3896
+ if (t) {
3897
+ t = t.replace(/^(\(\)\s*=>\s*)/, "").replace(/^globalThis\./, "").replace(/^state\./, "");
3898
+ const path = t.startsWith("store.") || t.startsWith("state.") ? t : "state." + t;
3877
3899
  if (!handlers.includes(path)) {
3878
- message_default(`useObserver: [Auto-Discovery] -> "${path}"`);
3879
3900
  handlers.push(path);
3880
3901
  const off = dispatch.listen(path, () => func());
3881
3902
  if (typeof off === "function") unsubs.push({ path, off });
3882
3903
  }
3883
- });
3884
- }
3885
- });
3904
+ } else {
3905
+ exports.internal.clearTrackedPaths();
3906
+ exports.internal.tracking = true;
3907
+ try {
3908
+ func();
3909
+ } catch (_) {
3910
+ }
3911
+ exports.internal.tracking = false;
3912
+ exports.internal.trackedPaths.forEach((path) => {
3913
+ if (!handlers.includes(path)) {
3914
+ message_default(`useObserver: [Auto-Discovery] -> "${path}"`);
3915
+ handlers.push(path);
3916
+ const off = dispatch.listen(path, () => func());
3917
+ if (typeof off === "function") unsubs.push({ path, off });
3918
+ }
3919
+ });
3920
+ }
3921
+ });
3922
+ }
3886
3923
  if (handlers.length > 0) {
3887
3924
  message_default(`useObserver: Monitoring ${handlers.length} paths.`);
3888
3925
  } else {
@@ -4808,7 +4845,7 @@ init_internal();
4808
4845
 
4809
4846
  // package.json
4810
4847
  var package_default = {
4811
- version: "5.1.3"};
4848
+ version: "5.1.4"};
4812
4849
 
4813
4850
  // core/global.ts
4814
4851
  init_env();
@@ -5484,19 +5521,22 @@ var memory = {
5484
5521
  async patch(key, patches) {
5485
5522
  const entry = await this._getEntry(key);
5486
5523
  if (!entry) return;
5524
+ const patchedValue = JSON.parse(JSON.stringify(entry.value ?? {}));
5487
5525
  let patched = false;
5488
5526
  for (const p of patches) {
5527
+ const relativePath = p.path.replace(/^[^.]+\./, "");
5489
5528
  if (p.op === "set") {
5490
- setNestedPath({ [entry.key]: entry.value }, p.path.replace(/^[^.]+\./, ""), p.value);
5529
+ setNestedPath(patchedValue, relativePath, p.value);
5491
5530
  patched = true;
5492
5531
  } else if (p.op === "delete") {
5493
- deleteNestedPath({ [entry.key]: entry.value }, p.path.replace(/^[^.]+\./, ""));
5532
+ deleteNestedPath(patchedValue, relativePath);
5494
5533
  patched = true;
5495
5534
  }
5496
5535
  }
5497
5536
  if (!patched) return;
5498
5537
  const updated = {
5499
5538
  ...entry,
5539
+ value: patchedValue,
5500
5540
  updatedAt: now(),
5501
5541
  lastConfirmedAt: now(),
5502
5542
  version: (entry.version ? entry.version : 0) + 1,
package/global.js CHANGED
@@ -3324,19 +3324,39 @@ if (globalThis[STATE_INSTANCE_KEY]) {
3324
3324
  const initial = store.get(key);
3325
3325
  if (initial !== void 0 && initial !== null) {
3326
3326
  let acc = state2;
3327
+ let seedOk = true;
3327
3328
  for (let i = 0; i < segments.length - 1; i++) {
3328
3329
  const seg = segments[i];
3329
- if (acc[seg] === void 0 || acc[seg] === null) acc[seg] = {};
3330
- acc = acc[seg];
3330
+ const existing = acc[seg];
3331
+ if (existing === void 0 || existing === null) {
3332
+ acc[seg] = {};
3333
+ acc = acc[seg];
3334
+ } else if (typeof existing === "object") {
3335
+ acc = existing;
3336
+ } else {
3337
+ if (DEV) {
3338
+ console.warn(
3339
+ `[memorio state] state.persist('${normalized}') aborted seeding: '${seg}' already holds a primitive value (${typeof existing}), cannot nest '${segments.slice(i + 1).join(".")}' under it.`
3340
+ );
3341
+ }
3342
+ seedOk = false;
3343
+ break;
3344
+ }
3345
+ }
3346
+ if (seedOk && acc) {
3347
+ acc[segments[segments.length - 1]] = deepRaw(initial);
3331
3348
  }
3332
- if (acc) acc[segments[segments.length - 1]] = deepRaw(initial);
3333
3349
  }
3334
3350
  const handler = /* @__PURE__ */ __name(() => {
3335
3351
  let current = state2;
3336
3352
  for (const seg of segments) {
3337
3353
  current = current === void 0 || current === null ? void 0 : current[seg];
3338
3354
  }
3339
- store.set(key, deepRaw(current));
3355
+ if (current === void 0) {
3356
+ store.remove(key);
3357
+ } else {
3358
+ store.set(key, deepRaw(current));
3359
+ }
3340
3360
  }, "handler");
3341
3361
  const off = dispatch.listen(normalized, handler);
3342
3362
  return typeof off === "function" ? off : () => {
@@ -3756,8 +3776,7 @@ var observerFunction = /* @__PURE__ */ __name((s, cb = null, option = true) => {
3756
3776
  }
3757
3777
  if (s && cb) {
3758
3778
  const resolvedPath = resolvePath(s);
3759
- if (!resolvedPath) return;
3760
- if (typeof s !== "string" && !(s && typeof s === "object")) {
3779
+ if (!resolvedPath) {
3761
3780
  console.error(`Observer Error: name of state need to be a 'string' like 'state.test' or a direct state value`);
3762
3781
  return;
3763
3782
  }
@@ -3807,7 +3826,7 @@ Object.defineProperties(
3807
3826
  * @returns void
3808
3827
  */
3809
3828
  removeAll: {
3810
- get: /* @__PURE__ */ __name(() => {
3829
+ value: /* @__PURE__ */ __name(() => {
3811
3830
  const obs = globalThis.observer;
3812
3831
  const list = obs?.list;
3813
3832
  if (list) {
@@ -3816,7 +3835,7 @@ Object.defineProperties(
3816
3835
  });
3817
3836
  }
3818
3837
  return true;
3819
- }, "get")
3838
+ }, "value")
3820
3839
  },
3821
3840
  ///
3822
3841
  /**
@@ -3846,40 +3865,58 @@ var useObserver = /* @__PURE__ */ __name(((func, deps = []) => {
3846
3865
  const unsubs = [];
3847
3866
  const depList = Array.isArray(deps) && !deps.__path ? deps : [deps];
3848
3867
  try {
3849
- depList.forEach((dep) => {
3850
- let t = "";
3851
- if (typeof dep === "function") {
3852
- const result = propertyName(dep);
3853
- if (result) t = result;
3854
- } else if (typeof dep === "object" && dep !== null) {
3855
- t = dep.__path || (typeof dep.toString === "function" && dep.toString() !== "[object Object]" ? dep.toString() : "");
3868
+ if (depList.length === 0) {
3869
+ internal_default.clearTrackedPaths();
3870
+ internal_default.tracking = true;
3871
+ try {
3872
+ func();
3873
+ } catch (_) {
3856
3874
  }
3857
- if (t) {
3858
- t = t.replace(/^(\(\)\s*=>\s*)/, "").replace(/^globalThis\./, "").replace(/^state\./, "");
3859
- const path2 = t.startsWith("store.") || t.startsWith("state.") ? t : "state." + t;
3875
+ internal_default.tracking = false;
3876
+ internal_default.trackedPaths.forEach((path2) => {
3860
3877
  if (!handlers.includes(path2)) {
3878
+ message_default(`useObserver: [Auto-Discovery] -> "${path2}"`);
3861
3879
  handlers.push(path2);
3862
3880
  const off = dispatch.listen(path2, () => func());
3863
3881
  if (typeof off === "function") unsubs.push({ path: path2, off });
3864
3882
  }
3865
- } else {
3866
- internal_default.clearTrackedPaths();
3867
- internal_default.tracking = true;
3868
- try {
3869
- func();
3870
- } catch (_) {
3883
+ });
3884
+ } else {
3885
+ depList.forEach((dep) => {
3886
+ let t = "";
3887
+ if (typeof dep === "function") {
3888
+ const result = propertyName(dep);
3889
+ if (result) t = result;
3890
+ } else if (typeof dep === "object" && dep !== null) {
3891
+ t = dep.__path || (typeof dep.toString === "function" && dep.toString() !== "[object Object]" ? dep.toString() : "");
3871
3892
  }
3872
- internal_default.tracking = false;
3873
- internal_default.trackedPaths.forEach((path2) => {
3893
+ if (t) {
3894
+ t = t.replace(/^(\(\)\s*=>\s*)/, "").replace(/^globalThis\./, "").replace(/^state\./, "");
3895
+ const path2 = t.startsWith("store.") || t.startsWith("state.") ? t : "state." + t;
3874
3896
  if (!handlers.includes(path2)) {
3875
- message_default(`useObserver: [Auto-Discovery] -> "${path2}"`);
3876
3897
  handlers.push(path2);
3877
3898
  const off = dispatch.listen(path2, () => func());
3878
3899
  if (typeof off === "function") unsubs.push({ path: path2, off });
3879
3900
  }
3880
- });
3881
- }
3882
- });
3901
+ } else {
3902
+ internal_default.clearTrackedPaths();
3903
+ internal_default.tracking = true;
3904
+ try {
3905
+ func();
3906
+ } catch (_) {
3907
+ }
3908
+ internal_default.tracking = false;
3909
+ internal_default.trackedPaths.forEach((path2) => {
3910
+ if (!handlers.includes(path2)) {
3911
+ message_default(`useObserver: [Auto-Discovery] -> "${path2}"`);
3912
+ handlers.push(path2);
3913
+ const off = dispatch.listen(path2, () => func());
3914
+ if (typeof off === "function") unsubs.push({ path: path2, off });
3915
+ }
3916
+ });
3917
+ }
3918
+ });
3919
+ }
3883
3920
  if (handlers.length > 0) {
3884
3921
  message_default(`useObserver: Monitoring ${handlers.length} paths.`);
3885
3922
  } else {
@@ -4805,7 +4842,7 @@ init_internal();
4805
4842
 
4806
4843
  // package.json
4807
4844
  var package_default = {
4808
- version: "5.1.3"};
4845
+ version: "5.1.4"};
4809
4846
 
4810
4847
  // core/global.ts
4811
4848
  init_env();
@@ -5481,19 +5518,22 @@ var memory = {
5481
5518
  async patch(key, patches) {
5482
5519
  const entry = await this._getEntry(key);
5483
5520
  if (!entry) return;
5521
+ const patchedValue = JSON.parse(JSON.stringify(entry.value ?? {}));
5484
5522
  let patched = false;
5485
5523
  for (const p of patches) {
5524
+ const relativePath = p.path.replace(/^[^.]+\./, "");
5486
5525
  if (p.op === "set") {
5487
- setNestedPath({ [entry.key]: entry.value }, p.path.replace(/^[^.]+\./, ""), p.value);
5526
+ setNestedPath(patchedValue, relativePath, p.value);
5488
5527
  patched = true;
5489
5528
  } else if (p.op === "delete") {
5490
- deleteNestedPath({ [entry.key]: entry.value }, p.path.replace(/^[^.]+\./, ""));
5529
+ deleteNestedPath(patchedValue, relativePath);
5491
5530
  patched = true;
5492
5531
  }
5493
5532
  }
5494
5533
  if (!patched) return;
5495
5534
  const updated = {
5496
5535
  ...entry,
5536
+ value: patchedValue,
5497
5537
  updatedAt: now(),
5498
5538
  lastConfirmedAt: now(),
5499
5539
  version: (entry.version ? entry.version : 0) + 1,