memorio 4.7.1 → 4.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
  ![Tests](https://img.shields.io/badge/tests-101%20passed-green)
14
14
  ![License](https://img.shields.io/badge/License-MIT-gray)
15
15
 
16
- ### One import. Global state, persistence, and a IndexedDB layer done.
16
+ ### One import. Global state, persistence, and a IndexedDB layer - done.
17
17
 
18
18
  ```javascript
19
19
  import 'memorio'
@@ -21,7 +21,7 @@ import 'memorio'
21
21
  state.user = { name: 'Sara' } // reactive, everywhere, instantly
22
22
  ```
23
23
 
24
- No provider tree. No reducers. No actions to dispatch before you can change a number. If you've ever set up a global store for an app that just needed *"a value, shared, that updates the UI when it changes,"* memorio is the toolkit for that thirty-second job and it keeps growing with you: session storage, a cache, typed IndexedDB tables, an observer system, a React hook, devtools, and a change logger, all sharing one mental model.
24
+ No provider tree. No reducers. No actions to dispatch before you can change a number. If you've ever set up a global store for an app that just needed *"a value, shared, that updates the UI when it changes,"* memorio is the toolkit for that thirty-second job - and it keeps growing with you: session storage, a cache, typed IndexedDB tables, an observer system, a React hook, devtools, and a change logger, all sharing one mental model.
25
25
 
26
26
  ---
27
27
 
@@ -31,26 +31,28 @@ No provider tree. No reducers. No actions to dispatch before you can change a nu
31
31
  2. [Installation](#installation)
32
32
  3. [Quick Start](#quick-start)
33
33
  4. [API Reference](#api-reference)
34
- 5. [Cross-Platform Behavior](#cross-platform-behavior)
35
- 6. [Context Isolation (multi-tenant)](#context-isolation-multi-tenant)
36
- 7. [Security](#security)
37
- 8. [License](#license)
34
+ 5. [Typed Stores](#typed-stores)
35
+ 6. [Schema Validation](#schema-validation)
36
+ 7. [Cross-Platform Behavior](#cross-platform-behavior)
37
+ 8. [Context Isolation (multi-tenant)](#context-isolation-multi-tenant)
38
+ 9. [Security](#security)
39
+ 10. [License](#license)
38
40
 
39
41
  ---
40
42
 
41
43
  ## Is this for you?
42
44
 
43
- memorio is built for speed of thought: you think "I need this value everywhere," you write one line, it works. That's the entire pitch and it's a real one, not a rewritten complaint about how much Redux boilerplate you've had to write.
45
+ memorio is built for speed of thought: you think "I need this value everywhere," you write one line, it works. That's the entire pitch - and it's a real one, not a rewritten complaint about how much Redux boilerplate you've had to write.
44
46
 
45
47
  **Reach for memorio when:**
46
- - You want state that's simply *there* no store setup, no provider wrapping your app.
48
+ - You want state that's simply *there* - no store setup, no provider wrapping your app.
47
49
  - You want `localStorage`, `sessionStorage`, and `IndexedDB` behind one consistent API instead of learning three.
48
50
  - You're prototyping, building an internal tool, or shipping something small-to-medium where iteration speed matters more than architectural ceremony.
49
51
 
50
52
  **Reach for something else when:**
51
- - You need every state path statically type-checked against every observer memorio's `state.foo.bar` and `observer('state.foo.bar', ...)` are connected by a string, not by the compiler, so a rename won't be caught for you.
52
- - You need Redux-style middleware, action logs, or time-travel debugging as a hard requirement for a large team those tools exist because that discipline solves real problems at scale, not because their authors enjoy boilerplate.
53
- - Your isolation requirements are a security boundary, not a convenience see [Context Isolation](#context-isolation-multi-tenant) before you rely on it for that.
53
+ - You need every state path statically type-checked against every observer - memorio's `state.foo.bar` and `observer('state.foo.bar', ...)` are connected by a string, not by the compiler, so a rename won't be caught for you. However, if you want compiler-checked state access, use `memorio.typed<T>()` (see [Typed Stores](#typed-stores)) which gives you full static types on the global `state` proxy. For runtime validation as well, combine with `memorio.registerSchema()` (see [Schema Validation](#schema-validation)).
54
+ - You need Redux-style middleware, action logs, or time-travel debugging as a hard requirement for a large team - those tools exist because that discipline solves real problems at scale, not because their authors enjoy boilerplate. Memorio logs through `memorio.logger` and inspects via `memorio.devtools`, but doesn't have Redux DevTools time-travel.
55
+ - Your isolation requirements are a security boundary, not a convenience - see [Context Isolation](#context-isolation-multi-tenant) before you rely on it for that.
54
56
 
55
57
  Pick the right tool. memorio is at its best when the answer to "how much state architecture do I need here?" is genuinely "not much."
56
58
 
@@ -63,15 +65,18 @@ npm i memorio
63
65
  # pnpm add memorio
64
66
  # yarn add memorio
65
67
 
66
- # Optional only if you use the React hook
68
+ # Optional - only if you use the React hook
67
69
  npm i react react-dom
70
+
71
+ # Optional - only if you want a local sql.js for `sqlite` (default uses CDN)
72
+ npm i sql.js
68
73
  ```
69
74
 
70
75
  ---
71
76
 
72
77
  ## Quick Start
73
78
 
74
- ### Global style the whole point of memorio
79
+ ### Global style - the whole point of memorio
75
80
 
76
81
  ```typescript
77
82
  import 'memorio' // once, at your entry point
@@ -85,7 +90,7 @@ useObserver(
85
90
  )
86
91
  ```
87
92
 
88
- ### Named imports same instances, explicit about it
93
+ ### Named imports - same instances, explicit about it
89
94
 
90
95
  ```typescript
91
96
  import { state, store, session, cache, idb, observer, useObserver, dispatch, memorio } from 'memorio'
@@ -102,13 +107,13 @@ function Counter() {
102
107
  }
103
108
  ```
104
109
 
105
- Two styles, one engine underneath use whichever reads better in your codebase.
110
+ Two styles, one engine underneath - use whichever reads better in your codebase.
106
111
 
107
112
  ---
108
113
 
109
114
  ## API Reference
110
115
 
111
- ### `state` reactive, volatile, Proxy-based
116
+ ### `state` - reactive, volatile, Proxy-based
112
117
 
113
118
  ```javascript
114
119
  state.user = { name: 'Sara', role: 'admin' }
@@ -125,7 +130,7 @@ state.config.maxUsers = 200 // throws: state 'config' is locked
125
130
  state.config.unlock()
126
131
  ```
127
132
 
128
- ### `store` the value that survives a refresh
133
+ ### `store` - the value that survives a refresh
129
134
 
130
135
  ```javascript
131
136
  store.set('preferences', { theme: 'dark' })
@@ -133,21 +138,21 @@ store.get('preferences') // { theme: 'dark' } or null
133
138
  store.isPersistent // true when backed by real localStorage
134
139
  ```
135
140
 
136
- ### `session` lives as long as the tab does
141
+ ### `session` - lives as long as the tab does
137
142
 
138
143
  ```javascript
139
144
  session.set('token', 'user-abc-123')
140
145
  session.get('token')
141
146
  ```
142
147
 
143
- ### `cache` the fastest thing you own, gone on refresh
148
+ ### `cache` - the fastest thing you own, gone on refresh
144
149
 
145
150
  ```javascript
146
151
  cache.set('temp', computeExpensiveResult())
147
152
  cache.get('temp')
148
153
  ```
149
154
 
150
- ### `idb` typed, async, structured tables without the ceremony
155
+ ### `idb` - typed, async, structured tables without the ceremony
151
156
 
152
157
  ```javascript
153
158
  await idb.db.create('my-db')
@@ -156,9 +161,91 @@ await idb.data.set('my-db', 'users', { id: 1, name: 'Sara' })
156
161
  const user = await idb.data.get('my-db', 'users', 1)
157
162
  ```
158
163
 
159
- > IndexedDB is a browser-only primitive see [Cross-Platform Behavior](#cross-platform-behavior) for what happens off the browser.
164
+ > IndexedDB is a browser-only primitive - see [Cross-Platform Behavior](#cross-platform-behavior) for what happens off the browser.
165
+
166
+ ### `sqlite` - in-memory SQLite, zero config
167
+
168
+ `memorio.sqlite` is an optional, lazily-loaded SQLite engine backed by [`sql.js`](https://sql.js.org) (the optional `sql.js` npm package is **not** a build-time dependency). It runs entirely in memory — ideal for ad-hoc SQL, structured queryable data, and local relational lookups. The engine is fetched on first use and never loaded when you don't touch `sqlite`.
169
+
170
+ ```javascript
171
+ import 'memorio' // or: import { sqlite } from 'memorio'
172
+
173
+ await sqlite.ready // waits until the sql.js engine is loaded (or rejected)
174
+ sqlite.db.support() // true when the current platform can run sql.js
175
+ ```
176
+
177
+ Bases (databases) are addressed by name, like `idb`:
178
+ const SQL = await sqlite.db.getSQL() // the underlying sql.js engine (loaded once, cached)
179
+ sqlite.db.version() // engine version string, or null before load
180
+ ```
181
+
182
+ Bases (databases) are addressed by name, like `idb`. `sqlite.db.create('app')` opens (or creates) an in-memory database, while `sqlite.db.get('app')` retrieves an already-open handle (`sqlite.db.list/delete/size` round out the lifecycle):
183
+
184
+ ```javascript
185
+ await sqlite.db.create('app') // in-memory database handle
186
+ await sqlite.query.run('app', 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)')
187
+ ``` `sqlite.data` gives a small shortcut layer, and `sqlite.query` runs SQL:
188
+
189
+ ```javascript
190
+ // Write (shortcut over raw prepare/bind)
191
+ await sqlite.data.set('app', 'users', { id: 1, name: 'Sara' })
192
+ await sqlite.data.get('app', 'users', 1) // { id: 1, name: 'Sara' }
193
+
194
+ // SQL
195
+ await sqlite.query.run('app', 'CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)')
196
+ const rows = await sqlite.query.select('app', 'SELECT * FROM items', [1])
197
+
198
+ // Portability: export / import a whole base as a binary blob
199
+ const blob = await sqlite.db.export('app')
200
+ await sqlite.db.import('app', blob)
201
+
202
+ // Clean up
203
+ await sqlite.db.delete('app')
204
+ ```
205
+
206
+ #### Loading the engine
207
+
208
+ The default loader injects the sql.js UMD build (`sql-wasm-browser.js`) from the
209
+ jsDelivr CDN as a classic `<script>`; on load it exposes the `initSqlJs` factory
210
+ on `globalThis`, which memorio then calls with your `locateFile`. This avoids a
211
+ bare `import('sql.js')` so bundlers never resolve the optional `sql.js`
212
+ dependency at build time:
213
+
214
+ ```javascript
215
+ // Zero config: CDN <script> loader (default)
216
+ await sqlite.ready
217
+ ```
218
+
219
+ If you prefer a local/bundled `sql.js` (offline, private network, or to pin a
220
+ version), install it and supply your own loader:
221
+
222
+ ```bash
223
+ npm i sql.js # optional peer
224
+ ```
225
+
226
+ ```javascript
227
+ import { sqlite } from 'memorio'
228
+
229
+ sqlite.config({ loader: () => import('sql.js') }) // local/npm build of sql.js
230
+ await sqlite.ready
231
+ ```
232
+
233
+ You can also point the WASM `locateFile` step at your own CDN/base via
234
+ `sqliteWasmBase` on `globalThis`, or pass a custom `locateFile` through
235
+ `sqlite.config({ locateFile })`:
236
+
237
+ ```javascript
238
+ globalThis.sqliteWasmBase = 'https://your-cdn.example.com/sql.js/dist/'
239
+ sqlite.config({ locateFile: (file) => `https://your-cdn.example.com/sql.js/dist/${file}` })
240
+ ```
241
+
242
+ When the engine can't load (e.g. no WASM support), `sqlite.ready` rejects, `sqlite._disabled` becomes `true`, and `sqlite._warning` explains why — `db.support()` simply returns `false` so you can degrade gracefully.
243
+
244
+ - `sqlite.config({ persistence: true })` / `sqlite.db.create('app', { persistence: true })` snapshot a database to `store` (localStorage, namespaced) and restore it on reopen — sql.js databases are in-memory by default; see [SQLite docs – Persistence & dev download](markdown/SQLITE.md#example-5-persistence--dev-download).
160
245
 
161
- ### `observer` / `useObserver` watch a path, react to it
246
+ See [SQLite docs](markdown/SQLITE.md) for the full reference.
247
+
248
+ ### `observer` / `useObserver` - watch a path, react to it
162
249
 
163
250
  ```javascript
164
251
  observer('state.user', (newVal, oldVal) => {
@@ -168,14 +255,14 @@ observer('state.user', (newVal, oldVal) => {
168
255
 
169
256
  > Keys here are plain strings, not statically checked against `state`'s shape. Keep the observer near the code that shapes that state, and grep before you rename.
170
257
 
171
- ### `dispatch` the event bus underneath it all
258
+ ### `dispatch` - the event bus underneath it all
172
259
 
173
260
  ```javascript
174
261
  memorio.dispatch.listen('state.user', (event) => console.debug(event.detail))
175
262
  memorio.dispatch.set('state.user', { detail: { name: 'Sara' } })
176
263
  ```
177
264
 
178
- ### `devtools` see everything, instantly
265
+ ### `devtools` - see everything, instantly
179
266
 
180
267
  ```javascript
181
268
  memorio.devtools.inspect()
@@ -185,32 +272,358 @@ memorio.devtools.exportData()
185
272
 
186
273
  Pairs with [dphelper-manager](https://chrome.google.com/webstore/detail/dphelper-manager-dev-tool/oppppldaoknfddeikfloonnialijngbk) for visual, time-travel inspection of your global state straight from devtools.
187
274
 
188
- ### `logger` a black box for every change
275
+ ### `logger` - a black box for every change
189
276
 
190
277
  ```javascript
191
278
  memorio.logger.configure({ enabled: true, logToConsole: true })
192
279
  memorio.logger.getHistory()
193
280
  ```
194
281
 
195
- > It logs *everything* written to `state`/`store`/`session` including anything sensitive you put there. Great for debugging, but don't leave it on unconditionally in production if secrets or PII pass through your state. See [Security](#security).
282
+ > It logs *everything* written to `state`/`store`/`session` - including anything sensitive you put there. Great for debugging, but don't leave it on unconditionally in production if secrets or PII pass through your state. See [Security](#security).
283
+
284
+ ---
285
+
286
+ ## Typed Stores
287
+
288
+ `memorio.typed<T>()` returns the global `state` proxy cast to a TypeScript type `T`, giving you **compile-time** type safety on every access and mutation. The returned object is the *exact same* Proxy as `globalThis.state` — no duplication, no overhead.
289
+
290
+ ```typescript
291
+ import 'memorio'
292
+
293
+ interface AppState {
294
+ user: { name: string; age: number; email: string }
295
+ theme: 'light' | 'dark'
296
+ items: string[]
297
+ }
298
+
299
+ const app = memorio.typed<AppState>()
300
+
301
+ // Type-checked at compile time:
302
+ app.user = { name: 'Sara', age: 30, email: 'sara@test.com' }
303
+ app.theme = 'dark'
304
+
305
+ // TypeScript errors:
306
+ // app.user = { name: 42 } // age missing, name wrong type
307
+ // app.theme = 'purple' // not a valid literal
308
+ ```
309
+
310
+ The same data is accessible via the global `state` proxy — `app` and `state` are identical instances:
311
+
312
+ ```typescript
313
+ state.user = { name: 'Sara', age: 30, email: 'sara@test.com' }
314
+ app.user.name // 'Sara' — same Proxy
315
+ ```
316
+
317
+ For runtime safety (rejecting invalid values even when TypeScript isn't checking), combine with [Schema Validation](#schema-validation) below.
318
+
319
+ See [Typed Stores docs](markdown/TYPED.md) for full examples.
320
+
321
+ ---
322
+
323
+ ## Schema Validation
324
+
325
+ `memorio.registerSchema(path, schema)` lets you register runtime validators for state paths. When a write to `state.somePath = value` violates a registered schema, the write is **rejected** before the value is stored.
326
+
327
+ ```typescript
328
+ import 'memorio'
329
+
330
+ memorio.registerSchema('user', {
331
+ type: 'object',
332
+ required: ['name', 'email'],
333
+ properties: {
334
+ name: { type: 'string', min: 1 },
335
+ email: { type: 'string', pattern: /^[^@]+@[^@]+$/ },
336
+ age: { type: 'number', min: 0, max: 150 }
337
+ }
338
+ })
339
+
340
+ state.user = { name: 'Sara', email: 'sara@test.com', age: 30 } // accepted
341
+ state.user = { name: 'Sara' } // rejected: missing 'email'
342
+ state.user = { name: 42 } // rejected: wrong type
343
+ ```
344
+
345
+ Custom validator functions are also supported:
346
+
347
+ ```typescript
348
+ memorio.registerSchema('theme', (val) => {
349
+ return val === 'light' || val === 'dark'
350
+ ? true
351
+ : 'theme must be "light" or "dark"'
352
+ })
353
+
354
+ state.theme = 'purple' // rejected: "theme must be light or dark"
355
+ state.theme = 'dark' // accepted
356
+ ```
357
+
358
+ Manual validation without writing:
359
+
360
+ ```typescript
361
+ memorio.validate('user', { name: 'Sara', email: 'sara@test.com' })
362
+ // { valid: true }
363
+
364
+ memorio.validate('user', { name: 'Sara' })
365
+ // { valid: false, errors: ["user: missing required property 'email'"] }
366
+ ```
367
+
368
+ Manage registered schemas:
369
+
370
+ ```typescript
371
+ memorio.listSchemas() // ['user', 'theme']
372
+ memorio.unregisterSchema('theme') // removes the validator
373
+ ```
374
+
375
+ **Typed + Schema** — combine both for full safety:
376
+
377
+ ```typescript
378
+ interface AppState {
379
+ user: { name: string; email: string }
380
+ theme: 'light' | 'dark'
381
+ }
382
+
383
+ const app = memorio.typed<AppState>()
384
+
385
+ memorio.registerSchema('user', {
386
+ type: 'object',
387
+ required: ['name', 'email'],
388
+ properties: {
389
+ name: { type: 'string', min: 1 },
390
+ email: { type: 'string', pattern: /^[^@]+@[^@]+$/ }
391
+ }
392
+ })
393
+
394
+ app.user = { names: 'Sara' } // ❌ TS: wrong shape
395
+ app.user = { name: '', email: '' } // ❌ TS passes, ❌ runtime: name too short
396
+ app.user = { name: 'Sara', email: 'sara@test.com' } // ✅ both pass
397
+ ```
398
+
399
+ See [Schema Validation docs](markdown/SCHEMA.md) for the full schema definition reference.
400
+
401
+ ---
402
+
403
+ ## Memory System
404
+
405
+ `memorio.memory` provides a semantic memory layer — a key/value store with **type safety**, **TTL**, **confidence scoring**, **tagging**, and **scope-based persistence**. It's designed for AI agents and applications that need structured, queryable memory with lifecycle management.
406
+
407
+ ### Core API
408
+
409
+ ```ts
410
+ // remember(key, value, options)
411
+ await memorio.memory.remember('user.language', 'Italian', {
412
+ type: 'preference',
413
+ confidence: 0.92,
414
+ scope: 'local',
415
+ ttl: null, // never expires
416
+ tags: ['ui', 'user'],
417
+ source: 'conversation'
418
+ })
419
+
420
+ // recall(key, options)
421
+ const lang = await memorio.memory.recall('user.language')
422
+ // → 'Italian'
423
+
424
+ // update(key, value, options) — creates a superseded version of the old entry
425
+ await memorio.memory.update('user.language', 'English', { confidence: 0.95 })
426
+
427
+ // forget(key) — permanently deletes
428
+ await memorio.memory.forget('user.language')
429
+ ```
430
+
431
+ ### Scopes
432
+
433
+ | Scope | Storage | TTL | Cross-session | Size limit |
434
+ |---|---|---|---|---|
435
+ | `hot` | `state` proxy | ✅ | ❌ | ~5MB (RAM) |
436
+ | `session` | `sessionStorage` | ✅ | Tab only | ~5MB |
437
+ | `local` | `localStorage` | ✅ | ✅ | ~10MB |
438
+ | `durable` | `IndexedDB` | ✅ | ✅ | ~1GB+ |
439
+
440
+ Default scope is `local` for values ≤100KB, `durable` for larger values.
441
+
442
+ ### Context API
443
+
444
+ ```ts
445
+ // context() — returns ranked, relevant memories
446
+ const context = await memorio.memory.context({
447
+ tags: 'user',
448
+ types: ['preference', 'decision'],
449
+ minConfidence: 0.7,
450
+ maxEntries: 10
451
+ })
452
+ // → [{ key, value, relevance, age, confidence, ... }]
453
+ ```
454
+
455
+ ### Memory Entry Model
456
+
457
+ Each memory is stored as a structured entry:
458
+
459
+ ```ts
460
+ {
461
+ id: string // unique identifier
462
+ key: string // path-like key (e.g. "user.preference.theme")
463
+ value: any // the stored data
464
+ type: MemoryType // 'fact' | 'preference' | 'decision' | 'task' | 'context'
465
+ confidence: number // 0.0–1.0
466
+ scope: MemoryScope // 'hot' | 'session' | 'local' | 'durable'
467
+ ttl?: number | null // milliseconds (null = never expires)
468
+ tags: string[] // for filtering
469
+ source?: string // where this memory came from
470
+ status: MemoryStatus // 'active' | 'obsolete' | 'superseded'
471
+ createdAt: number // timestamp
472
+ lastConfirmedAt: number
473
+ supersededId?: string | null
474
+ }
475
+ ```
476
+
477
+ ### Memory Lifecycle
478
+
479
+ | Operation | Behavior |
480
+ |---|---|
481
+ | `remember(key, newValue)` when entry exists | Old entry → `superseded` status, new entry → `active` |
482
+ | `recall(key)` | Returns active value; expired entries return `null` unless `includeObsolete: true` |
483
+ | `update(key, value)` | Creates superseded copy + updated active entry |
484
+ | Entry with `ttl` expires | Status → `obsolete` (via `forgetExpired()`) |
485
+ | `clear()` | Wipes all memories + index |
486
+
487
+ See [Memory docs](markdown/MEMORY.md) for full reference.
488
+
489
+ ---
490
+
491
+ ## Synchronization & Cloud (optional)
492
+
493
+ `memorio.memory` is **local-first**. The data is created and served from the
494
+ device; the cloud is only ever a **transport/persistence provider**, never the
495
+ source of truth. Enabling sync does not replace local storage — it *mirrors* it.
496
+
497
+ ```
498
+ memorio
499
+
500
+ ┌────────┴────────┐
501
+ │ Memory Engine │
502
+ └────────┬────────┘
503
+ ┌────────────┼────────────┐
504
+ ▼ ▼ ▼
505
+ local SQLite cloud
506
+ memory durable sync
507
+ ```
508
+
509
+ ### Configuring a backend
510
+
511
+ Sync is **opt-in**. You supply an application-owned `provider` that knows how to
512
+ talk to your backend (REST, WebSocket, Supabase, a custom agent server, a PostgreSQL
513
+ database, etc.). **Memorio never handles credentials** — authentication and
514
+ authorization live in your backend/provider (OWASP A01: Broken Access Control):
515
+
516
+ ```ts
517
+ memorio.memory.configure({
518
+ namespace: 'user:123:device:abc', // tenant/user/device — partitions the journal
519
+ provider: { // application-owned transport
520
+ push(ops) { return fetch('/api/sync', { method: 'POST', body: JSON.stringify(ops), headers: auth }) }
521
+ pull(since) { return fetch(`/api/sync?since=${since}`).then(r => r.json()) }
522
+ },
523
+ auto: true // auto-replay pending ops on focus/online (default true)
524
+ })
525
+
526
+ await memorio.memory.ready // waits until the local journal substrate is chosen
527
+ ```
528
+
529
+ Once configured, `memorio.memory` records every operation locally and keeps it
530
+ until the provider acknowledges it.
531
+
532
+ ### The local journal
533
+
534
+ Every mutation (`remember`, `update`, `forget`, `expire`, `confirm`, `supersede`)
535
+ is written to a local, **namespaced** operation journal:
536
+
537
+ ```ts
538
+ await memory.remember('user.language', 'Italian', { scope: 'local' })
539
+
540
+ memory.journal.pending() // -> [{ id, key:'user.language', value:'Italian', operation:'remember', sync:'pending', version: 1, updatedAt: ... }]
541
+ memory.journal.markSynced([id])
542
+ memory.journal.replay() // pushes pending() to provider.provider, marks synced, optional pull+apply
543
+ memory.journal.status() // 'sqlite' | 'store' (substrate in use)
544
+ ```
545
+
546
+ The journal stores the full entry plus `operation`, `version`, `updatedAt` and
547
+ `sync` status, so conflicts can be resolved when connectivity returns.
548
+
549
+ ### Substrates
550
+
551
+ The journal is persisted on `store` (localStorage / in-memory Map fallback) — **not** on a sql.js database, because sql.js databases are volatile (in-memory) and would lose pending operations on reload:
552
+
553
+ | Substrate | Used for | Notes |
554
+ |---|---|---|
555
+ | `store` (localStorage) | sync journal | persistent across reloads; namespaced; Map fallback in Node |
556
+ | `sqlite` (sql.js) | ad-hoc SQL / value storage | in-memory by default — use `persistence: true` to snapshot to `store` |
557
+ | `idb` (IndexedDB) | `memorio.memory` durable scope values | persistent; persistent across reloads |
558
+
559
+ The `sqlite.db.download(name)` dev helper also triggers a browser `.sqlite` download of an in-memory database on demand.
560
+
561
+ ### Sync = operations, not database dumps
562
+
563
+ Memorio syncronizza **operazioni di memoria**, never a raw database dump. When
564
+ two devices diverge, conflict resolution is based on `confidence`,
565
+ `lastConfirmedAt`, `source`, `version` and `scope` — not just "last write wins":
566
+
567
+ ```ts
568
+ // Device A: user.language = Italian, confidence 0.92
569
+ // Device B: user.language = English, confidence 0.61
570
+ // → the higher-confidence entry wins locally; the provider decides for shared.
571
+ ```
572
+
573
+ The `namespace` (tenant / user / device) is the single partition key: local
574
+ journal reads and writes are scoped to it, and they can never cross namespaces —
575
+ a client holding a fake/forged namespace simply sees its own empty journal.
576
+
577
+ ### Scopes (isolation, not a security boundary)
578
+
579
+ ```
580
+ scope: 'device' // only this browser/device
581
+ scope: 'user' // follows the user across devices (via sync)
582
+ scope: 'shared' // shared across users/tenant
583
+ ```
584
+
585
+ `scope: 'user'`/`'shared'` require a provider + namespace. `scope: 'device'`
586
+ is local only. As with `memorio.createContext`, **scoping is a naming convention,
587
+ not a security boundary** — enforce real isolation in your backend.
588
+
589
+ ### Security posture
590
+
591
+ - **NIST SP 800-53 / OWASP**: no credentials, tokens, or secrets are read from or
592
+ stored by `memorio`; sensitive state you place in `state`/`store`/`session`/`idb`
593
+ is not encrypted by memorio (see [Security](#security)).
594
+ - **Namespace isolation**: journal reads/writes are keyed by
595
+ `namespace:id` at the storage layer; there is no API to enumerate or open
596
+ another namespace's journal (defense-in-depth).
597
+ - **No dynamic code**: journal entries are strictly JSON-round-tripped and
598
+ size-capped (10 MB/entry); no `eval`/template-injection of provider data.
599
+ - **Trust boundary**: the provider/backend owns authentication, authorization,
600
+ and remote-side conflict resolution. Memorio owns the local durable copy and
601
+ the operation log; it surfaces `conflict`/`error` rows via `journal.pending()`.
602
+ - **NSA/CISA advice (data-at-rest/secrets)**: if you persist `state`/`store`
603
+ server-side or ship user data through your backend, encrypt it server-side with
604
+ keys you manage; memorio treats the local journal as untrusted-from-the-browser
605
+ and does not attest its own integrity.
606
+
607
+ See [SQLite docs](markdown/SQLITE.md), [Memory docs](markdown/MEMORY.md), and the [Synchronization & Cloud guide](markdown/SYNC.md).
196
608
 
197
609
  ---
198
610
 
199
611
  ## Cross-Platform Behavior
200
612
 
201
- memorio runs everywhere JavaScript does but "everywhere" means different guarantees in different places, and we'd rather tell you now than have you find out at 2am:
613
+ memorio runs everywhere JavaScript does - but "everywhere" means different guarantees in different places, and we'd rather tell you now than have you find out at 2am:
202
614
 
203
615
  | API | Browser | Node.js | Deno | Edge / Workers |
204
616
  |---|---|---|---|---|
205
617
  | `state` | Proxy in memory | Proxy in memory | Proxy in memory | Proxy in memory |
206
618
  | `observer` / `useObserver` | ✅ | ✅ | ✅ | ✅ |
207
619
  | `cache` | ✅ in memory | ✅ in memory | ✅ in memory | ✅ in memory |
208
- | `store` | `localStorage` | `Map` fallback **not durable across restarts** | `Map` fallback not durable | `localStorage` where available, else `Map` |
209
- | `session` | `sessionStorage` | `Map` fallback not durable | `Map` fallback not durable | `sessionStorage` where available, else `Map` |
620
+ | `store` | `localStorage` | `Map` fallback - **not durable across restarts** | `Map` fallback - not durable | `localStorage` where available, else `Map` |
621
+ | `session` | `sessionStorage` | `Map` fallback - not durable | `Map` fallback - not durable | `sessionStorage` where available, else `Map` |
210
622
  | `idb` | ✅ `IndexedDB` | ❌ not available | ❌ not available | ⚠️ check `getCapabilities()` |
623
+ | `sqlite` | ✅ `sql.js` (lazy, CDN by default) | ❌ not available | ❌ not available | ⚠️ check `sqlite.db.support()` |
211
624
  | `devtools` | ✅ | ❌ | ❌ | ⚠️ |
212
625
 
213
- Same API top to bottom that's the promise. But if your server code leans on `store.get(...)` surviving a redeploy, know that on Node/Deno it won't; the fallback is an in-memory cache with the same shape, not durable storage.
626
+ Same API top to bottom - that's the promise. But if your server code leans on `store.get(...)` surviving a redeploy, know that on Node/Deno it won't; the fallback is an in-memory cache with the same shape, not durable storage.
214
627
 
215
628
  ```javascript
216
629
  memorio.isBrowser()
@@ -234,10 +647,10 @@ const ctx = memorio.createContext('tenant-123')
234
647
  ctx.state.user = { name: 'Isolated' }
235
648
  ctx.store.set('settings', { theme: 'dark' })
236
649
 
237
- console.debug(state.user) // undefined separate namespace
650
+ console.debug(state.user) // undefined - separate namespace
238
651
  ```
239
652
 
240
- Handy for keeping per-tenant or per-request state from colliding in the same process. Under the hood it's a key prefix (`tenant-123-key`) inside the same storage a naming convention, not a hard memory boundary. Great for a browser tab; if you're running this in a shared Node.js process or an edge isolate that might be reused across requests, make sure tenant IDs can't collide or be forged, and don't treat this as your only isolation layer if the stakes are real.
653
+ Handy for keeping per-tenant or per-request state from colliding in the same process. Under the hood it's a key prefix (`tenant-123-key`) inside the same storage - a naming convention, not a hard memory boundary. Great for a browser tab; if you're running this in a shared Node.js process or an edge isolate that might be reused across requests, make sure tenant IDs can't collide or be forged, and don't treat this as your only isolation layer if the stakes are real.
241
654
 
242
655
  ---
243
656
 
@@ -248,9 +661,9 @@ Handy for keeping per-tenant or per-request state from colliding in the same pro
248
661
  - Session IDs generated via `crypto.randomUUID`.
249
662
  - Inputs validated, keys sanitized, errors caught at module boundaries.
250
663
 
251
- memorio does **not** encrypt what you put into `store`, `session`, or `idb` they're thin, fast wrappers over `localStorage`/`sessionStorage`/`IndexedDB`, which store data in the clear on the user's device. Bring your own encryption layer for tokens, secrets, or regulated personal data.
664
+ memorio does **not** encrypt what you put into `store`, `session`, or `idb` - they're thin, fast wrappers over `localStorage`/`sessionStorage`/`IndexedDB`, which store data in the clear on the user's device. Bring your own encryption layer for tokens, secrets, or regulated personal data.
252
665
 
253
- We build with recognized engineering guidance (NIST SP 800-53 practices) as an input to how we write code that's a design discipline, not a compliance certificate, and no third-party audit has been performed. Found an issue? Report it privately see `SECURITY.md` rather than opening a public issue.
666
+ We build with recognized engineering guidance (NIST SP 800-53 practices) as an input to how we write code - that's a design discipline, not a compliance certificate, and no third-party audit has been performed. Found an issue? Report it privately - see `SECURITY.md` - rather than opening a public issue.
254
667
 
255
668
  ---
256
669
 
package/SECURITY.md CHANGED
@@ -24,15 +24,15 @@ Memorio follows NIST and NSA security standards at the enterprise level.
24
24
  ## OWASP Compliance
25
25
 
26
26
  Addresses OWASP Top 10 (2021):
27
- - A01:2021 Broken Access Control (global object protection, property locks)
28
- - A02:2021 Cryptographic Failures (crypto.randomUUID for session IDs)
29
- - A03:2021 Injection (CSS sanitization in devtools)
30
- - A05:2021 Security Misconfiguration (minimal surface area, no bundled secrets)
31
- - A06:2021 Vulnerable and Outdated Components (regular npm audit, Socket.dev)
32
- - A07:2021 Identification and Authentication Failures (N/A library, no auth)
33
- - A08:2021 Software and Data Integrity Failures (strict tsconfig, lock files)
34
- - A09:2021 Security Logging and Monitoring Failures (DevTools inspect, Logger module)
35
- - A10:2021 Server-Side Request Forgery (N/A no network requests)
27
+ - A01:2021 - Broken Access Control (global object protection, property locks)
28
+ - A02:2021 - Cryptographic Failures (crypto.randomUUID for session IDs)
29
+ - A03:2021 - Injection (CSS sanitization in devtools)
30
+ - A05:2021 - Security Misconfiguration (minimal surface area, no bundled secrets)
31
+ - A06:2021 - Vulnerable and Outdated Components (regular npm audit, Socket.dev)
32
+ - A07:2021 - Identification and Authentication Failures (N/A - library, no auth)
33
+ - A08:2021 - Software and Data Integrity Failures (strict tsconfig, lock files)
34
+ - A09:2021 - Security Logging and Monitoring Failures (DevTools inspect, Logger module)
35
+ - A10:2021 - Server-Side Request Forgery (N/A - no network requests)
36
36
 
37
37
  ## Reporting Security Issues
38
38
 
@@ -44,5 +44,5 @@ If you find a security vulnerability:
44
44
  Do not open public issues for security vulnerabilities.
45
45
 
46
46
  ---
47
- *Document version: 2.0 Last updated: 2026-05-19*
47
+ *Document version: 2.0 - Last updated: 2026-05-19*
48
48
  *Owner: BigLogic Security Team*