memorio 4.9.35 → 5.0.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.
Files changed (76) hide show
  1. package/AGENTS.md +3 -3
  2. package/README.md +307 -359
  3. package/SECURITY.md +17 -1
  4. package/SUMMARY.md +59 -45
  5. package/adr/001-state-proxy-model.md +96 -0
  6. package/adr/002-observer-semantics.md +180 -0
  7. package/adr/003-deep-mutation-semantics.md +129 -0
  8. package/adr/004-array-mutation-semantics.md +128 -0
  9. package/adr/005-scheduler-contract.md +149 -0
  10. package/adr/006-context-isolation.md +92 -0
  11. package/adr/007-mutation-records.md +118 -0
  12. package/adr/008-transactions.md +106 -0
  13. package/adr/009-history-model.md +110 -0
  14. package/adr/README.md +46 -0
  15. package/adr/template.md +49 -0
  16. package/examples/basic.ts +115 -115
  17. package/examples/browser-vanilla.html +358 -358
  18. package/examples/cache.ts +72 -72
  19. package/examples/cross-platform-guards.ts +57 -57
  20. package/examples/history.ts +104 -0
  21. package/examples/idb.ts +109 -109
  22. package/examples/multi-tenant-context.ts +44 -44
  23. package/examples/node-server.ts +308 -308
  24. package/examples/observer.ts +60 -60
  25. package/examples/platform.ts +115 -115
  26. package/examples/react-app.tsx +362 -362
  27. package/examples/react-observer.tsx +63 -63
  28. package/examples/semantic-memory.ts +60 -60
  29. package/examples/session-advanced.ts +91 -91
  30. package/examples/sqlite-batched-writes.ts +57 -57
  31. package/examples/state-advanced.ts +89 -89
  32. package/examples/store-advanced.ts +117 -117
  33. package/examples/sync.ts +90 -0
  34. package/examples/typed-and-schema.ts +102 -100
  35. package/examples/useObserver.tsx +140 -141
  36. package/global.cjs +4594 -0
  37. package/global.d.ts +8 -0
  38. package/global.js +4532 -0
  39. package/index.cjs +700 -678
  40. package/index.d.ts +1 -0
  41. package/index.js +680 -677
  42. package/llms.txt +72 -4
  43. package/markdown/AUDIT-REPORT.md +135 -0
  44. package/markdown/CACHE.md +100 -0
  45. package/markdown/CHANGELOG.md +243 -0
  46. package/markdown/DEVTOOLS.md +129 -0
  47. package/markdown/DISPATCH.md +177 -0
  48. package/markdown/HISTORY.md +199 -0
  49. package/markdown/IDB.md +178 -0
  50. package/markdown/IMPORT.md +153 -0
  51. package/markdown/INSPECT.md +123 -0
  52. package/markdown/LOGGER.md +154 -0
  53. package/markdown/MEMORY-ATTACHMENT.md +96 -0
  54. package/markdown/MEMORY.md +162 -0
  55. package/markdown/OBSERVER.md +209 -0
  56. package/markdown/PLATFORM.md +271 -0
  57. package/markdown/PROJECT.md +311 -0
  58. package/markdown/SCHEMA.md +176 -0
  59. package/markdown/SECURITY.md +330 -0
  60. package/markdown/SESSION.md +165 -0
  61. package/markdown/SQLITE.md +190 -0
  62. package/markdown/STATE.md +160 -0
  63. package/markdown/STORE.md +171 -0
  64. package/markdown/SYNC.md +319 -0
  65. package/markdown/TYPED.md +165 -0
  66. package/markdown/USEOBSERVER.md +257 -0
  67. package/modules/redux.cjs +320 -167
  68. package/modules/redux.cjs.map +1 -1
  69. package/modules/redux.js +320 -167
  70. package/modules/redux.js.map +1 -1
  71. package/package.json +13 -3
  72. package/types/env.d.ts +19 -9
  73. package/types/exports.d.ts +20 -0
  74. package/types/history.d.ts +13 -1
  75. package/types/memorio.d.ts +17 -5
  76. package/types/mutation.d.ts +75 -0
package/README.md CHANGED
@@ -1,132 +1,92 @@
1
1
  # 🧠 memorio
2
2
 
3
- **Local-first memory for JavaScript.**
4
- One import. Global state, local persistence, SQLite, semantic memory, optional sync.
3
+ **Application state intelligence runtime.**
5
4
 
