memorio 4.9.30 → 4.9.35

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
@@ -18,18 +18,30 @@ state.counter++
18
18
  ```
19
19
 
20
20
  No provider tree. No reducers. No actions. No boilerplate.
21
- Just data that exists where your application needs it — and grows with it.
21
+ Just data, available where your application needs it.
22
+
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.
22
30
 
23
31
  ---
24
32
 
25
33
  ## Table of contents
26
34
 
27
35
  - [Why memorio](#why-memorio)
36
+ - [Global is optional](#global-is-optional)
37
+ - [Before / After](#before--after)
28
38
  - [Which layer should I use](#which-layer-should-i-use)
29
39
  - [Install](#install)
30
40
  - [Quick start](#quick-start)
31
41
  - [Observing changes](#observing-changes)
32
- - [The layers](#the-layers) — state · store · session · cache · idb · sqlite · memory
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)
33
45
  - [React integration](#react-integration)
34
46
  - [Typed state & schema validation](#typed-state--schema-validation)
35
47
  - [Local-first sync](#local-first-sync)
@@ -38,62 +50,105 @@ Just data that exists where your application needs it — and grows with it.
38
50
  - [Honest limitations](#honest-limitations)
39
51
  - [When to use something else](#when-to-use-something-else)
40
52
  - [Design philosophy](#design-philosophy)
53
+ - [A mental model](#a-mental-model)
54
+ - [Recipes](#recipes)
41
55
  - [License](#license)
42
56
 
43
57
  ---
44
58
 
45
59
  ## Why memorio
46
60
 
47
- Modern apps usually split their data across systems that don't talk to each other: a state manager, `localStorage`, `sessionStorage`, IndexedDB, a database, a cache, maybe an AI memory layer, maybe a sync layer on top. Each with its own API, its own mental model, its own edge cases.
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.
48
62
 
49
- memorio gives these concerns **one runtime and one mental model** — without forcing you to use all of it.
63
+ memorio brings these concerns into **one runtime and one consistent model**, while keeping each layer independent.
50
64
 
51
- | Layer | Purpose | Maturity |
52
- |---|---|---|
53
- | `state` | reactive volatile application state | Stable |
54
- | `cache` | transient runtime data | Stable |
55
- | `session` | session-scoped persistence | Stable |
56
- | `store` | persistent key/value data | Stable |
57
- | `idb` | durable structured browser data | Stable |
58
- | `sqlite` | relational data & SQL | Beta |
59
- | `memory` | semantic app/agent memory | Beta |
60
- | `journal` | local-first operation history | Beta |
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 |
75
+
76
+ Start with `state`. Add persistence or memory only when your application actually needs it.
77
+
78
+ ## Global is optional
79
+
80
+ ```ts
81
+ import 'memorio'
82
+ state.user = { name: 'Sara' }
83
+ ```
84
+
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:
86
+
87
+ ```ts
88
+ import { state } from 'memorio'
89
+ state.user = { name: 'Sara' }
90
+ ```
61
91
 
62
- Start with `state`. Add the rest only when your app actually needs it.
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.
93
+
94
+ **Global when convenient. Explicit when appropriate.**
63
95
 
64
96
  ## Which layer should I use
65
97
 
66
98
  ```text
67
99
  Does the UI need to react automatically to changes?
68
100
  │
69
- ├─ Yes → state, or a memory-backed reactive slice
101
+ ├─ Yes → state
70
102
  │
71
- └─ No, I just need to store/retrieve a value
72
- │
73
- ├─ Survive a reload?
74
- │ ├─ No → cache
75
- │ ├─ This tab only → session
76
- │ └─ Yes, indefinitely→ store (small) or idb (larger/structured)
103
+ └─ No
77
104
  │
78
- ├─ Need relations, joins, SQL? → sqlite
79
- └─ Is this "knowledge" the app reasons
80
- about (confidence, source, expiry)? → memory
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
81
112
  ```
82
113
 
83
114
  ## Install
84
115
 
85
116
  ```bash
