memorio 4.9.10 → 4.9.31

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,29 @@ 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)
28
37
  - [Which layer should I use](#which-layer-should-i-use)
29
38
  - [Install](#install)
30
39
  - [Quick start](#quick-start)
31
40
  - [Observing changes](#observing-changes)
32
- - [The layers](#the-layers) — state · store · session · cache · idb · sqlite · memory
41
+ - [The layers](#the-layers) — store · session · cache · idb · sqlite · memory
42
+ - [Redux and other state managers](#redux-and-other-state-managers)
43
+ - [memorio vs Redux, at a glance](#memorio-vs-redux-at-a-glance)
33
44
  - [React integration](#react-integration)
34
45
  - [Typed state & schema validation](#typed-state--schema-validation)
35
46
  - [Local-first sync](#local-first-sync)
@@ -38,62 +49,80 @@ Just data that exists where your application needs it — and grows with it.
38
49
  - [Honest limitations](#honest-limitations)
39
50
  - [When to use something else](#when-to-use-something-else)
40
51
  - [Design philosophy](#design-philosophy)
52
+ - [A mental model](#a-mental-model)
41
53
  - [License](#license)
42
54
 
43
55
  ---
44
56
 
45
57
  ## Why memorio
46
58
 
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.
59
+ 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
60
 
49
- memorio gives these concerns **one runtime and one mental model** — without forcing you to use all of it.
61
+ memorio brings these concerns into **one runtime and one consistent model**, while keeping each layer independent.
50
62
 
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 |
63
+ | Layer | Purpose | Persistence | Reactive |
64
+ |---|---|---:|---:|
65
+ | `state` | application state | No | Yes |
66
+ | `cache` | transient runtime data | No | No |
67
+ | `session` | tab/session data | Yes | No |
68
+ | `store` | persistent key/value data | Yes | No |
69
+ | `idb` | durable structured browser data | Yes | No |
70
+ | `sqlite` *(beta)* | relational local SQL | Optional | No |
71
+ | `memory` | structured application memory | Yes | Optional |
72
+ | `journal` *(beta)* | local-first operation history | Yes | No |
73
+
74
+ Start with `state`. Add persistence or memory only when your application actually needs it.
75
+
76
+ ## Global is optional
77
+
78
+ ```ts
79
+ import 'memorio'
80
+ state.user = { name: 'Sara' }
81
+ ```
82
+
83
+ 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:
84
+
85
+ ```ts
86
+ import { state } from 'memorio'
87
+ state.user = { name: 'Sara' }
88
+ ```
89
+
90
+ 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.
61
91
 
62
- Start with `state`. Add the rest only when your app actually needs it.
92
+ **Global when convenient. Explicit when appropriate.**
63
93
 
64
94
  ## Which layer should I use
65
95
 
66
96
  ```text
67
97
  Does the UI need to react automatically to changes?
68
98
  │
69
- ├─ Yes → state, or a memory-backed reactive slice
99
+ ├─ Yes → state
70
100
  │
71
- └─ No, I just need to store/retrieve a value
101
+ └─ No
72
102
  │
73
- ├─ Survive a reload?
74
- │ ├─ No → cache
75
- │ ├─ This tab only → session
76
- │ └─ Yes, indefinitely→ store (small) or idb (larger/structured)
77
- │
78
- ├─ Need relations, joins, SQL? → sqlite
79
- └─ Is this "knowledge" the app reasons
80
- about (confidence, source, expiry)? → memory
103
+ ├─ Only while the runtime exists? → cache
104
+ ├─ Only for this browser tab/session? → session
105
+ ├─ Small persistent key/value data? → store
106
+ ├─ Larger or structured browser data? → idb
107
+ ├─ Relations, joins, or SQL? → sqlite
108
+ └─ Knowledge the application should
109
+ remember (confidence, source, expiry)? → memory
81
110
  ```
82
111
 
83
112
  ## Install
84
113
 
85
114
  ```bash
86
- npm i memorio
115
+ npm install memorio
87
116
  ```
88
117
 
89
118
  Optional peers, only loaded when used:
90
119
 
91
120
  ```bash
92
- npm i react react-dom # React integration
93
- npm i sql.js # SQLite engine
121
+ npm install react react-dom # React integration
122
+ npm install sql.js # sqlite engine
94
123
  ```
95
124
 
96
- **Zero production dependencies.** See [Security](#security).
125
+ **Zero production dependencies** in the core package. See [Security](#security).
97
126
 
98
127
  ## Quick start
99
128
 
@@ -101,16 +130,24 @@ npm i sql.js # SQLite engine
101
130
  import 'memorio'
102
131
 
103
132
  state.user = { name: 'Sara', role: 'admin' }
104
- const name = state.user.name
133
+ state.counter++
134
+
135
+ console.log(state.user.name)
136
+ console.log(state.counter)
105
137
  ```
106
138
 
107
- Reactive, in-memory, Proxy-based, globally accessible. Lock a slice you don't want mutated by accident:
139
+ Reactive, Proxy-based state. No provider tree to configure, no reducer to maintain.
140
+
141
+ Lock a slice you don't want mutated by accident:
108
142
 
109
143
  ```ts
110
144
  state.config = { maxUsers: 100 }
111
145
  state.config.lock()
146
+
112
147
  state.config.maxUsers = 200 // throws
148
+
113
149
  state.config.unlock()
150
+ state.config.maxUsers = 200 // ok
114
151
  ```
115
152
 
116
153
  Prefer explicit imports over the global? Same runtime either way:
@@ -121,7 +158,7 @@ import { state, store, session, cache, idb, sqlite, memorio } from 'memorio'
121
158
 
122
159
  ## Observing changes
123
160
 
124
- Reactivity isn't a React add-on — it's built into the runtime. Watch any state path directly, in any environment:
161
+ Reactivity is part of the runtime — it is not tied to React.
125
162
 
126
163
  ```ts
127
164
  observer('state.user', (next, previous) => {
@@ -132,37 +169,40 @@ observer('state.user', (next, previous) => {
132
169
  observer('state.user.name', callback)
133
170
  ```
134
171
 
135
- `dispatch` is the event mechanism underneath it, if you need to hook in lower-level:
172
+ Lower-level event access is also available:
136
173
 
137
174
  ```ts
138
- memorio.dispatch.listen('state.user', event => console.debug(event.detail))
175
+ const off = memorio.dispatch.listen('state.user', event => console.debug(event.detail))
139
176
  memorio.dispatch.set('state.user', { detail: { name: 'Sara' } })
177
+
178
+ off() // remove only this subscription
140
179
  ```
141
180
 
142
- `observer` paths are runtime strings — for compiler-checked access, see [typed state](#typed-state--schema-validation).
181
+ 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.
143
182
 
144
183
  React apps get a dedicated hook, `useObserver` — see [React integration](#react-integration).
145
184
 
146
185
  ## The layers
147
186
 
148
- ### `store` — persistent key/value
187
+ ### `store` — persistent key/value data
149
188
 
150
189
  ```ts
151
190
  store.set('preferences', { theme: 'dark' })
152
191
  const preferences = store.get('preferences')
153
192
  ```
154
193
 
155
- Backed by `localStorage` in the browser (`store.isPersistent === true`); falls back to memory elsewhere.
194
+ Backed by `localStorage` in the browser. Falls back to memory in environments without persistent storage — check `store.isPersistent` when portability matters.
156
195
 
157
- ### `session` — follows the tab
196
+ ### `session` — data for the current session
158
197
 
159
198
  ```ts
160
- session.set('token', 'user-abc-123')
199
+ session.set('wizard-step', 3)
200
+ const step = session.get('wizard-step')
161
201
  ```
162
202
 
163
- Backed by `sessionStorage`. Good for auth state, wizards, tab-scoped data.
203
+ Backed by `sessionStorage`. Good for multi-step workflows, temporary preferences, tab-scoped data.
164
204
 
165
- ### `cache` — volatile, fast
205
+ ### `cache` — volatile runtime data
166
206
 
167
207
  ```ts
168
208
  cache.set('expensive-result', computeExpensiveResult())
@@ -170,18 +210,19 @@ cache.set('expensive-result', computeExpensiveResult())
170
210
 
171
211
  Disappears when the runtime disappears. No persistence guarantee, ever.
172
212
 
173
- ### `idb` — durable, structured
213
+ ### `idb` — durable structured browser data
174
214
 
175
215
  ```ts
176
216
  await idb.db.create('app')
177
217
  await idb.table.create('app', 'users')
178
218
  await idb.data.set('app', 'users', { id: 1, name: 'Sara' })
219
+
179
220
  const user = await idb.data.get('app', 'users', 1)
180
221
  ```
181
222
 
182
- Check before relying on it in portable code: `memorio.getCapabilities()`.
223
+ Check runtime support before depending on it: `memorio.getCapabilities()`.
183
224
 
184
- ### `sqlite` — a real local SQL engine
225
+ ### `sqlite` — local SQL
185
226
 
186
227
  ```ts
187
228
  await sqlite.ready
@@ -195,17 +236,17 @@ await sqlite.query.run('app', `INSERT INTO users (name, role) VALUES (?, ?)`, ['
195
236
  const admins = await sqlite.query.select('app', `SELECT * FROM users WHERE role = ?`, ['admin'])
196
237
  ```
197
238
 
198
- Runs **in memory by default**, powered by `sql.js` (lazy-loaded — see [loading strategies](#cross-platform-support)). Enable persistence explicitly when you need it:
239
+ Runs **in memory by default**, using `sql.js`. Persistence is explicit:
199
240
 
200
241
  ```ts
201
242
  await sqlite.db.create('app', { persistence: true })
202
243
  ```
203
244
 
204
- > ⚠️ 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).
245
+ > ⚠️ **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.
205
246
 
206
- ### `memory` — semantic application memory
247
+ ### `memory` — application memory
207
248
 
208
- The layer that makes memorio more than a state manager. Structured memory with type, confidence, TTL, tags, source, scope, and a real lifecycle:
249
+ 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.
209
250
 
210
251
  ```ts
211
252
  await memorio.memory.remember('user.language', 'Italian', {
@@ -219,14 +260,14 @@ await memorio.memory.remember('user.language', 'Italian', {
219
260
  const language = await memorio.memory.recall('user.language')
220
261
  ```
221
262
 
222
- Updates don't overwrite — they **supersede**, preserving history:
263
+ Updates don't overwrite — they supersede, preserving history:
223
264
 
224
265
  ```ts
225
266
  await memorio.memory.update('user.language', 'English', { confidence: 0.95 })
226
- // old entry → status: 'superseded' | new entry → status: 'active'
267
+ // previous entry → status: 'superseded' | new entry → status: 'active'
227
268
  ```
228
269
 
229
- Retrieve what's *relevant*, not everything:
270
+ Retrieve what's relevant to the current operation, not everything:
230
271
 
231
272
  ```ts
232
273
  const context = await memorio.memory.context({
@@ -237,13 +278,86 @@ const context = await memorio.memory.context({
237
278
  })
238
279
  ```
239
280
 
240
- > ℹ️ **"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.
281
+ The context system considers tags, type, confidence, and recency.
282
+
283
+ > ℹ️ **"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).
284
+
285
+ 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.
286
+
287
+ ## Redux and other state managers
288
+
289
+ memorio does not need to replace your existing state manager. Its Redux integration ships as a **separate, optional entry point**:
290
+
291
+ ```ts
292
+ import { createMemorioReduxBridge } from 'memorio/redux'
293
+ ```
294
+
295
+ 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.
296
+
297
+ > **Ownership rule:** your state manager owns application state. memorio owns memory.
298
+
299
+ The bridge does not mirror the entire store — only explicitly mapped values are persisted:
300
+
301
+ ```ts
302
+ const memorioRedux = createMemorioReduxBridge<AppState>({
303
+ mappings: {
304
+ 'user.preferences': {
305
+ selector: state => state.user.preferences,
306
+ type: 'preference',
307
+ scope: 'local',
308
+ tags: ['app', 'user'],
309
+ },
310
+ 'user.name': state => state.user.name,
311
+ },
312
+ whitelist: ['user/preferencesChanged', 'user/nameChanged'],
313
+ debug: true,
314
+ })
315
+
316
+ const store = createStore(reducer, applyMiddleware(memorioRedux.middleware))
317
+ ```
318
+
319
+ Hydration is explicit and one-shot:
320
+
321
+ ```ts
322
+ await memorioRedux.hydrate(store, {
323
+ onHydration(values) {
324
+ store.dispatch(restorePreferences(values))
325
+ },
326
+ })
327
+ ```
328
+
329
+ Direction of ownership — there is intentionally no permanent two-way mirror:
330
+
331
+ ```text
332
+ Redux ─────────────→ memorio memorio ────────────→ Redux
333
+ application state durable memory bootstrap hydration
334
+ ```
335
+
336
+ - only mapped selectors are persisted — the entire Redux tree is never mirrored
337
+ - hydration is one-shot; restore dispatches do not create synchronization loops
338
+ - persistence happens outside the reducer path; bursts of actions are coalesced
339
+ - memorio failures do not break Redux dispatch — errors can be reported through `onError`
340
+ - the adapter does not expose the memorio database through `globalThis` or DevTools
341
+
342
+ ### memorio vs Redux, at a glance
241
343
 
242
- `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.
344
+ Not a replacement — a different scope. Useful mainly for deciding which one (or both) fits a given piece of state.
345
+
346
+ | | Redux | memorio |
347
+ |---|---|---|
348
+ | Core model | single store, plain object | multiple independent layers (`state`, `store`, `session`, `cache`, `idb`, `sqlite`, `memory`) |
349
+ | State changes | via dispatched actions + reducers | direct mutation on a reactive proxy |
350
+ | Persistence | not built in (needs `redux-persist` or similar) | built into `store`/`session`/`idb`/`sqlite` |
351
+ | Structured "memory" (confidence, TTL, source, supersession) | not a concept in Redux | native, via the `memory` layer |
352
+ | Offline sync / conflict resolution | not built in | built into `memory.configure()` (operations + journal + HLC ordering) |
353
+ | Time-travel debugging, strict middleware pipelines | yes, mature ecosystem | not a goal — see [When to use something else](#when-to-use-something-else) |
354
+ | Dependencies | small core, large plugin ecosystem | zero production dependencies |
355
+
356
+ 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.
243
357
 
244
358
  ## React integration
245
359
 
246
- React is an integration, not a requirement — `useObserver` is a thin bridge onto the same `observer` mechanism from above:
360
+ React is an integration, not a requirement — `useObserver` connects to the same observer system described above.
247
361
 
248
362
  ```tsx
249
363
  function Counter() {
@@ -253,9 +367,11 @@ function Counter() {
253
367
  }
254
368
  ```
255
369
 
370
+ The runtime itself remains independent of React.
371
+
256
372
  ## Typed state & schema validation
257
373
 
258
- TypeScript types for compile-time safety:
374
+ TypeScript types give compile-time guarantees without creating another state container:
259
375
 
260
376
  ```ts
261
377
  interface AppState {
@@ -265,12 +381,12 @@ interface AppState {
265
381
 
266
382
  const app = memorio.typed<AppState>()
267
383
  app.theme = 'dark'
268
- app.theme = 'purple' // ❌ TypeScript error
384
+ app.theme = 'purple' // TypeScript error
269
385
  ```
270
386
 
271
- `app === state` — same Proxy, no duplicated store.
387
+ `app === state` — same proxy, no duplicated store.
272
388
 
273
- Runtime validation for values crossing trust boundaries:
389
+ Runtime validation protects values crossing trust boundaries:
274
390
 
275
391
  ```ts
276
392
  memorio.registerSchema('user', {
@@ -282,41 +398,40 @@ memorio.registerSchema('user', {
282
398
  },
283
399
  })
284
400
 
285
- state.user = { name: 'Sara' } // rejected — missing required "email"
401
+ state.user = { name: 'Sara' } // rejected: missing required "email"
286
402
  ```
287
403
 
288
- Array item validation is supported per-element with the `items` schema field:
404
+ Arrays validate each element via `items`:
289
405
 
290
406
  ```ts
291
- memorio.registerSchema('tags', {
292
- type: 'array',
293
- items: { type: 'string' },
294
- })
407
+ memorio.registerSchema('tags', { type: 'array', items: { type: 'string' } })
295
408
 
296
409
  state.tags = ['a', 'b'] // accepted
297
- state.tags = [1, 2, 3] // rejected — items must be strings
410
+ state.tags = [1, 2, 3] // rejected
298
411
  ```
299
412
 
300
413
  ## Local-first sync
301
414
 
302
- The local application owns its data; the cloud is optional transport.
303
-
304
- memorio syncs **operations** (`remember`, `update`, `forget`, `expire`, `confirm`, `supersede`), not database dumps, via a local journal that survives network failure:
415
+ 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.
305
416
 
306
417
  ```ts
307
418
  memorio.memory.configure({
308
419
  namespace: 'user:123:device:abc',
309
420
  provider: {
310
- push: (ops) => fetch('/api/sync', { method: 'POST', body: JSON.stringify(ops) }),
311
- pull: (since) => fetch(`/api/sync?since=${since}`).then(r => r.json()),
421
+ push: ops => fetch('/api/sync', { method: 'POST', body: JSON.stringify(ops) }),
422
+ pull: since => fetch(`/api/sync?since=${since}`).then(r => r.json()),
312
423
  },
313
424
  auto: true,
314
425
  })
315
426
  ```
316
427
 
317
- When `replay()` pulls remote operations, entries are ordered causally by HLC timestamp. For
318
- *concurrent* writes to the same key (an HLC tie) the **remote entry wins by default**; provide a
319
- `resolveConflict` to override:
428
+ ```text
429
+ local application → memorio memory → local journal → offline / cloud
430
+ ```
431
+
432
+ The network is an extension of the local application, not its prerequisite.
433
+
434
+ 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:
320
435
 
321
436
  ```ts
322
437
  memorio.memory.configure({
@@ -327,66 +442,92 @@ memorio.memory.configure({
327
442
  })
328
443
  ```
329
444
 
330
- When no resolver is supplied, Memorio logs a warn (dev only) and applies the remote entry on a
331
- tie. The resolver only settles *client-side* divergence between what memorio has seen locally —
332
- the provider is still responsible for the final server-side policy.
445
+ 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.
333
446
 
334
447
  ## Cross-platform support
335
448
 
336
449
  | API | Browser | Node.js | Deno | Edge / Workers |
337
- |---|---:|---:|---:|---:|
450
+ |---|:---:|:---:|:---:|:---:|
338
451
  | `state` / `observer` / `cache` | ✅ | ✅ | ✅ | ✅ |
339
452
  | `store` / `session` | persistent | memory fallback | memory fallback | capability-dependent |
340
453
  | `idb` | ✅ | ❌ | ❌ | capability-dependent |
341
454
  | `sqlite` | ✅ | ❌ | ❌ | capability-dependent |
342
- | `devtools` | ✅ (dev-only) | ❌ | ❌ | capability-dependent |
455
+ | `devtools` | dev-only | ❌ | ❌ | capability-dependent |
343
456
 
344
457
  ```ts
345
458
  memorio.getCapabilities()
346
- memorio.isBrowser() / isNode() / isDeno() / isEdge()
459
+ memorio.isBrowser() / memorio.isNode() / memorio.isDeno() / memorio.isEdge()
347
460
  ```
348
461
 
462
+ Do not assume every persistence backend exists in every runtime.
463
+
349
464
  ## Security
350
465
 
351
- - Zero production dependencies — [verified by Socket.dev](https://socket.dev/npm/package/memorio) and [scanned by Snyk](https://snyk.io/test/npm/memorio)
466
+ - Zero production dependencies — [checked by Socket.dev](https://socket.dev/npm/package/memorio) and [scanned by Snyk](https://snyk.io/test/npm/memorio)
352
467
  - No `eval`, no dynamic code execution, no bundled telemetry
353
468
  - Sanitized keys, validated inputs, caught module-boundary errors
354
- - UUID-based session identifiers, bounded journal entries
469
+ - Bounded journal entries, UUID-based session identifiers
470
+
471
+ > ⚠️ **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.
355
472
 
356
- **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.
473
+ **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.
357
474
 
358
- **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.
475
+ 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.
359
476
 
360
477
  ## Honest limitations
361
478
 
362
- We'd rather tell you where the edges are than have you find them in production.
479
+ memorio deliberately documents its edges.
363
480
 
364
- - **`memory.context()` is rule-based, not embedding-based.** No free-text semantic similarity yet — see [roadmap](#when-to-use-something-else).
365
- - **SQLite persistence is a full serialize-on-flush**, not incremental. Costly for large datasets if triggered on every write.
366
- - **Namespaces and contexts are organizational, not authorization boundaries.**
367
- - **Nothing is encrypted by default**, anywhere.
368
- - **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.
481
+ - **Semantic memory is structured, not vector-based.** `memory.context()` uses structured ranking, not embedding similarity search.
482
+ - **SQLite persistence is not incremental** — the current strategy serializes the complete database on flush; large datasets need deliberate batching.
483
+ - **Contexts and namespaces are not authorization** — they organize application data, they don't replace authentication or backend authorization.
484
+ - **Data is not encrypted automatically** — memorio doesn't pretend local persistence is secure storage.
485
+ - **DevTools are runtime-controlled, not build-removed** — production builds should still configure `NODE_ENV` correctly.
369
486
 
370
487
  ## When to use something else
371
488
 
372
- - **Need strict Redux-style architecture** (action pipelines, middleware, time-travel debugging) → use a dedicated Redux-style setup.
373
- - **Need hard security isolation** → real backend authorization, process isolation, encryption. Don't lean on memorio contexts.
374
- - **Need durable server storage** → Node/edge runtimes don't gain browser persistence for free. Use a server database.
375
- - **Need true semantic/embedding retrieval** → pair `memorio.memory` with a dedicated embedding store; use memorio for the lifecycle metadata on top.
489
+ memorio is intentionally not everything.
490
+
491
+ - **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.
492
+ - **Need a server database** (authoritative persistence, multi-user authorization, server-side transactions, backend-controlled access)? Use one — memorio doesn't provide it.
493
+ - **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.
376
494
 
377
495
  ## Design philosophy
378
496
 
379
- 1. **Local first** — the app stays useful when the network disappears.
380
- 2. **Persistence is incremental** — start with memory, persist only when useful.
381
- 3. **The cloud is optional** — an extension, never a prerequisite.
382
- 4. **Choose the right primitive** — don't put relational data in a key/value store, or semantic memory in ordinary state.
383
- 5. **Memory has meaning** — confidence, source, lifetime, scope, type, history.
384
- 6. **Tell the truth about boundaries** — volatile, unencrypted, not-a-security-boundary: say so, everywhere it applies.
385
- 7. **Keep the common case tiny**:
497
+ 1. **Local first** — the application stays useful when the network disappears.
498
+ 2. **Persistence is optional** — start in memory, persist only when it provides value.
499
+ 3. **The cloud is optional** — remote sync extends the local application; it does not define it.
500
+ 4. **Use the right primitive** — don't put relational data in a key/value store, or semantic memory in ordinary state.
501
+ 5. **Memory has meaning** — confidence, source, type, scope, TTL, tags, history, status. That's different from simply storing a value.
502
+ 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.
503
+ 7. **Tell the truth about boundaries** — local data is not automatically secure, persistence is not authorization, structured memory is not automatically AI semantic search.
504
+ 8. **Keep the common case tiny:**
386
505
  ```ts
387
506
  import 'memorio'
388
507
  state.value = 42
389
508
  ```
509
+ Everything else is there when you need it.
510
+
511
+ ## A mental model
512
+
513
+ ```text
514
+ memorio
515
+ │
516
+ ┌──────────────┼──────────────┐
517
+ │ │ │
518
+ runtime persistence memory
519
+ │ │ │
520
+ ┌─────┼─────┐ ┌────┼────┐ structured
521
+ │ │ │ │ │ │ knowledge
522
+ state cache session store idb sqlite
523
+ │
524
+ observer
525
+ │
526
+ ▼
527
+ application → journal → optional sync → cloud
528
+ ```
529
+
530
+ The important part is not that memorio has many layers — it's that **you do not have to use them all**.
390
531
 
391
532
  ## License
392
533
 
package/examples/basic.ts CHANGED
@@ -81,7 +81,7 @@ console.debug('Username:', store.get('username'))
81
81
  console.debug('Preferences:', store.get('preferences'))
82
82
 
83
83
  // Get storage size
84
- console.debug('Storage size:', store.size(), 'bytes')
84
+ console.debug('Storage size:', store.size(), 'kilobytes')
85
85
 
86
86
  // ============================================
87
87
  // SESSION - Temporary session storage