6
- [![npm version](https://img.shields.io/npm/v/memorio.svg)](https://www.npmjs.com/package/memorio)
7
- [![npm downloads](https://img.shields.io/npm/dm/memorio.svg)](https://www.npmjs.com/package/memorio)
8
- [![Socket Badge](https://socket.dev/api/badge/npm/package/memorio)](https://socket.dev/npm/package/memorio)
9
- [![Known Vulnerabilities](https://snyk.io/test/npm/memorio/badge.svg)](https://snyk.io/test/npm/memorio)
10
- [![zero deps](https://img.shields.io/badge/dependencies-0-brightgreen)](#security)
11
- [![license](https://img.shields.io/npm/l/memorio.svg)](#license)
5
+ Use it like an object.
12
6
 
13
7
  ```ts
14
- import 'memorio'
8
+ import 'memorio/global'
15
9
 
16
10
  state.user = { name: 'Sara', role: 'admin' }
17
11
  state.counter++
12
+
13
+ console.log(state.user.name)
14
+ console.log(state.counter)
18
15
  ```
19
16
 
20
17
  No provider tree. No reducers. No actions. No boilerplate.
21
18
  Just data, available where your application needs it.
22
19
 
23
- **And if you don't want a global API, don't use one:**
24
-
25
- ```ts
26
- import { state, store, session, cache, idb, sqlite, memorio } from 'memorio'
27
- ```
28
-
29
- The global API is a **choice**, not an architectural requirement.
30
-
31
20
  ---
32
21
 
33
- ## Table of contents
34
-
35
- - [Why memorio](#why-memorio)
36
- - [Global is optional](#global-is-optional)
37
- - [Before / After](#before--after)
38
- - [Which layer should I use](#which-layer-should-i-use)
39
- - [Install](#install)
40
- - [Quick start](#quick-start)
41
- - [Observing changes](#observing-changes)
42
- - [The layers](#the-layers) — store · session · cache · idb · sqlite · memory
43
- - [Redux and other state managers](#redux-and-other-state-managers)
44
- - [memorio vs Redux, at a glance](#memorio-vs-redux-at-a-glance)
45
- - [React integration](#react-integration)
46
- - [Typed state & schema validation](#typed-state--schema-validation)
47
- - [Local-first sync](#local-first-sync)
48
- - [Cross-platform support](#cross-platform-support)
49
- - [Security](#security)
50
- - [Honest limitations](#honest-limitations)
51
- - [When to use something else](#when-to-use-something-else)
52
- - [Design philosophy](#design-philosophy)
53
- - [A mental model](#a-mental-model)
54
- - [Recipes](#recipes)
55
- - [License](#license)
22
+ ## Why memorio?
56
23
 
57
- ---
24
+ Most applications eventually need more than one kind of data: reactive state, persistent values, session data, caches, structured browser storage, local SQL, application memory, history, and optional sync.
58
25
 
59
- ## Why memorio
26
+ Usually that means a different library - and a different mental model - for each.
60
27
 
61
- Modern JavaScript applications often split their data across systems that don't talk to each other: a state manager, `localStorage`, `sessionStorage`, IndexedDB, a cache, SQLite, a server database, maybe an AI memory layer, maybe a sync layer on top. Each with its own API, its own lifecycle, its own mental model.
28
+ **Memorio gives them one consistent runtime, without making the simple case complicated.**
62
29
 
63
- memorio brings these concerns into **one runtime and one consistent model**, while keeping each layer independent.
30
+ ```ts
31
+ state.value = 42
32
+ ```
64
33
 
65
- | Layer | Purpose | Persistence | Reactive |
66
- |---|---|---:|---:|
67
- | `state` | application state | No | Yes |
68
- | `cache` | transient runtime data | No | No |
69
- | `session` | tab/session data | Yes | No |
70
- | `store` | persistent key/value data | Yes | No |
71
- | `idb` | durable structured browser data | Yes | No |
72
- | `sqlite` *(beta)* | relational local SQL | Optional | No |
73
- | `memory` | structured application memory | Yes | Optional |
74
- | `journal` *(beta)* | local-first operation history | Yes | No |
34
+ Add persistence, memory, history, or sync only when your application actually needs them. The common case stays tiny; the runtime grows with you.
75
35
 
76
- Start with `state`. Add persistence or memory only when your application actually needs it.
36
+ ---
77
37
 
78
- ## Global is optional
38
+ ## Install
79
39
 
80
- ```ts
81
- import 'memorio'
82
- state.user = { name: 'Sara' }
40
+ ```bash
41
+ npm install memorio
83
42
  ```
84
43
 
85
- is useful when an application benefits from a shared runtime and a simple API. But memorio does **not** require applications to expose or use global state — explicit imports work against the exact same runtime:
44
+ Zero production dependencies in core. Optional integrations (`react`, `sql.js`, etc.) are installed only when you use them.
45
+
46
+ ---
47
+
48
+ ## Global or explicit
86
49
 
87
50
  ```ts
88
- import { state } from 'memorio'
51
+ // Opt-in global access
52
+ import 'memorio/global'
89
53
  state.user = { name: 'Sara' }
54
+
55
+ // Or explicit imports
56
+ import { state, store, memory } from 'memorio'
90
57
  ```
91
58
 
92
- This makes memorio suitable for small apps, modular apps, libraries, component systems, tests, or applications that already use another state manager and prefer explicit dependencies.
59
+ Memorio never puts APIs on `globalThis` automatically and never infers dev mode from `NODE_ENV` or your bundler - global access is always something you choose.
93
60
 
94
- **Global when convenient. Explicit when appropriate.**
61
+ ---
95
62
 
96
- ## Which layer should I use
63
+ ## The layers
97
64
 
98
- ```text
99
- Does the UI need to react automatically to changes?
100
- │
101
- ├─ Yes → state
102
- │
103
- └─ No
104
- │
105
- ├─ Only while the runtime exists? → cache
106
- ├─ Only for this browser tab/session? → session
107
- ├─ Small persistent key/value data? → store
108
- ├─ Larger or structured browser data? → idb
109
- ├─ Relations, joins, or SQL? → sqlite
110
- └─ Knowledge the application should
111
- remember (confidence, source, expiry)? → memory
112
- ```
65
+ | Layer | Purpose | Persistence | Reactive |
66
+ | --------- | --------------------------------- | :---------: | :------: |
67
+ | `state` | application state | No | Yes |
68
+ | `cache` | transient runtime data | No | No |
69
+ | `session` | tab/session data | Yes | No |
70
+ | `store` | persistent key/value data | Yes | No |
71
+ | `idb` | durable structured browser data | Yes | No |
72
+ | `sqlite` | relational local SQL | Optional | No |
73
+ | `memory` | structured application memory | Yes | Optional |
74
+ | `journal` | local-first operation history | Yes | No |
113
75
 
114
- ## Install
76
+ You don't have to use all of them - start with `state`.
115
77
 
116
- ```bash
117
- npm install memorio
118
- ```
119
-
120
- Optional peers, only loaded when used:
78
+ **Which one do I need?**
121
79
 
122
- ```bash
123
- npm install react react-dom # React integration
124
- npm install sql.js # sqlite engine
80
+ ```text
81
+ Does the UI need to react automatically to changes?
82
+ yes → state
83
+ no → cache (runtime only) · session (this tab) · store (small persistent kv)
84
+ idb (structured browser data) · sqlite (relations/SQL) · memory (what the app should remember)
125
85
  ```
126
86
 
127
- **Zero production dependencies** in the core package. See [Security](#security).
87
+ ---
128
88
 
129
- ## Before / After
89
+ ## Before / after
130
90
 
131
91
  ```ts
132
92
  // Without memorio
@@ -138,445 +98,433 @@ useEffect(() => {
138
98
  useEffect(() => {
139
99
  localStorage.setItem('user', JSON.stringify(user))
140
100
  }, [user])
141
- ```
142
101
 
143
- ```ts
144
102
  // With memorio
145
- import 'memorio'
146
- store.set('user', { name: 'Sara' }) // persistent immediately, no boilerplate
147
-
148
- const user = store.get('user')
103
+ import { state, persist } from 'memorio'
104
+ state.user = { name: 'Sara' }
105
+ const off = persist('state.user') // restores + keeps in sync
149
106
  ```
150
107
 
151
- Same result, one reactive line instead of two effects and manual JSON serialization.
108
+ ---
152
109
 
153
- ## Quick start
110
+ ## Persistent state
154
111
 
155
112
  ```ts
156
- import 'memorio'
113
+ import { state, persist } from 'memorio'
157
114
 
158
- state.user = { name: 'Sara', role: 'admin' }
159
- state.counter++
115
+ state.user = { name: 'Sara' }
116
+ const off = persist('state.user')
160
117
 
161
- console.log(state.user.name)
162
- console.log(state.counter)
118
+ // later
119
+ off()
163
120
  ```
164
121
 
165
- Reactive, Proxy-based state. No provider tree to configure, no reducer to maintain.
122
+ Nested paths work when the intermediate objects already exist: `persist('state.user.preferences.theme')`.
123
+ Without `localStorage` (e.g. Node), `store` falls back to memory.
166
124
 
167
- Lock a slice you don't want mutated by accident:
125
+ ---
126
+
127
+ ## Observing changes
168
128
 
169
129
  ```ts
170
- state.config = { maxUsers: 100 }
171
- state.config.lock()
130
+ import { observer } from 'memorio'
172
131
 
173
- state.config.maxUsers = 200 // throws
132
+ const off = observer('state.user', (next, previous) => {
133
+ console.log('user changed:', next, previous)
134
+ })
174
135
 
175
- state.config.unlock()
176
- state.config.maxUsers = 200 // ok
136
+ off() // unsubscribe
177
137
  ```
178
138
 
179
- Prefer explicit imports over the global? Same runtime either way:
139
+ Prefer the returned `off()` for targeted teardown (it unsubscribes only *this* callback).
140
+ The alternatives remove listeners by path name:
180
141
 
181
142
  ```ts
182
- import { state, store, session, cache, idb, sqlite, memorio } from 'memorio'
143
+ observer.remove('state.user') // removes ALL callbacks for this path
144
+ observer.removeAll() // removes every observer at once
183
145
  ```
184
146
 
185
- ## Observing changes
147
+ ---
148
+
149
+ ## Application memory
186
150
 
187
- Reactivity is part of the runtime — it is not tied to React.
151
+ State stores what the application **has**. Memory stores what it **remembers**.
188
152
 
189
153
  ```ts
190
- observer('state.user', (next, previous) => {
191
- console.log('user changed:', next, previous)
154
+ import { memorio } from 'memorio'
155
+
156
+ await memorio.memory.remember('user.language', 'Italian', {
157
+ type: 'preference',
158
+ confidence: 0.92,
159
+ scope: 'local',
160
+ tags: ['user', 'ui'],
161
+ source: 'conversation'
192
162
  })
193
163
 
194
- // nested paths work too
195
- observer('state.user.name', callback)
164
+ const language = await memorio.memory.recall('user.language')
196
165
  ```
197
166
 
198
- Lower-level event access is also available:
167
+ Updating an entry doesn't overwrite it - the previous entry becomes `superseded`, the new one `active`:
199
168
 
200
169
  ```ts
201
- const off = memorio.dispatch.listen('state.user', event => console.debug(event.detail))
202
- memorio.dispatch.set('state.user', { detail: { name: 'Sara' } })
203
-
204
- off() // remove only this subscription
170
+ await memorio.memory.update('user.language', 'English', { confidence: 0.95 })
205
171
  ```
206
172
 
207
- Each subscription owns its own unsubscribe function. Prefer the returned `off()` over `memorio.dispatch.remove('state.user')`, which removes **all** listeners registered under that name.
208
-
209
- React apps get a dedicated hook, `useObserver` — see [React integration](#react-integration).
210
-
211
- ## The layers
212
-
213
- ### `store` — persistent key/value data
173
+ Retrieve relevant memory instead of loading everything:
214
174
 
215
175
  ```ts
216
- store.set('preferences', { theme: 'dark' })
217
- const preferences = store.get('preferences')
176
+ const context = await memorio.memory.context({
177
+ tags: 'user',
178
+ types: ['preference', 'decision'],
179
+ minConfidence: 0.7,
180
+ maxEntries: 10
181
+ })
218
182
  ```
219
183
 
220
- Backed by `localStorage` in the browser. Falls back to memory in environments without persistent storage — check `store.isPersistent` when portability matters.
184
+ > **Note:** "structured memory" here means metadata-based ranking (tags, type, confidence, recency) - not embedding similarity search. Pair `memorio.memory` with a dedicated vector store if you need that.
221
185
 
222
- ### `session` — data for the current session
223
-
224
- ```ts
225
- session.set('wizard-step', 3)
226
- const step = session.get('wizard-step')
186
+ ```text
187
+ sqlite → what data do I have?
188
+ memory → what does my application remember?
227
189
  ```
228
190
 
229
- Backed by `sessionStorage`. Good for multi-step workflows, temporary preferences, tab-scoped data.
191
+ ---
230
192
 
231
- ### `cache` — volatile runtime data
193
+ ## The other layers, briefly
232
194
 
195
+ **`store`** - persistent key/value, backed by `localStorage` in browsers, memory elsewhere.
233
196
  ```ts
234
- cache.set('expensive-result', computeExpensiveResult())
197
+ store.set('preferences', { theme: 'dark' })
198
+ const preferences = store.get('preferences')
235
199
  ```
236
200
 
237
- Disappears when the runtime disappears. No persistence guarantee, ever.
201
+ **`session`** - scoped to the current browser session (`sessionStorage`). Good for wizard steps and tab-local state.
238
202
 
239
- ### `idb` — durable structured browser data
203
+ **`cache`** - volatile, in-memory, disappears when the runtime disappears.
240
204
 
205
+ **`idb`** - durable structured browser data:
241
206
  ```ts
242
207
  await idb.db.create('app')
243
208
  await idb.table.create('app', 'users')
244
209
  await idb.data.set('app', 'users', { id: 1, name: 'Sara' })
210
+ ```
245
211
 
246
- const user = await idb.data.get('app', 'users', 1)
212
+ **`sqlite`** - relational local SQL, in-memory by default via `sql.js`:
213
+ ```ts
214
+ await sqlite.ready
215
+ await sqlite.db.create('app', { persistence: true })
216
+ await sqlite.query.run('app', `CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, role TEXT)`)
217
+ await sqlite.query.select('app', `SELECT * FROM users WHERE role = ?`, ['admin'])
247
218
  ```
219
+ > ⚠️ Persistence serializes the **entire database** on flush - fine for small/medium datasets, but large ones need deliberate batching.
220
+
221
+ Check what's actually available at runtime before depending on a backend: `memorio.getCapabilities()`.
222
+
223
+ ---
248
224
 
249
- Check runtime support before depending on it: `memorio.getCapabilities()`.
225
+ ## State Intelligence
250
226
 
251
- ### `sqlite` — local SQL
227
+ With history tracking enabled, mutations are recorded with a unique ID, HLC timestamp, source, and causal context - the foundation for undo/redo, diff, trace, and (planned) replay.
252
228
 
253
229
  ```ts
254
- await sqlite.ready
255
- await sqlite.db.create('app')
230
+ import { memorio } from 'memorio'
231
+
232
+ memorio.enableHistory(true)
256
233
 
257
- await sqlite.query.run('app', `
258
- CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, role TEXT)
259
- `)
260
- await sqlite.query.run('app', `INSERT INTO users (name, role) VALUES (?, ?)`, ['Sara', 'admin'])
234
+ state.user = { name: 'John' }
235
+ state.user.name = 'Maria'
236
+ state.user.name = 'Pedro'
261
237
 
262
- const admins = await sqlite.query.select('app', `SELECT * FROM users WHERE role = ?`, ['admin'])
238
+ const history = memorio.trace()
239
+ memorio.undo()
240
+ memorio.redo()
241
+
242
+ memorio.canUndo()
243
+ memorio.canRedo()
263
244
  ```
264
245
 
265
- Runs **in memory by default**, using `sql.js`. Persistence is explicit:
246
+ **Explicit mutations & transactions:**
266
247
 
267
248
  ```ts
268
- await sqlite.db.create('app', { persistence: true })
249
+ const mutation = memorio.mutate('state.user.role', 'admin', { source: 'permissions.enableAdmin' })
250
+ // mutation.id · mutation.hlc · mutation.source
251
+
252
+ const tx = memorio.transaction('user.migration', 'Migrate to v2 schema')
253
+ state.user.role = 'admin'
254
+ state.user.permissions = ['read', 'write']
255
+ memorio.commitTransaction()
256
+
257
+ // or abort and roll back
258
+ memorio.transaction('failed-op')
259
+ state.user.name = 'Maria'
260
+ memorio.abortTransaction()
269
261
  ```
270
262
 
271
- > ⚠️ **Persistence is not incremental.** It serializes the **entire** database on flush — fine for small/medium local datasets, but large databases should not be persisted after every mutation. Batch writes and flush deliberately. memorio's SQLite layer is an embedded local SQL runtime, not a replacement for a server database.
263
+ **Roadmap:**
272
264
 
273
- ### `memory` — application memory
265
+ | Phase | Capability | Status |
266
+ | ----- | ---------------------------- | --------------------------------------------------- |
267
+ | 1 | Mutation Engine | ✅ Active |
268
+ | 2 | Undo / Redo / Diff / Revert | ✅ Partial (basic trace + undo/redo shipped above) |
269
+ | 3 | History Engine (persisted, queryable history) | Planned |
270
+ | 4 | Replay / Time Machine | Planned |
271
+ | 5 | Causal Graph | Planned |
272
+ | 6 | Explain / Trace / Impact | Planned |
273
+ | 7 | What-if Simulation | Planned |
274
274
 
275
- The layer that makes memorio more than a state manager. `memory` represents information the application wants to **remember**, not just store — it can carry type, confidence, source, scope, tags, lifetime, status, and history.
275
+ ---
276
+
277
+ ## Typed state & validation
276
278
 
277
279
  ```ts
278
- await memorio.memory.remember('user.language', 'Italian', {
279
- type: 'preference',
280
- confidence: 0.92,
281
- scope: 'local',
282
- tags: ['user', 'ui'],
283
- source: 'conversation',
284
- })
280
+ import { memorio } from 'memorio'
285
281
 
286
- const language = await memorio.memory.recall('user.language')
287
- ```
282
+ interface AppState {
283
+ user: { name: string; age: number; email: string }
284
+ theme: 'light' | 'dark'
285
+ }
288
286
 
289
- Updates don't overwrite — they supersede, preserving history:
287
+ const app = memorio.typed<AppState>()
288
+ app.theme = 'dark' // ✅
289
+ app.theme = 'purple' // ❌ TypeScript error
290
290
 
291
- ```ts
292
- await memorio.memory.update('user.language', 'English', { confidence: 0.95 })
293
- // previous entry → status: 'superseded' | new entry → status: 'active'
291
+ app === state // same proxy, typed
294
292
  ```
295
293
 
296
- Retrieve what's relevant to the current operation, not everything:
294
+ Runtime schemas validate values crossing trust boundaries:
297
295
 
298
296
  ```ts
299
- const context = await memorio.memory.context({
300
- tags: 'user',
301
- types: ['preference', 'decision'],
302
- minConfidence: 0.7,
303
- maxEntries: 10,
297
+ memorio.registerSchema('user', {
298
+ type: 'object',
299
+ required: ['name', 'email'],
300
+ properties: {
301
+ name: { type: 'string', min: 1 },
302
+ email: { type: 'string', pattern: /^[^@]+@[^@]+$/ }
303
+ }
304
304
  })
305
- ```
306
305
 
307
- The context system considers tags, type, confidence, and recency.
306
+ memorio.registerSchema('tags', { type: 'array', items: { type: 'string' } })
307
+
308
+ state.tags = ['a', 'b'] // accepted
309
+ state.tags = [1, 2, 3] // rejected
310
+ ```
308
311
 
309
- > ℹ️ **"Semantic" here means structured, not embedding-based.** `memory.context()` ranks by tags, type, confidence and recency — there is no hidden vector database and no claim that free text has been understood through embeddings. If you need true similarity search, pair a dedicated embedding/vector store with `memorio.memory` for the lifecycle on top (confidence, source, TTL, scope, history, supersession).
312
+ ---
310
313
 
311
- In short: `sqlite` answers "what data do I have?" — `memory` answers "what does my app remember, and how sure is it?" They're not competing for the same job.
314
+ ## React integration
312
315
 
313
- ## Redux and other state managers
316
+ `useObserver` is a thin bridge over the observer engine. For the common React case,
317
+ wrap it in a tiny helper so components read state like any other value:
314
318
 
315
- memorio does not need to replace your existing state manager. Its Redux integration ships as a **separate, optional entry point**:
319
+ ```tsx
320
+ import { useReducer } from 'react'
321
+ import { useObserver, state } from 'memorio'
322
+
323
+ // Subscribe to one or more state paths and re-render on change.
324
+ function useMemorioValue(path: any) {
325
+ const [, update] = useReducer((n: number) => n + 1, 0)
326
+ useObserver(update, path)
327
+ return path
328
+ }
316
329
 
317
- ```ts
318
- import { createMemorioReduxBridge } from 'memorio/redux'
330
+ function Counter() {
331
+ const counter = useMemorioValue(state.counter)
332
+ return <div>{counter}</div>
333
+ }
319
334
  ```
320
335
 
321
- Redux is not a dependency of memorio — the adapter works against a small structural interface (`getState` / `subscribe` / `dispatch`), so it can work with Redux-style stores without coupling memorio's core to Redux.
336
+ `useObserver` can also auto-discover dependencies — pass a callback with no explicit
337
+ deps and it tracks every state path read inside it — but the explicit-deps form above
338
+ is cheaper and easier to reason about at scale.
322
339
 
323
- > **Ownership rule:** your state manager owns application state. memorio owns memory.
340
+ Memorio itself stays framework-independent - React is an integration, not a requirement.
324
341
 
325
- The bridge does not mirror the entire store — only explicitly mapped values are persisted:
342
+ ---
343
+
344
+ ## Redux and other state managers
345
+
346
+ > **Your state manager owns application state. Memorio owns memory.**
326
347
 
327
348
  ```ts
349
+ import { createMemorioReduxBridge } from 'memorio/redux'
350
+
328
351
  const memorioRedux = createMemorioReduxBridge<AppState>({
329
352
  mappings: {
330
353
  'user.preferences': {
331
354
  selector: state => state.user.preferences,
332
355
  type: 'preference',
333
356
  scope: 'local',
334
- tags: ['app', 'user'],
357
+ tags: ['app', 'user']
335
358
  },
336
- 'user.name': state => state.user.name,
359
+ 'user.name': state => state.user.name
337
360
  },
338
361
  whitelist: ['user/preferencesChanged', 'user/nameChanged'],
339
- debug: true,
362
+ debug: true
340
363
  })
341
364
 
342
365
  const store = createStore(reducer, applyMiddleware(memorioRedux.middleware))
343
- ```
344
366
 
345
- Hydration is explicit and one-shot:
346
-
347
- ```ts
348
367
  await memorioRedux.hydrate(store, {
349
- onHydration(values) {
350
- store.dispatch(restorePreferences(values))
351
- },
368
+ onHydration(values) { store.dispatch(restorePreferences(values)) }
352
369
  })
353
370
  ```
354
371
 
355
- Direction of ownership — there is intentionally no permanent two-way mirror:
356
-
357
372
  ```text
358
- Redux ─────────────→ memorio memorio ────────────→ Redux
359
- application state durable memory bootstrap hydration
373
+ Redux ─────────────→ memorio memorio ───────────→ Redux
374
+ application state durable memory bootstrap hydration (one-shot)
360
375
  ```
361
376
 
362
- - only mapped selectors are persisted — the entire Redux tree is never mirrored
363
- - hydration is one-shot; restore dispatches do not create synchronization loops
364
- - persistence happens outside the reducer path; bursts of actions are coalesced
365
- - memorio failures do not break Redux dispatch — errors can be reported through `onError`
366
- - the adapter does not expose the memorio database through `globalThis` or DevTools
367
-
368
- ### memorio vs Redux, at a glance
377
+ The bridge persists only mapped selectors, never mirrors the full tree, coalesces bursts, and never breaks Redux dispatch if Memorio fails.
369
378
 
370
- Not a replacement — a different scope. Useful mainly for deciding which one (or both) fits a given piece of state.
371
-
372
- | | Redux | memorio |
379
+ | | Redux | Memorio |
373
380
  |---|---|---|
374
- | Core model | single store, plain object | multiple independent layers (`state`, `store`, `session`, `cache`, `idb`, `sqlite`, `memory`) |
375
- | State changes | via dispatched actions + reducers | direct mutation on a reactive proxy |
376
- | Persistence | not built in (needs `redux-persist` or similar) | built into `store`/`session`/`idb`/`sqlite` |
377
- | Structured "memory" (confidence, TTL, source, supersession) | not a concept in Redux | native, via the `memory` layer |
378
- | Offline sync / conflict resolution | not built in | built into `memory.configure()` (operations + journal + HLC ordering) |
379
- | Time-travel debugging, strict middleware pipelines | yes, mature ecosystem | not a goal — see [When to use something else](#when-to-use-something-else) |
380
- | Dependencies | small core, large plugin ecosystem | zero production dependencies |
381
-
382
- Pick Redux when you need its action-pipeline discipline and tooling. Pick memorio when you want state, persistence, and structured memory under one runtime without wiring several libraries together. Nothing stops you from using both — that's exactly what the [Redux bridge](#redux-and-other-state-managers) is for.
383
-
384
- ## React integration
385
-
386
- React is an integration, not a requirement — `useObserver` connects to the same observer system described above.
381
+ | Core model | single store | independent runtime layers |
382
+ | State changes | actions + reducers | direct reactive mutation |
383
+ | Persistence | external integration | built in |
384
+ | Structured memory | - | native |
385
+ | Offline memory sync | - | supported |
386
+ | Time-travel debugging | mature | State Intelligence (in progress) |
387
+ | Production dependencies | ecosystem-dependent | zero in core |
387
388
 
388
- ```tsx
389
- function Counter() {
390
- const [, forceUpdate] = useReducer(x => x + 1, 0)
391
- useObserver(forceUpdate, [state.counter])
392
- return <div>{state.counter}</div>
393
- }
394
- ```
395
-
396
- The runtime itself remains independent of React.
397
-
398
- ## Typed state & schema validation
399
-
400
- TypeScript types give compile-time guarantees without creating another state container:
401
-
402
- ```ts
403
- interface AppState {
404
- user: { name: string; age: number; email: string }
405
- theme: 'light' | 'dark'
406
- }
389
+ Use Redux when you need its action-pipeline discipline and ecosystem. Use Memorio when you want state, persistence, and structured memory under one runtime. You can use both.
407
390
 
408
- const app = memorio.typed<AppState>()
409
- app.theme = 'dark'
410
- app.theme = 'purple' // TypeScript error
411
- ```
412
-
413
- `app === state` — same proxy, no duplicated store.
414
-
415
- Runtime validation protects values crossing trust boundaries:
416
-
417
- ```ts
418
- memorio.registerSchema('user', {
419
- type: 'object',
420
- required: ['name', 'email'],
421
- properties: {
422
- name: { type: 'string', min: 1 },
423
- email: { type: 'string', pattern: /^[^@]+@[^@]+$/ },
424
- },
425
- })
426
-
427
- state.user = { name: 'Sara' } // rejected: missing required "email"
428
- ```
429
-
430
- Arrays validate each element via `items`:
431
-
432
- ```ts
433
- memorio.registerSchema('tags', { type: 'array', items: { type: 'string' } })
434
-
435
- state.tags = ['a', 'b'] // accepted
436
- state.tags = [1, 2, 3] // rejected
437
- ```
391
+ ---
438
392
 
439
393
  ## Local-first sync
440
394
 
441
- The local application owns its data — the cloud is optional transport. memorio synchronizes **operations** (`remember`, `update`, `forget`, `expire`, `confirm`, `supersede`), not database dumps. A local journal records operations so temporary network failures don't stop the application from working.
395
+ Memorio synchronizes **operations** (`remember`, `update`, `forget`, `expire`, `confirm`, `supersede`), not database dumps. A local journal keeps the app working through network failures; the cloud is optional transport, not a prerequisite.
442
396
 
443
397
  ```ts
444
398
  memorio.memory.configure({
445
399
  namespace: 'user:123:device:abc',
446
400
  provider: {
447
401
  push: ops => fetch('/api/sync', { method: 'POST', body: JSON.stringify(ops) }),
448
- pull: since => fetch(`/api/sync?since=${since}`).then(r => r.json()),
402
+ pull: since => fetch(`/api/sync?since=${since}`).then(r => r.json())
449
403
  },
450
- auto: true,
404
+ auto: true
451
405
  })
452
406
  ```
453
407
 
454
- ```text
455
- local application → memorio memory → local journal → offline / cloud
456
- ```
457
-
458
- The network is an extension of the local application, not its prerequisite.
459
-
460
- Remote operations are ordered causally using HLC timestamps. For concurrent writes to the same key, memorio provides a deterministic default — a custom resolver can override it:
408
+ Concurrent operations use HLC ordering by default; override with a custom resolver:
461
409
 
462
410
  ```ts
463
411
  memorio.memory.configure({
464
412
  resolveConflict(local, remote) {
465
413
  if (remote.source === 'user-correction') return remote
466
414
  return local.confidence >= remote.confidence ? local : remote
467
- },
415
+ }
468
416
  })
469
417
  ```
470
418
 
471
- Without a resolver, the default policy applies the remote entry on a tie. The resolver operates client-side — the sync provider is still responsible for any final server-side conflict policy.
419
+ The sync provider is still responsible for the final server-side conflict policy.
420
+
421
+ ---
472
422
 
473
423
  ## Cross-platform support
474
424
 
475
- | API | Browser | Node.js | Deno | Edge / Workers |
476
- |---|:---:|:---:|:---:|:---:|
477
- | `state` / `observer` / `cache` | ✅ | ✅ | ✅ | ✅ |
478
- | `store` / `session` | persistent | memory fallback | memory fallback | capability-dependent |
479
- | `idb` | ✅ | ❌ | ❌ | capability-dependent |
480
- | `sqlite` | ✅ | ❌ | ❌ | capability-dependent |
481
- | `devtools` | dev-only | ❌ | ❌ | capability-dependent |
425
+ | API | Browser | Node.js | Deno | Edge / Workers |
426
+ | -------------------------------- | :--------: | :---------------: | :----------------: | :---------------------: |
427
+ | `state` / `observer` / `cache` | ✅ | ✅ | ✅ | ✅ |
428
+ | `store` / `session` | persistent | memory fallback | memory fallback | capability-dependent |
429
+ | `idb` | ✅ | ❌ | ❌ | capability-dependent |
430
+ | `sqlite` | ✅ | ❌ | ❌ | capability-dependent |
431
+ | `memory` / `journal` | ✅ | ✅ | ✅ | ✅ |
432
+ | `devtools` | dev-only | ❌ | ❌ | capability-dependent |
482
433
 
483
434
  ```ts
484
435
  memorio.getCapabilities()
485
- memorio.isBrowser() / memorio.isNode() / memorio.isDeno() / memorio.isEdge()
436
+ memorio.isBrowser() // .isNode() · .isDeno() · .isEdge()
486
437
  ```
487
438
 
488
- Do not assume every persistence backend exists in every runtime.
439
+ Don't assume every persistence backend exists in every runtime - check first.
440
+
441
+ > **SQLite on Node.js / Deno:** Memorio's `sqlite` is backed by `sql.js` (SQLite compiled to WebAssembly), which requires browser APIs (`window`, `document`, `WebAssembly`). It is not available in Node.js or Deno. For server-side SQL, pair Memorio with a native SQLite package directly; use `store` or `idb` for in-process persistence from Memorio.
442
+
443
+ ---
489
444
 
490
445
  ## Security
491
446
 
492
- - Zero production dependencies — [checked by Socket.dev](https://socket.dev/npm/package/memorio) and [scanned by Snyk](https://snyk.io/test/npm/memorio)
493
- - No `eval`, no dynamic code execution, no bundled telemetry
494
- - Sanitized keys, validated inputs, caught module-boundary errors
495
- - Bounded journal entries, UUID-based session identifiers
447
+ Core has zero production dependencies. No `eval`, no dynamic code execution, no bundled telemetry, sanitized keys, validated inputs, bounded journal entries, UUID-based session identifiers. Independently checkable via Socket.dev / Snyk.
496
448
 
497
- > ⚠️ **memorio does not encrypt data by default.** This applies to `state`, `store`, `session`, `idb`, `memory`, and `sqlite`. Treat browser storage as client-controlled data — if your application handles auth secrets, credentials, or regulated data, bring your own encryption, backend authorization, and server-side security controls.
449
+ > ⚠️ **Memorio does not encrypt data by default** - this applies to every layer (`state`, `store`, `session`, `idb`, `memory`, `sqlite`). Treat browser storage as client-controlled. Use proper encryption and backend authorization for credentials or regulated data.
498
450
 
499
- **Contexts are not security boundaries.** `memorio.createContext('tenant-123')` is useful for organizing and isolating application concerns — it is **not** an authorization mechanism. Code running inside the same JS runtime can potentially access other memorio contexts. Real tenant isolation belongs at the backend/authentication layer.
451
+ **Contexts are not authorization:**
500
452
 
501
- memorio's DevTools are intended for development only, disabled via runtime detection (`process.env.NODE_ENV`) — not build-time removal. Make sure your bundler actually defines this correctly in production; don't treat DevTools absence as a security boundary.
453
+ ```ts
454
+ memorio.createContext('tenant-123')
455
+ ```
502
456
 
503
- ## Honest limitations
457
+ Contexts organize and isolate application concerns - they're not a security boundary. Code in the same JS runtime can potentially reach other contexts. Real tenant isolation belongs at the auth/backend layer.
504
458
 
505
- memorio deliberately documents its edges.
459
+ DevTools expose data for development only, and only when you opt into `memorio/global`.
506
460
 
507
- - **Semantic memory is structured, not vector-based.** `memory.context()` uses structured ranking, not embedding similarity search.
508
- - **SQLite persistence is not incremental** — the current strategy serializes the complete database on flush; large datasets need deliberate batching.
509
- - **Contexts and namespaces are not authorization** — they organize application data, they don't replace authentication or backend authorization.
510
- - **Data is not encrypted automatically** — memorio doesn't pretend local persistence is secure storage.
511
- - **DevTools are runtime-controlled, not build-removed** — production builds should still configure `NODE_ENV` correctly.
461
+ ---
512
462
 
513
463
  ## When to use something else
514
464
 
515
- memorio is intentionally not everything.
465
+ - **A strict Redux-style architecture** - when you need action pipelines, extensive middleware, event-sourcing, or mature time-travel tooling. Memorio can integrate rather than replace.
466
+ - **A server database** - for authoritative persistence, multi-user authorization, or server-side transactions.
467
+ - **A dedicated vector database** - for true embedding similarity search. Memorio can still manage the lifecycle/metadata of what you retrieve from it.
468
+
469
+ Memorio occupies a distinct space: **application state + history + causality + replay + impact analysis + simulation**. If you need to know *why* state changed, *who* changed it, or *what would happen* if it changed, Memorio is the tool - not just a state container.
516
470
 
517
- - **Need strict Redux-style architecture** (action pipelines, reducer-based transitions, extensive middleware, time-travel debugging, event-sourcing)? memorio can integrate with such systems rather than replace them.
518
- - **Need a server database** (authoritative persistence, multi-user authorization, server-side transactions, backend-controlled access)? Use one — memorio doesn't provide it.
519
- - **Need true embedding similarity / vector search**? Pair a dedicated vector database with `memorio.memory`, which can still manage the lifecycle and metadata of the retrieved knowledge.
471
+ ---
520
472
 
521
473
  ## Design philosophy
522
474
 
523
- 1. **Local first** — the application stays useful when the network disappears.
524
- 2. **Persistence is optional** — start in memory, persist only when it provides value.
525
- 3. **The cloud is optional** — remote sync extends the local application; it does not define it.
526
- 4. **Use the right primitive** — don't put relational data in a key/value store, or semantic memory in ordinary state.
527
- 5. **Memory has meaning** — confidence, source, type, scope, TTL, tags, history, status. That's different from simply storing a value.
528
- 6. **Global access is a convenience, not a requirement** — both `import 'memorio'` and `import { state } from 'memorio'` are valid; memorio does not impose one architecture.
529
- 7. **Tell the truth about boundaries** — local data is not automatically secure, persistence is not authorization, structured memory is not automatically AI semantic search.
530
- 8. **Keep the common case tiny:**
531
- ```ts
532
- import 'memorio'
533
- state.value = 42
534
- ```
535
- Everything else is there when you need it.
536
-
537
- ## A mental model
475
+ 1. **Local first** - the app stays useful when the network disappears.
476
+ 2. **Persistence is optional** - start in memory, persist only when it adds value.
477
+ 3. **The cloud is optional** - sync extends the local app; it doesn't define it.
478
+ 4. **Use the right primitive** - relational data belongs in SQL, not a kv store; memory isn't the same as ordinary state.
479
+ 5. **Memory has meaning** - confidence, source, type, scope, TTL, tags, history, status: more than "just a value."
480
+ 6. **Global access is explicit** - opt in with `import 'memorio/global'`, or keep it explicit with named imports.
481
+ 7. **Tell the truth about boundaries** - not encrypted, not authorization, not embedding search unless paired with one.
482
+ 8. **Keep the common case tiny** - `state.value = 42` is a complete, valid program.
538
483
 
539
- ```text
540
- memorio
541
- │
542
- ┌──────────────┼──────────────┐
543
- │ │ │
544
- runtime persistence memory
545
- │ │ │
546
- ┌─────┼─────┐ ┌────┼────┐ structured
547
- │ │ │ │ │ │ knowledge
548
- state cache session store idb sqlite
549
- │
550
- observer
551
- │
552
- ▼
553
- application → journal → optional sync → cloud
554
- ```
555
-
556
- The important part is not that memorio has many layers — it's that **you do not have to use them all**.
484
+ ---
557
485
 
558
- ## Sneak Peek
486
+ ## Quick recipes
559
487
 
560
- ### Making a `state` slice persistent
488
+ ```ts
489
+ // Global state
490
+ import 'memorio/global'
491
+ state.user = { name: 'Sara', role: 'admin' }
561
492
 
562
- `state` is reactive but not persistent; `store` is persistent but not reactive. `persist()` bridges the two — it restores the value from `store` and then keeps the selected state path synchronized with it:
493
+ // Explicit state
494
+ import { state } from 'memorio'
495
+ state.counter++
563
496
 
564
- ```ts
565
- import 'memorio'
497
+ // Persistent kv
498
+ import { store } from 'memorio'
499
+ store.set('preferences', { theme: 'dark' })
566
500
 
501
+ // Persistent reactive state
502
+ import { state, persist } from 'memorio'
567
503
  state.user = { name: 'Sara' }
504
+ const off = persist('state.user')
568
505
 
569
- const off = persist('state.user') // restore from store, then persist changes
570
-
571
- // later, e.g. on component unmount
572
- off()
573
- ```
506
+ // Application memory
507
+ import { memorio } from 'memorio'
508
+ await memorio.memory.remember('user.language', 'Italian', {
509
+ type: 'preference', confidence: 0.92, source: 'conversation'
510
+ })
574
511
 
575
- `persist()` uses the same path syntax as `observer()`.
512
+ // Observe a value
513
+ import { observer } from 'memorio'
514
+ const off = observer('state.user', (next, previous) => console.log(next, previous))
576
515
 
577
- Nested paths such as `state.user.preferences.theme` are supported, provided the intermediate objects already exist in `state`.
516
+ // Track mutations, undo, and trace history
517
+ import { memorio } from 'memorio'
518
+ memorio.enableHistory(true)
519
+ memorio.mutate('state.user.role', 'admin', { source: 'permissions.save' })
520
+ const tx = memorio.transaction('migration', 'v2 schema migration')
521
+ state.user.v2 = true
522
+ memorio.commitTransaction()
523
+ memorio.undo()
524
+ console.debug(memorio.trace())
525
+ ```
578
526
 
579
- In environments without `localStorage` (Node/Deno/edge), `store` falls back to memory. `persist()` still works there, but persistence does not survive process or runtime restarts.
527
+ ---
580
528
 
581
529
  ## License
582
530