memorio 5.1.2 → 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,9 +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 serializes the entire database on every flush** - not incremental. Never
73
- generate code that calls a persisting write inside a loop; batch writes in one transaction/run
74
- 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.
75
73
  - **`observer('state.some.path', cb)` paths are plain strings, unchecked against `state`'s actual
76
74
  shape.** A typo or a later rename fails *silently* - the observer just never fires again. Prefer
77
75
  `memorio.typed<T>()` + `registerSchema()` for anything you'd hate to have silently stop working,
package/CHANGELOG.md ADDED
@@ -0,0 +1,83 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [Unreleased]
6
+
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.
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')`.
12
+
13
+ ## [5.1.2] - 2026-09-14
14
+
15
+ ### Fixed
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`.
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.
18
+ - **`functions/sqlite/tools/bun-sqlite-adapter.ts`**: Removed unused `_index` variable.
19
+ - **`functions/security/index.ts`**: Removed redundant type re-export block that conflicted with inline `export type`/`export interface` declarations under `isolatedModules`.
20
+ - **`extension/vscode/src/astIndexer.ts`**: Removed unused imports (`path`, `StateUsage`, `StatePathInfo`, `StoreOperation`) and unused `lines`/`program` parameters.
21
+ - **`extension/vscode/esbuild.config.mjs`**: Replaced TypeScript type annotations with JSDoc `/** @type */` to fix parse errors in `.mjs` context.
22
+
23
+ ### Packaging
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.
25
+ - Added `types/bun-sqlite.d.ts` module declaration so `import('bun:sqlite')` resolves under TypeScript.
26
+
27
+ ### Migration guide: globals change (v5 breaking change)
28
+
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:
30
+
31
+ ```ts
32
+ // 4.x - globals installed on import
33
+ import 'memorio'
34
+
35
+ // 5.x - globals installed via the global entrypoint
36
+ import 'memorio/global'
37
+ ```
38
+
39
+ Nothing else changes. The named imports (`import { state } from 'memorio'`) work identically.
40
+
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.
42
+
43
+ ## [5.1.0] - 2026-09-12
44
+
45
+ ### Added
46
+ - **Bun runtime support**: `memorio.isBun()` and `memorio.getCapabilities().hasBunSqlite` for runtime detection alongside `isBrowser()`/`isNode()`/`isDeno()`/`isEdge()`.
47
+ - **`bun:sqlite` backend**: SQLite operations under Bun use the native `bun:sqlite` binding - no WASM, real incremental writes, no full-database serialization on flush. Falls back to `sql.js` (browser) where Bun sqlite is unavailable. No changes to consumers: the same `sqlite.db.create()`, `sqlite.query.run()`, `sqlite.query.select()` APIs work unchanged.
48
+ - **Bun dev toolchain**: `bunfig.toml`, `test:bun`, `build:bun`, `lint:bun`, `tsc:bun` scripts. Existing `npm`/`npx` scripts remain as fallbacks. `bun` added to `devDependencies` only (not `dependencies`/`peerDependencies`).
49
+ - **`security.sanitizeValue()`**: recursive sanitization with HTML entity decode, URL decode, XSS scheme blocking, and `__proto__`/`constructor`/`prototype` key stripping.
50
+ - **`security.RBAC`**: `addAccessRule()`, `hasPermission()` with regex/function patterns, fail-closed by default when no rule matches.
51
+ - **`security.audit logging`**: `setAuditLogger()`, `logAudit()`, `isAuditActive()`. `hasPermission()` auto-logs denied attempts when an audit logger is configured.
52
+ - **`computed` module**: lazy-memoized derived state with dependency tracking (`compute()`, `getValue()`, `subscribe()`, `remove()`, `list()`, `clear()`).
53
+ - **`broadcast` module**: cross-tab sync via `BroadcastChannel` + `storage` event fallback (`install()`, `uninstall()`, `isInstalled()`).
54
+ - **PBKDF2 600k iterations** (NIST SP 800-132), up from 100k. `deriveKey()` accepts `iterations` parameter.
55
+ - **`encryption.exportKey()` / `encryption.importKey()`**: base64 key export/import with length validation.
56
+ - **`store.config()` / `session.config()`**: accept `onError`, `maxObjectSize`, `maxTotalSize` alongside existing `encryptionKey`.
57
+
58
+ ### Security
59
+ - PBKDF2 key derivation upgraded to 600,000 iterations (NIST SP 800-132 recommendation).
60
+ - `sanitizeValue` now strips dangerous URL schemes (`javascript:`, `vbscript:`, `data:text/html`, `about:blank`, `chrome:`) without word-boundary requirements, catching encoded variants like `%6javascript:`.
61
+ - `sanitizeValue` now handles `<object>`, `<embed>`, `<svg>`, `<iframe>`, `<script>` tag removal including void elements and nested payloads.
62
+ - `sanitizeValue` skips `__proto__`, `constructor`, `prototype` keys to prevent prototype pollution.
63
+ - `encryption.importKey()` validates AES-256 key length (32 bytes) and throws descriptive error for truncated input.
64
+ - RBAC `hasPermission()` fail-closed: no matching rule → denied + auto-audit-logged.
65
+ - `AuditEntry.action` type extended to include `'read'`, `'write'`, `'admin'`, `'permission_check'`.
66
+
67
+ ### Documentation
68
+ - README restyled: tagline "The memory layer for AI agents and apps - owned by the user, not the vendor."
69
+ - README: "Application memory" section moved before "Global or explicit".
70
+ - README: "Skip this unless..." callout lines added to 10 skippable sections.
71
+ - README: layers table gained "When" column (Start here / Add later).
72
+ - README: Cross-platform table gained Bun column with `bun:sqlite` native support.
73
+ - README: Quick reading map added after Install.
74
+ - README: "This page covers everything... Most apps only need the first three sections." callout after first example.
75
+ - SECURITY.md: documented Web Crypto async/non-blocking behavior.
76
+ - SECURITY.md: PBKDF2 iteration count updated to 600k.
77
+ - SECURITY-HARDENING.md: checklist expanded to 16 items.
78
+ - CHANGELOG.md: this file.
79
+
80
+ ### Developer experience
81
+ - Lint check test (`lint-check.test.ts`) integrated into test suite.
82
+ - `tests/bun/` directory for Bun-specific tests (`bun:test` imports).
83
+ - `tests/vitest/tests/` for Node.js/Vitest tests (unchanged).
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
 
@@ -63,6 +58,20 @@ npm install memorio
63
58
 
64
59
  Zero production dependencies in core. Optional integrations (`react`, `sql.js`, etc.) are installed only when you use them.
65
60
 
61
+ ### Globals migration (v5)
62
+
63
+ If you are upgrading from memorio 4.x and your code uses `state`, `store`, `session`, `cache`, `observer`, or `useObserver` as **bare globals** (without importing them), change one line:
64
+
65
+ ```ts
66
+ // 4.x
67
+ import 'memorio'
68
+
69
+ // 5.x
70
+ import 'memorio/global'
71
+ ```
72
+
73
+ Named imports (`import { state } from 'memorio'`) are unaffected. After upgrading, restart your dev server and clear `node_modules/.vite` / `.next/cache` to avoid stale pre-bundles masking the change. See [`markdown/IMPORT.md`](./markdown/IMPORT.md) for full details.
74
+
66
75
  ---
67
76
 
68
77
  ## Start here
@@ -70,17 +79,27 @@ Zero production dependencies in core. Optional integrations (`react`, `sql.js`,
70
79
  Three things cover most apps - reactive state, persistence, and observation:
71
80
 
72
81
  ```ts
