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 +1 -1
- package/CHANGELOG.md +9 -6
- package/README.md +18 -13
- package/examples/sqlite-batched-writes.ts +60 -60
- package/global.cjs +73 -33
- package/global.js +73 -33
- package/index.cjs +73 -33
- package/index.js +73 -33
- package/llms.txt +13 -0
- package/markdown/MEMORY.md +27 -0
- package/markdown/SQLITE.md +2 -2
- package/markdown/SYNC.md +1 -1
- package/modules/redux.cjs +29 -6
- package/modules/redux.cjs.map +1 -1
- package/modules/redux.js +29 -6
- package/modules/redux.js.map +1 -1
- package/package.json +1 -1
- package/types/state.d.ts +19 -5
- package/vsix/memorio.vsix +0 -0
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
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
32
|
+
// 4.x - globals installed on import
|
|
30
33
|
import 'memorio'
|
|
31
34
|
|
|
32
|
-
// 5.x
|
|
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
|
|
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
|
[](https://www.npmjs.com/package/memorio)
|
|
6
6
|
[](./LICENSE)
|
|
7
|
-
[](https://bundlephobia.com/package/memorio)
|
|
8
7
|
[](https://socket.dev/npm/package/memorio)
|
|
9
8
|
[](https://snyk.io/test/npm/memorio)
|
|
10
9
|
[](#security)
|
|
@@ -13,14 +12,10 @@
|
|
|
13
12
|

|
|
14
13
|

|
|
15
14
|

|
|
16
|
-

|
|
17
15
|
[](#license)
|
|
18
16
|
|
|
19
|
-
<!--
|
|
20
|
-
[](https://github.com/GITHUB_ORG/GITHUB_REPO/actions)
|
|
21
|
-
-->
|
|
22
17
|
|
|
23
|
-
**The memory layer for AI agents and apps
|
|
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"
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
177
|
-
- **`memory.context()` is rule-based, not embedding-based.** It ranks structured entries by recency, confidence, and type
|
|
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
|
|
8
|
-
* write-idle
|
|
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
|
|
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
|
-
|
|
3333
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
}, "
|
|
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.
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
}
|
|
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
|
-
|
|
3861
|
-
|
|
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
|
-
}
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
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
|
-
|
|
3876
|
-
|
|
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.
|
|
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(
|
|
5529
|
+
setNestedPath(patchedValue, relativePath, p.value);
|
|
5491
5530
|
patched = true;
|
|
5492
5531
|
} else if (p.op === "delete") {
|
|
5493
|
-
deleteNestedPath(
|
|
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
|
-
|
|
3330
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
}, "
|
|
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.
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
}
|
|
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
|
-
|
|
3858
|
-
|
|
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
|
-
}
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
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
|
-
|
|
3873
|
-
|
|
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.
|
|
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(
|
|
5526
|
+
setNestedPath(patchedValue, relativePath, p.value);
|
|
5488
5527
|
patched = true;
|
|
5489
5528
|
} else if (p.op === "delete") {
|
|
5490
|
-
deleteNestedPath(
|
|
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,
|