86
- npm i memorio
117
+ npm install memorio
87
118
  ```
88
119
 
89
120
  Optional peers, only loaded when used:
90
121
 
91
122
  ```bash
92
- npm i react react-dom # React integration
93
- npm i sql.js # SQLite engine
123
+ npm install react react-dom # React integration
124
+ npm install sql.js # sqlite engine
125
+ ```
126
+
127
+ **Zero production dependencies** in the core package. See [Security](#security).
128
+
129
+ ## Before / After
130
+
131
+ ```ts
132
+ // Without memorio
133
+ const [user, setUser] = useState(null)
134
+ useEffect(() => {
135
+ const saved = localStorage.getItem('user')
136
+ if (saved) setUser(JSON.parse(saved))
137
+ }, [])
138
+ useEffect(() => {
139
+ localStorage.setItem('user', JSON.stringify(user))
140
+ }, [user])
141
+ ```
142
+
143
+ ```ts
144
+ // With memorio
145
+ import 'memorio'
146
+ store.set('user', { name: 'Sara' }) // persistent immediately, no boilerplate
147
+
148
+ const user = store.get('user')
94
149
  ```
95
150
 
96
- **Zero production dependencies.** See [Security](#security).
151
+ Same result, one reactive line instead of two effects and manual JSON serialization.
97
152
 
98
153
  ## Quick start
99
154
 
@@ -101,16 +156,24 @@ npm i sql.js # SQLite engine
101
156
  import 'memorio'
102
157
 
103
158
  state.user = { name: 'Sara', role: 'admin' }
104
- const name = state.user.name
159
+ state.counter++
160
+
161
+ console.log(state.user.name)
162
+ console.log(state.counter)
105
163
  ```
106
164
 
107
- Reactive, in-memory, Proxy-based, globally accessible. Lock a slice you don't want mutated by accident:
165
+ Reactive, Proxy-based state. No provider tree to configure, no reducer to maintain.
166
+
167
+ Lock a slice you don't want mutated by accident:
108
168
 
109
169
  ```ts
110
170
  state.config = { maxUsers: 100 }
111
171
  state.config.lock()
172
+
112
173
  state.config.maxUsers = 200 // throws
174
+
113
175
  state.config.unlock()
176
+ state.config.maxUsers = 200 // ok
114
177
  ```
115
178
 
116
179
  Prefer explicit imports over the global? Same runtime either way:
@@ -121,7 +184,7 @@ import { state, store, session, cache, idb, sqlite, memorio } from 'memorio'
121
184
 
122
185
  ## Observing changes
123
186
 
124
- Reactivity isn't a React add-on — it's built into the runtime. Watch any state path directly, in any environment:
187
+ Reactivity is part of the runtime — it is not tied to React.
125
188
 
126
189
  ```ts
127
190
  observer('state.user', (next, previous) => {
@@ -132,102 +195,40 @@ observer('state.user', (next, previous) => {
132
195
  observer('state.user.name', callback)
133
196
  ```
134
197
 
135
- `dispatch` is the event mechanism underneath it, if you need to hook in lower-level:
198
+ Lower-level event access is also available:
136
199
 
137
200
  ```ts
138
201
  const off = memorio.dispatch.listen('state.user', event => console.debug(event.detail))
139
202
  memorio.dispatch.set('state.user', { detail: { name: 'Sara' } })
140
203
 
141
- off() // remove only this subscription
142
- memorio.dispatch.remove('state.user') // remove all subscriptions on a name
204
+ off() // remove only this subscription
143
205
  ```
144
206
 