73
- import { state, persist, observer } from 'memorio'
82
+ import { state, observer } from 'memorio'
74
83
 
75
84
  state.user = { name: 'Sara' }
76
85
 
77
- persist('state.user')
86
+ const stopPersisting = state.persist('state.user')
78
87
 
79
88
  observer('state.user', user => {
80
89
  console.log(user)
81
90
  })
82
91
  ```
83
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
+
84
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:
85
104
 
86
105
  ```ts
@@ -148,7 +167,6 @@ For most apps, that's enough. Everything below is an optional capability you can
148
167
  ### Other
149
168
  | Topic | Reference |
150
169
  | --- | --- |
151
- | Version history | [`CHANGELOG.md`](./CHANGELOG.md) |
152
170
  | Working examples - each file has a `Run:` comment at the top (typically `npx ts-node examples/<name>.ts`, or `npx ts-node --esm examples/<name>.tsx` for React examples) | [`examples/`](./examples/) |
153
171
  | Architecture decisions | [`adr/`](./adr/) |
154
172
 
@@ -159,9 +177,9 @@ For most apps, that's enough. Everything below is an optional capability you can
159
177
  Memorio is upfront about what it doesn't do, so you don't find out the hard way:
160
178
 
161
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).
162
- - **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.
163
- - **SQLite persistence (`sql.js` backend) isn't incremental.** Each write exports and re-saves the whole database, which gets costly as data grows. The `bun:sqlite` backend avoids this cost but doesn't support `export()` at all — see [`markdown/SQLITE.md`](./markdown/SQLITE.md).
164
- - **`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."
165
183
  - **Key management is your responsibility.** Memorio's encryption is client-side; it doesn't manage, rotate, or store keys for you.
166
184
 
167
185
  ---
package/SUMMARY.md CHANGED
@@ -17,7 +17,7 @@
17
17
  | `react-observer.tsx` | `useObserver` in due modalità (auto-discovery vs deps espliciti) combinato con `typed<T>()` e `registerSchema()`. |
18
18
  | `semantic-memory.ts` | Memoria applicativa per un'app LLM-backed: remember/update con confidence e source, retrieval con `memory.context()` - con nota esplicita che non è ricerca semantica per embedding. |
19
19
  | `session-advanced.ts` | Uso avanzato di `session`: auth token, bozza di form, carrello, dimensione dello storage. |
20
- | `sqlite-batched-writes.ts` | Come evitare di serializzare l'intero DB SQLite ad ogni riga: batch di insert seguito da un solo flush. |
20
+ | `sqlite-batched-writes.ts` | Con il journal+checkpoint, i write sono incrementali ma i checkpoint (snapshot completo) sono costosi: batch di insert seguito da un solo flush. |
21
21
  | `state-advanced.ts` | Stato annidato, array, locking di un valore (`.lock()`), path tracking, rimozione stato. |
22
22
  | `store-advanced.ts` | `store` avanzato: persistenza, quota, alias dei metodi, gestione errori, serializzazione di vari tipi. |
23
23
  | `sync.ts` *(nuovo)* | Local-first sync: configurazione di un `SyncProvider` (push/pull/resolve), scope `device`/`user`/`shared`, ispezione e replay del journal. |
@@ -47,7 +47,7 @@
47
47
  | `SCHEMA.md` | Validazione runtime dei percorsi di stato: schema oggetto/array/enum/funzione custom. |
48
48
  | `SECURITY.md` | Postura di sicurezza del progetto (minacce coperte, cosa NON fa Memorio, come viene gestito l'accesso ai dati). |
49
49
  | `SESSION.md` | Reference di `session` (sessionStorage) con fallback in-memory fuori dal browser. |
50
- | `SQLITE.md` | Reference del layer SQLite via `sql.js`: caricamento lazy, persistenza, query. |
50
+ | `SQLITE.md` | Reference del layer SQLite via `sql.js`/`bun:sqlite`: caricamento lazy, persistenza journal+checkpoint, query. |
51
51
  | `STATE.md` | Reference dello stato reattivo basato su Proxy - il layer centrale di Memorio. |
52
52
  | `STORE.md` | Reference di `store` (localStorage) con fallback in-memory fuori dal browser. |
53
53
  | `SYNC.md` | Sincronizzazione local-first opzionale: scope, journal, conflict resolution, strategie avanzate per multi-device (HLC, tombstones, fractional indexing). |
@@ -1,57 +1,60 @@
1
- /**
2
- * sqlite-batched-writes.ts
3
- *
4
- * Scenario: importing or writing many rows into memorio's sqlite layer.
5
- *
6
- * Persistence there serializes the ENTIRE database on every flush - not
7
- * incremental. Calling a persisting write inside a per-row loop is the
8
- * single most common way to accidentally make this layer slow.
9
- */
10
- import { memorio } from 'memorio'
11
-
12
- interface UserRow {
13
- id: number
14
- name: string
15
- role: string
16
- }
17
-
18
- export async function importUsers(users: UserRow[]) {
19
- await memorio.sqlite.ready
20
- await memorio.sqlite.db.create('app', { persistence: true })
21
-
22
- await memorio.sqlite.query.run(
23
- 'app',
24
- `CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, role TEXT)`
25
- )
26
-
27
- // Bad: this would serialize the whole DB after every single insert.
28
- //
29
- // for (const user of users) {
30
- // await memorio.sqlite.query.run('app', 'INSERT INTO users ...', [...])
31
- // await memorio.sqlite.db.flush('app') // <- don't do this per row
32
- // }
33
-
34
- // Good: run every insert first, persist once after the batch.
35
- for (const user of users) {
36
- await memorio.sqlite.query.run(
37
- 'app',
38
- `INSERT INTO users (id, name, role) VALUES (?, ?, ?)`,
39
- [user.id, user.name, user.role]
40
- )
41
- }
42
-
43
- // Persist explicitly, once, after the whole batch - check the installed
44
- // version's API for the exact flush/persist call name if it differs from
45
- // automatic persistence-on-close.
46
- const admins = await memorio.sqlite.query.select(
47
- 'app',
48
- `SELECT * FROM users WHERE role = ?`,
49
- ['admin']
50
- )
51
-
52
- return admins
53
- }
54
-
55
- // For read-heavy or scratch-space use where durability doesn't matter,
56
- // skip { persistence: true } entirely - sqlite runs in memory by default,
57
- // which avoids the serialize cost 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.