145
- Multiple subscribers on the same path each keep firing — each `listen` returns
146
- its own unsubscribe. Prefer the returned `off()` over `remove()` for per-caller
147
- teardown, since `remove()` clears *every* listener registered under that name.
148
-
149
- `observer` paths are runtime strings — for compiler-checked access, see [typed state](#typed-state--schema-validation).
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.
150
208
 
151
209
  React apps get a dedicated hook, `useObserver` — see [React integration](#react-integration).
152
210
 
153
- ## Redux / state-manager integration
154
-
155
- Memorio ships its Redux integration as a **separate, optional entrypoint** (`memorio/redux`). It is
156
- *not* part of core and is **never imported by `import memorio from 'memorio'`** — so applications
157
- that don't use Redux pay nothing. Redux is neither a dependency of Memorio nor of the adapter: the
158
- adapter talks to any store that satisfies a tiny structural shape (`getState` / `subscribe` /
159
- `dispatch`), so `redux`, Redux Toolkit, Zustand, NgRx, etc. are all accepted without importing the
160
- `redux` types.
161
-
162
- > **Ownership rule:** *Redux owns application state. Memorio owns memory.* The adapter only writes
163
- > **selected** values (declared opt-in mappings) into Memorio memory, and only recalls them once,
164
- > at bootstrap. It never mirrors the whole Redux tree and never two-way syncs.
165
-
166
- ```ts
167
- import memorio from 'memorio'
168
- import { createMemorioReduxBridge } from 'memorio/redux'
169
-
170
- type AppState = { user: { name: string; preferences?: { theme?: string } } }
171
-
172
- const memorioRedux = createMemorioReduxBridge<AppState>({
173
- // Opt-in: only these paths become durable memory.
174
- mappings: {
175
- 'user.preferences': {
176
- selector: (s) => s.user.preferences,
177
- type: 'preference',
178
- scope: 'local', // never 'hot' for Redux mappings
179
- tags: ['app', 'user']
180
- },
181
- 'user.name': (s) => s.user.name // a bare selector is also accepted
182
- },
183
- // Performance: only consider the action types that can mutate mapped state.
184
- whitelist: ['user/preferencesChanged', 'user/nameChanged'],
185
- // Diagnostics
186
- debug: true
187
- })
188
-
189
- const store = createStore(reducer, applyMiddleware(memorioRedux.middleware))
190
-
191
- // Bootstrap hydration (Memorio -> Redux, one-shot). The bridge arms a
192
- // re-entrancy guard so the restore dispatch does not echo back to Memorio.
193
- await memorioRedux.hydrate(store, {
194
- onHydration: (values) => store.dispatch(restorePreferences(values))
195
- })
196
- ```
197
-
198
- Directional flow, no loops:
199
-
200
- | Direction | Mechanism | Ownership |
201
- | --- | --- | --- |
202
- | Redux -> Memorio | middleware persists only `mappings` whose selector changed **and** whose action type isn't blacklisted (#30) | Memorio is the durable owner (#31) |
203
- | Memorio -> Redux | `hydrate()` recalls once at bootstrap; the app dispatches its own restore actions | Redux owns runtime state |
204
-
205
- - **No store mirroring:** only mapped selectors are persisted — never `store.getState()` wholesale.
206
- - **No synchronization loops:** hydration arms `setHydrating(true)` + seeds the change-detection cache, so the restore dispatch is seen as "no change" and is not re-persisted.
207
- - **Async is off the reducer path:** middleware defers `memorio.memory.remember` to a microtask and coerces a burst of actions into a single flush.
208
- - **Failure-isolated:** Memorio errors are swallowed by default (`failSoft: true`, default) and routed to `onError`; Redux dispatch always completes normally.
209
- - **Production-safe:** nothing in the adapter exposes Memorio's database on `globalThis` / DevTools.
210
-
211
211
  ## The layers
212
212
 
213
- ### `store` — persistent key/value
213
+ ### `store` — persistent key/value data
214
214
 
215
215
  ```ts
216
216
  store.set('preferences', { theme: 'dark' })
217
217
  const preferences = store.get('preferences')
218
218
  ```
219
219
 
220
- Backed by `localStorage` in the browser (`store.isPersistent === true`); falls back to memory elsewhere.
220
+ Backed by `localStorage` in the browser. Falls back to memory in environments without persistent storage — check `store.isPersistent` when portability matters.
221
221
 
222
- ### `session` — follows the tab
222
+ ### `session` — data for the current session
223
223
 
224
224
  ```ts
225
- session.set('token', 'user-abc-123')
225
+ session.set('wizard-step', 3)
226
+ const step = session.get('wizard-step')
226
227
  ```
227
228
 
228
- Backed by `sessionStorage`. Good for auth state, wizards, tab-scoped data.
229
+ Backed by `sessionStorage`. Good for multi-step workflows, temporary preferences, tab-scoped data.
229
230
 
230
- ### `cache` — volatile, fast
231
+ ### `cache` — volatile runtime data
231
232
 
232
233
  ```ts
233
234
  cache.set('expensive-result', computeExpensiveResult())
@@ -235,18 +236,19 @@ cache.set('expensive-result', computeExpensiveResult())
235
236
 
236
237
  Disappears when the runtime disappears. No persistence guarantee, ever.
237
238
 
238
- ### `idb` — durable, structured
239
+ ### `idb` — durable structured browser data
239
240
 
240
241
  ```ts
241
242
  await idb.db.create('app')
242
243
  await idb.table.create('app', 'users')
243
244
  await idb.data.set('app', 'users', { id: 1, name: 'Sara' })
245
+
244
246
  const user = await idb.data.get('app', 'users', 1)
245
247
  ```
246
248
 
247
- Check before relying on it in portable code: `memorio.getCapabilities()`.
249
+ Check runtime support before depending on it: `memorio.getCapabilities()`.
248
250
 
249
- ### `sqlite` — a real local SQL engine
251
+ ### `sqlite` — local SQL
250
252
 
251
253
  ```ts
252
254
  await sqlite.ready
@@ -260,17 +262,17 @@ await sqlite.query.run('app', `INSERT INTO users (name, role) VALUES (?, ?)`, ['
260
262
  const admins = await sqlite.query.select('app', `SELECT * FROM users WHERE role = ?`, ['admin'])
261
263
  ```
262
264
 
263
- Runs **in memory by default**, powered by `sql.js` (lazy-loaded — see [loading strategies](#cross-platform-support)). Enable persistence explicitly when you need it:
265
+ Runs **in memory by default**, using `sql.js`. Persistence is explicit:
264
266
 
265
267
  ```ts
266
268
  await sqlite.db.create('app', { persistence: true })
267
269
  ```
268
270
 
269
- > ⚠️ Persistence serializes the **entire** database on each flush — it is not incremental. Fine for small/medium data; for larger datasets, persist deliberately after a batch of writes, not on every mutation. See [Honest limitations](#honest-limitations).
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.
270
272
 
271
- ### `memory` — semantic application memory
273
+ ### `memory` — application memory
272
274
 
273
- The layer that makes memorio more than a state manager. Structured memory with type, confidence, TTL, tags, source, scope, and a real lifecycle:
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.
274
276
 
275
277
  ```ts
276
278
  await memorio.memory.remember('user.language', 'Italian', {
@@ -284,14 +286,14 @@ await memorio.memory.remember('user.language', 'Italian', {
284
286
  const language = await memorio.memory.recall('user.language')
285
287
  ```
286
288
 
287
- Updates don't overwrite — they **supersede**, preserving history:
289
+ Updates don't overwrite — they supersede, preserving history:
288
290
 
289
291
  ```ts
290
292
  await memorio.memory.update('user.language', 'English', { confidence: 0.95 })
291
- // old entry → status: 'superseded' | new entry → status: 'active'
293
+ // previous entry → status: 'superseded' | new entry → status: 'active'
292
294
  ```
293
295
 
294
- Retrieve what's *relevant*, not everything:
296
+ Retrieve what's relevant to the current operation, not everything:
295
297
 
296
298
  ```ts
297
299
  const context = await memorio.memory.context({
@@ -302,13 +304,86 @@ const context = await memorio.memory.context({
302
304
  })
303
305
  ```
304
306
 
305
- > ℹ️ **"Semantic" here means structured, not embedding-based.** `memory.context()` ranks by tags, type, confidence and recency — there's no vector similarity search under the hood (yet — see [roadmap](#honest-limitations)). Need true meaning-based retrieval over free text? Pair this layer with your own embedding store and use `memorio.memory` for the lifecycle (confidence, TTL, supersession) on top.
307
+ The context system considers tags, type, confidence, and recency.
306
308
 
307
- `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.
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).
310
+
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.
312
+
313
+ ## Redux and other state managers
314
+
315
+ memorio does not need to replace your existing state manager. Its Redux integration ships as a **separate, optional entry point**:
316
+
317
+ ```ts
318
+ import { createMemorioReduxBridge } from 'memorio/redux'
319
+ ```
320
+
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.
322
+
323
+ > **Ownership rule:** your state manager owns application state. memorio owns memory.
324
+
325
+ The bridge does not mirror the entire store — only explicitly mapped values are persisted:
326
+
327
+ ```ts
328
+ const memorioRedux = createMemorioReduxBridge<AppState>({
329
+ mappings: {
330
+ 'user.preferences': {
331
+ selector: state => state.user.preferences,
332
+ type: 'preference',
333
+ scope: 'local',
334
+ tags: ['app', 'user'],
335
+ },
336
+ 'user.name': state => state.user.name,
337
+ },
338
+ whitelist: ['user/preferencesChanged', 'user/nameChanged'],
339
+ debug: true,
340
+ })
341
+
342
+ const store = createStore(reducer, applyMiddleware(memorioRedux.middleware))
343
+ ```
344
+
345
+ Hydration is explicit and one-shot:
346
+
347
+ ```ts
348
+ await memorioRedux.hydrate(store, {
349
+ onHydration(values) {
350
+ store.dispatch(restorePreferences(values))
351
+ },
352
+ })
353
+ ```
354
+
355
+ Direction of ownership — there is intentionally no permanent two-way mirror:
356
+
357
+ ```text
358
+ Redux ─────────────→ memorio memorio ────────────→ Redux
359
+ application state durable memory bootstrap hydration
360
+ ```
361
+
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
369
+
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 |
373
+ |---|---|---|
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.
308
383
 
309
384
  ## React integration
310
385
 
311
- React is an integration, not a requirement — `useObserver` is a thin bridge onto the same `observer` mechanism from above:
386
+ React is an integration, not a requirement — `useObserver` connects to the same observer system described above.
312
387
 
313
388
  ```tsx
314
389
  function Counter() {
@@ -318,9 +393,11 @@ function Counter() {
318
393
  }
319
394
  ```
320
395
 
396
+ The runtime itself remains independent of React.
397
+
321
398
  ## Typed state & schema validation
322
399
 
323
- TypeScript types for compile-time safety:
400
+ TypeScript types give compile-time guarantees without creating another state container:
324
401
 
325
402
  ```ts
326
403
  interface AppState {
@@ -330,12 +407,12 @@ interface AppState {
330
407
 
331
408
  const app = memorio.typed<AppState>()
332
409
  app.theme = 'dark'
333
- app.theme = 'purple' // ❌ TypeScript error
410
+ app.theme = 'purple' // TypeScript error
334
411
  ```
335
412
 
336
- `app === state` — same Proxy, no duplicated store.
413
+ `app === state` — same proxy, no duplicated store.
337
414
 
338
- Runtime validation for values crossing trust boundaries:
415
+ Runtime validation protects values crossing trust boundaries:
339
416
 
340
417
  ```ts
341
418
  memorio.registerSchema('user', {
@@ -347,41 +424,40 @@ memorio.registerSchema('user', {
347
424
  },
348
425
  })
349
426
 
350
- state.user = { name: 'Sara' } // rejected — missing required "email"
427
+ state.user = { name: 'Sara' } // rejected: missing required "email"
351
428
  ```
352
429
 
353
- Array item validation is supported per-element with the `items` schema field:
430
+ Arrays validate each element via `items`:
354
431
 
355
432
  ```ts
356
- memorio.registerSchema('tags', {
357
- type: 'array',
358
- items: { type: 'string' },
359
- })
433
+ memorio.registerSchema('tags', { type: 'array', items: { type: 'string' } })
360
434
 
361
435
  state.tags = ['a', 'b'] // accepted
362
- state.tags = [1, 2, 3] // rejected — items must be strings
436
+ state.tags = [1, 2, 3] // rejected
363
437
  ```
364
438
 
365
439
  ## Local-first sync
366
440
 
367
- The local application owns its data; the cloud is optional transport.
368
-
369
- memorio syncs **operations** (`remember`, `update`, `forget`, `expire`, `confirm`, `supersede`), not database dumps, via a local journal that survives network failure:
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.
370
442
 
371
443
  ```ts
372
444
  memorio.memory.configure({
373
445
  namespace: 'user:123:device:abc',
374
446
  provider: {
375
- push: (ops) => fetch('/api/sync', { method: 'POST', body: JSON.stringify(ops) }),
376
- pull: (since) => fetch(`/api/sync?since=${since}`).then(r => r.json()),
447
+ push: ops => fetch('/api/sync', { method: 'POST', body: JSON.stringify(ops) }),
448
+ pull: since => fetch(`/api/sync?since=${since}`).then(r => r.json()),
377
449
  },
378
450
  auto: true,
379
451
  })
380
452
  ```
381
453
 
382
- When `replay()` pulls remote operations, entries are ordered causally by HLC timestamp. For
383
- *concurrent* writes to the same key (an HLC tie) the **remote entry wins by default**; provide a
384
- `resolveConflict` to override:
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:
385
461
 
386
462
  ```ts
387
463
  memorio.memory.configure({
@@ -392,66 +468,115 @@ memorio.memory.configure({
392
468
  })
393
469
  ```
394
470
 
395
- When no resolver is supplied, Memorio logs a warn (dev only) and applies the remote entry on a
396
- tie. The resolver only settles *client-side* divergence between what memorio has seen locally —
397
- the provider is still responsible for the final server-side policy.
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.
398
472
 
399
473
  ## Cross-platform support
400
474
 
401
475
  | API | Browser | Node.js | Deno | Edge / Workers |
402
- |---|---:|---:|---:|---:|
476
+ |---|:---:|:---:|:---:|:---:|
403
477
  | `state` / `observer` / `cache` | ✅ | ✅ | ✅ | ✅ |
404
478
  | `store` / `session` | persistent | memory fallback | memory fallback | capability-dependent |
405
479
  | `idb` | ✅ | ❌ | ❌ | capability-dependent |
406
480
  | `sqlite` | ✅ | ❌ | ❌ | capability-dependent |
407
- | `devtools` | ✅ (dev-only) | ❌ | ❌ | capability-dependent |
481
+ | `devtools` | dev-only | ❌ | ❌ | capability-dependent |
408
482
 
409
483
  ```ts
410
484
  memorio.getCapabilities()
411
- memorio.isBrowser() / isNode() / isDeno() / isEdge()
485
+ memorio.isBrowser() / memorio.isNode() / memorio.isDeno() / memorio.isEdge()
412
486
  ```
413
487
 
488
+ Do not assume every persistence backend exists in every runtime.
489
+
414
490
  ## Security
415
491
 
416
- - Zero production dependencies — [verified by Socket.dev](https://socket.dev/npm/package/memorio) and [scanned by Snyk](https://snyk.io/test/npm/memorio)
492
+ - Zero production dependencies — [checked by Socket.dev](https://socket.dev/npm/package/memorio) and [scanned by Snyk](https://snyk.io/test/npm/memorio)
417
493
  - No `eval`, no dynamic code execution, no bundled telemetry
418
494
  - Sanitized keys, validated inputs, caught module-boundary errors
419
- - UUID-based session identifiers, bounded journal entries
495
+ - Bounded journal entries, UUID-based session identifiers
420
496
 
421
- **What memorio does *not* do:** it does not encrypt `state`, `store`, `session`, `idb`, local memory, or SQLite contents. Treat browser storage as client-controlled data. If you handle auth tokens, secrets, or regulated data, bring your own encryption and backend security.
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.
422
498
 
423
- **Contexts, scopes, and namespaces are not security boundaries.** `memorio.createContext('tenant-123')` is for code organization — anything in the same JS runtime can, in principle, reach any context through the memorio API. Real tenant isolation belongs at the backend/auth layer.
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.
500
+
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.
424
502
 
425
503
  ## Honest limitations
426
504
 
427
- We'd rather tell you where the edges are than have you find them in production.
505
+ memorio deliberately documents its edges.
428
506
 
429
- - **`memory.context()` is rule-based, not embedding-based.** No free-text semantic similarity yet — see [roadmap](#when-to-use-something-else).
430
- - **SQLite persistence is a full serialize-on-flush**, not incremental. Costly for large datasets if triggered on every write.
431
- - **Namespaces and contexts are organizational, not authorization boundaries.**
432
- - **Nothing is encrypted by default**, anywhere.
433
- - **DevTools are dev-only by runtime detection** (`process.env.NODE_ENV`), not a build-time strip — double-check your bundler actually sets this in production.
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.
434
512
 
435
513
  ## When to use something else
436
514
 
437
- - **Need strict Redux-style architecture** (action pipelines, middleware, time-travel debugging) → use a dedicated Redux-style setup.
438
- - **Need hard security isolation** → real backend authorization, process isolation, encryption. Don't lean on memorio contexts.
439
- - **Need durable server storage** → Node/edge runtimes don't gain browser persistence for free. Use a server database.
440
- - **Need true semantic/embedding retrieval** → pair `memorio.memory` with a dedicated embedding store; use memorio for the lifecycle metadata on top.
515
+ memorio is intentionally not everything.
516
+
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.
441
520
 
442
521
  ## Design philosophy
443
522
 
444
- 1. **Local first** — the app stays useful when the network disappears.
445
- 2. **Persistence is incremental** — start with memory, persist only when useful.
446
- 3. **The cloud is optional** — an extension, never a prerequisite.
447
- 4. **Choose the right primitive** — don't put relational data in a key/value store, or semantic memory in ordinary state.
448
- 5. **Memory has meaning** — confidence, source, lifetime, scope, type, history.
449
- 6. **Tell the truth about boundaries** — volatile, unencrypted, not-a-security-boundary: say so, everywhere it applies.
450
- 7. **Keep the common case tiny**:
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:**
451
531
  ```ts
452
532
  import 'memorio'
453
533
  state.value = 42
454
534
  ```
535
+ Everything else is there when you need it.
536
+
537
+ ## A mental model
538
+
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**.
557
+
558
+ ## Sneak Peek
559
+
560
+ ### Making a `state` slice persistent
561
+
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:
563
+
564
+ ```ts
565
+ import 'memorio'
566
+
567
+ state.user = { name: 'Sara' }
568
+
569
+ const off = persist('state.user') // restore from store, then persist changes
570
+
571
+ // later, e.g. on component unmount
572
+ off()
573
+ ```
574
+
575
+ `persist()` uses the same path syntax as `observer()`.
576
+
577
+ Nested paths such as `state.user.preferences.theme` are supported, provided the intermediate objects already exist in `state`.
578
+
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.
455
580
 
456
581
  ## License
457
582