memorio 4.7.3 โ†’ 4.9.5

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
@@ -1,466 +1,377 @@
1
1
  # ๐Ÿง  memorio
2
2
 
3
- ![banner](https://raw.githubusercontent.com/passariello/container/refs/heads/main/memorio/banner.svg)
4
-
5
- [![npm version](https://img.shields.io/npm/v/memorio.svg)](https://npmjs.com/package/memorio)
6
- [![npm downloads](https://img.shields.io/npm/dm/memorio.svg)](https://npmjs.com/package/memorio)
7
- [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18-gray?logo=node.js)](https://nodejs.org)
8
- ![Browser](https://img.shields.io/badge/Browser-Chrome%20/%20Firefox%20/%20Safari-gray?logo=google-chrome)
9
- ![Deno](https://img.shields.io/badge/Deno-compatible-gray?logo=deno)
10
- ![Edge Workers](https://img.shields.io/badge/Edge%20Workers-compatible-gray)
11
- ![TypeScript](https://img.shields.io/badge/TypeScript-native-gray?logo=typescript)
12
- ![React](https://img.shields.io/badge/React-compatible-gray?logo=react)
13
- ![Tests](https://img.shields.io/badge/tests-101%20passed-green)
14
- ![License](https://img.shields.io/badge/License-MIT-gray)
15
-
16
- ### One import. Global state, persistence, and a IndexedDB layer - done.
17
-
18
- ```javascript
3
+ **Local-first memory for JavaScript.**
4
+ One import. Global state, local persistence, SQLite, semantic memory, optional sync.
5
+
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)
12
+
13
+ ```ts
19
14
  import 'memorio'
20
15
 
21
- state.user = { name: 'Sara' } // reactive, everywhere, instantly
16
+ state.user = { name: 'Sara', role: 'admin' }
17
+ state.counter++
22
18
  ```
23
19
 
24
- No provider tree. No reducers. No actions to dispatch before you can change a number. If you've ever set up a global store for an app that just needed *"a value, shared, that updates the UI when it changes,"* memorio is the toolkit for that thirty-second job - and it keeps growing with you: session storage, a cache, typed IndexedDB tables, an observer system, a React hook, devtools, and a change logger, all sharing one mental model.
20
+ No provider tree. No reducers. No actions. No boilerplate.
21
+ Just data that exists where your application needs it โ€” and grows with it.
25
22
 
26
23
  ---
27
24
 
28
- ## Table of Contents
29
-
30
- 1. [Is this for you?](#is-this-for-you)
31
- 2. [Installation](#installation)
32
- 3. [Quick Start](#quick-start)
33
- 4. [API Reference](#api-reference)
34
- 5. [Typed Stores](#typed-stores)
35
- 6. [Schema Validation](#schema-validation)
36
- 7. [Cross-Platform Behavior](#cross-platform-behavior)
37
- 8. [Context Isolation (multi-tenant)](#context-isolation-multi-tenant)
38
- 9. [Security](#security)
39
- 10. [License](#license)
25
+ ## Table of contents
26
+
27
+ - [Why memorio](#why-memorio)
28
+ - [Which layer should I use](#which-layer-should-i-use)
29
+ - [Install](#install)
30
+ - [Quick start](#quick-start)
31
+ - [Observing changes](#observing-changes)
32
+ - [The layers](#the-layers) โ€” state ยท store ยท session ยท cache ยท idb ยท sqlite ยท memory
33
+ - [React integration](#react-integration)
34
+ - [Typed state & schema validation](#typed-state--schema-validation)
35
+ - [Local-first sync](#local-first-sync)
36
+ - [Cross-platform support](#cross-platform-support)
37
+ - [Security](#security)
38
+ - [Honest limitations](#honest-limitations)
39
+ - [When to use something else](#when-to-use-something-else)
40
+ - [Design philosophy](#design-philosophy)
41
+ - [License](#license)
40
42
 
41
43
  ---
42
44
 
43
- ## Is this for you?
44
-
45
- memorio is built for speed of thought: you think "I need this value everywhere," you write one line, it works. That's the entire pitch - and it's a real one, not a rewritten complaint about how much Redux boilerplate you've had to write.
46
-
47
- **Reach for memorio when:**
48
- - You want state that's simply *there* - no store setup, no provider wrapping your app.
49
- - You want `localStorage`, `sessionStorage`, and `IndexedDB` behind one consistent API instead of learning three.
50
- - You're prototyping, building an internal tool, or shipping something small-to-medium where iteration speed matters more than architectural ceremony.
51
-
52
- **Reach for something else when:**
53
- - You need every state path statically type-checked against every observer - memorio's `state.foo.bar` and `observer('state.foo.bar', ...)` are connected by a string, not by the compiler, so a rename won't be caught for you. However, if you want compiler-checked state access, use `memorio.typed<T>()` (see [Typed Stores](#typed-stores)) which gives you full static types on the global `state` proxy. For runtime validation as well, combine with `memorio.registerSchema()` (see [Schema Validation](#schema-validation)).
54
- - You need Redux-style middleware, action logs, or time-travel debugging as a hard requirement for a large team - those tools exist because that discipline solves real problems at scale, not because their authors enjoy boilerplate. Memorio logs through `memorio.logger` and inspects via `memorio.devtools`, but doesn't have Redux DevTools time-travel.
55
- - Your isolation requirements are a security boundary, not a convenience - see [Context Isolation](#context-isolation-multi-tenant) before you rely on it for that.
56
-
57
- Pick the right tool. memorio is at its best when the answer to "how much state architecture do I need here?" is genuinely "not much."
58
-
59
- ---
45
+ ## Why memorio
46
+
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.
48
+
49
+ memorio gives these concerns **one runtime and one mental model** โ€” without forcing you to use all of it.
50
+
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 |
61
+
62
+ Start with `state`. Add the rest only when your app actually needs it.
63
+
64
+ ## Which layer should I use
65
+
66
+ ```text
67
+ Does the UI need to react automatically to changes?
68
+ โ”‚
69
+ โ”œโ”€ Yes โ†’ state, or a memory-backed reactive slice
70
+ โ”‚
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)
77
+ โ”‚
78
+ โ”œโ”€ Need relations, joins, SQL? โ†’ sqlite
79
+ โ””โ”€ Is this "knowledge" the app reasons
80
+ about (confidence, source, expiry)? โ†’ memory
81
+ ```
60
82
 
61
- ## Installation
83
+ ## Install
62
84
 
63
85
  ```bash
64
86
  npm i memorio
65
- # pnpm add memorio
66
- # yarn add memorio
67
-
68
- # Optional - only if you use the React hook
69
- npm i react react-dom
70
87
  ```
71
88
 
72
- ---
89
+ Optional peers, only loaded when used:
73
90
 
74
- ## Quick Start
75
-
76
- ### Global style - the whole point of memorio
77
-
78
- ```typescript
79
- import 'memorio' // once, at your entry point
80
-
81
- state.user = { name: 'Sara', role: 'admin' }
82
- state.counter++
83
-
84
- useObserver(
85
- () => console.debug('user changed:', state.user),
86
- [state.user]
87
- )
88
- ```
89
-
90
- ### Named imports - same instances, explicit about it
91
-
92
- ```typescript
93
- import { state, store, session, cache, idb, observer, useObserver, dispatch, memorio } from 'memorio'
94
-
95
- state.user = { name: 'Sara' }
96
- store.set('theme', 'dark')
97
- ```
98
-
99
- ```tsx
100
- function Counter() {
101
- const [, forceUpdate] = useReducer(x => x + 1, 0)
102
- useObserver(forceUpdate, [state.counter])
103
- return <div>Count: {state.counter}</div>
104
- }
91
+ ```bash
92
+ npm i react react-dom # React integration
93
+ npm i sql.js # SQLite engine
105
94
  ```
106
95
 
107
- Two styles, one engine underneath - use whichever reads better in your codebase.
96
+ **Zero production dependencies.** See [Security](#security).
108
97
 
109
- ---
110
-
111
- ## API Reference
98
+ ## Quick start
112
99
 
113
- ### `state` - reactive, volatile, Proxy-based
100
+ ```ts
101
+ import 'memorio'
114
102
 
115
- ```javascript
116
103
  state.user = { name: 'Sara', role: 'admin' }
117
104
  const name = state.user.name
105
+ ```
118
106
 
119
- state.list // ['user', 'items', ...]
120
- state.remove('items')
121
- state.removeAll()
107
+ Reactive, in-memory, Proxy-based, globally accessible. Lock a slice you don't want mutated by accident:
122
108
 
123
- // Freeze a slice of state when you need to stop guessing who mutated it
109
+ ```ts
124
110
  state.config = { maxUsers: 100 }
125
111
  state.config.lock()
126
- state.config.maxUsers = 200 // throws: state 'config' is locked
112
+ state.config.maxUsers = 200 // throws
127
113
  state.config.unlock()
128
114
  ```
129
115
 
130
- ### `store` - the value that survives a refresh
116
+ Prefer explicit imports over the global? Same runtime either way:
131
117
 
132
- ```javascript
133
- store.set('preferences', { theme: 'dark' })
134
- store.get('preferences') // { theme: 'dark' } or null
135
- store.isPersistent // true when backed by real localStorage
118
+ ```ts
119
+ import { state, store, session, cache, idb, sqlite, memorio } from 'memorio'
136
120
  ```
137
121
 
138
- ### `session` - lives as long as the tab does
122
+ ## Observing changes
139
123
 
140
- ```javascript
141
- session.set('token', 'user-abc-123')
142
- session.get('token')
143
- ```
124
+ Reactivity isn't a React add-on โ€” it's built into the runtime. Watch any state path directly, in any environment:
144
125
 
145
- ### `cache` - the fastest thing you own, gone on refresh
126
+ ```ts
127
+ observer('state.user', (next, previous) => {
128
+ console.log('user changed:', next, previous)
129
+ })
146
130
 
147
- ```javascript
148
- cache.set('temp', computeExpensiveResult())
149
- cache.get('temp')
131
+ // nested paths work too
132
+ observer('state.user.name', callback)
150
133
  ```
151
134
 
152
- ### `idb` - typed, async, structured tables without the ceremony
135
+ `dispatch` is the event mechanism underneath it, if you need to hook in lower-level:
153
136
 
154
- ```javascript
155
- await idb.db.create('my-db')
156
- await idb.table.create('my-db', 'users')
157
- await idb.data.set('my-db', 'users', { id: 1, name: 'Sara' })
158
- const user = await idb.data.get('my-db', 'users', 1)
137
+ ```ts
138
+ memorio.dispatch.listen('state.user', event => console.debug(event.detail))
139
+ memorio.dispatch.set('state.user', { detail: { name: 'Sara' } })
159
140
  ```
160
141
 
161
- > IndexedDB is a browser-only primitive - see [Cross-Platform Behavior](#cross-platform-behavior) for what happens off the browser.
142
+ `observer` paths are runtime strings โ€” for compiler-checked access, see [typed state](#typed-state--schema-validation).
162
143
 
163
- ### `observer` / `useObserver` - watch a path, react to it
144
+ React apps get a dedicated hook, `useObserver` โ€” see [React integration](#react-integration).
164
145
 
165
- ```javascript
166
- observer('state.user', (newVal, oldVal) => {
167
- console.debug('user changed:', newVal, oldVal)
168
- })
169
- ```
170
-
171
- > Keys here are plain strings, not statically checked against `state`'s shape. Keep the observer near the code that shapes that state, and grep before you rename.
146
+ ## The layers
172
147
 
173
- ### `dispatch` - the event bus underneath it all
148
+ ### `store` โ€” persistent key/value
174
149
 
175
- ```javascript
176
- memorio.dispatch.listen('state.user', (event) => console.debug(event.detail))
177
- memorio.dispatch.set('state.user', { detail: { name: 'Sara' } })
150
+ ```ts
151
+ store.set('preferences', { theme: 'dark' })
152
+ const preferences = store.get('preferences')
178
153
  ```
179
154
 
180
- ### `devtools` - see everything, instantly
155
+ Backed by `localStorage` in the browser (`store.isPersistent === true`); falls back to memory elsewhere.
156
+
157
+ ### `session` โ€” follows the tab
181
158
 
182
- ```javascript
183
- memorio.devtools.inspect()
184
- memorio.devtools.stats()
185
- memorio.devtools.exportData()
159
+ ```ts
160
+ session.set('token', 'user-abc-123')
186
161
  ```
187
162
 
188
- Pairs with [dphelper-manager](https://chrome.google.com/webstore/detail/dphelper-manager-dev-tool/oppppldaoknfddeikfloonnialijngbk) for visual, time-travel inspection of your global state straight from devtools.
163
+ Backed by `sessionStorage`. Good for auth state, wizards, tab-scoped data.
189
164
 
190
- ### `logger` - a black box for every change
165
+ ### `cache` โ€” volatile, fast
191
166
 
192
- ```javascript
193
- memorio.logger.configure({ enabled: true, logToConsole: true })
194
- memorio.logger.getHistory()
167
+ ```ts
168
+ cache.set('expensive-result', computeExpensiveResult())
195
169
  ```
196
170
 
197
- > It logs *everything* written to `state`/`store`/`session` - including anything sensitive you put there. Great for debugging, but don't leave it on unconditionally in production if secrets or PII pass through your state. See [Security](#security).
171
+ Disappears when the runtime disappears. No persistence guarantee, ever.
198
172
 
199
- ---
173
+ ### `idb` โ€” durable, structured
200
174
 
201
- ## Typed Stores
175
+ ```ts
176
+ await idb.db.create('app')
177
+ await idb.table.create('app', 'users')
178
+ await idb.data.set('app', 'users', { id: 1, name: 'Sara' })
179
+ const user = await idb.data.get('app', 'users', 1)
180
+ ```
202
181
 
203
- `memorio.typed<T>()` returns the global `state` proxy cast to a TypeScript type `T`, giving you **compile-time** type safety on every access and mutation. The returned object is the *exact same* Proxy as `globalThis.state` โ€” no duplication, no overhead.
182
+ Check before relying on it in portable code: `memorio.getCapabilities()`.
204
183
 
205
- ```typescript
206
- import 'memorio'
207
-
208
- interface AppState {
209
- user: { name: string; age: number; email: string }
210
- theme: 'light' | 'dark'
211
- items: string[]
212
- }
184
+ ### `sqlite` โ€” a real local SQL engine
213
185
 
214
- const app = memorio.typed<AppState>()
186
+ ```ts
187
+ await sqlite.ready
188
+ await sqlite.db.create('app')
215
189
 
216
- // Type-checked at compile time:
217
- app.user = { name: 'Sara', age: 30, email: 'sara@test.com' }
218
- app.theme = 'dark'
190
+ await sqlite.query.run('app', `
191
+ CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, role TEXT)
192
+ `)
193
+ await sqlite.query.run('app', `INSERT INTO users (name, role) VALUES (?, ?)`, ['Sara', 'admin'])
219
194
 
220
- // TypeScript errors:
221
- // app.user = { name: 42 } // age missing, name wrong type
222
- // app.theme = 'purple' // not a valid literal
195
+ const admins = await sqlite.query.select('app', `SELECT * FROM users WHERE role = ?`, ['admin'])
223
196
  ```
224
197
 
225
- The same data is accessible via the global `state` proxy โ€” `app` and `state` are identical instances:
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:
226
199
 
227
- ```typescript
228
- state.user = { name: 'Sara', age: 30, email: 'sara@test.com' }
229
- app.user.name // 'Sara' โ€” same Proxy
200
+ ```ts
201
+ await sqlite.db.create('app', { persistence: true })
230
202
  ```
231
203
 
232
- For runtime safety (rejecting invalid values even when TypeScript isn't checking), combine with [Schema Validation](#schema-validation) below.
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).
233
205
 
234
- See [Typed Stores docs](markdown/TYPED.md) for full examples.
206
+ ### `memory` โ€” semantic application memory
235
207
 
236
- ---
237
-
238
- ## Schema Validation
208
+ The layer that makes memorio more than a state manager. Structured memory with type, confidence, TTL, tags, source, scope, and a real lifecycle:
239
209
 
240
- `memorio.registerSchema(path, schema)` lets you register runtime validators for state paths. When a write to `state.somePath = value` violates a registered schema, the write is **rejected** before the value is stored.
210
+ ```ts
211
+ await memorio.memory.remember('user.language', 'Italian', {
212
+ type: 'preference',
213
+ confidence: 0.92,
214
+ scope: 'local',
215
+ tags: ['user', 'ui'],
216
+ source: 'conversation',
217
+ })
241
218
 
242
- ```typescript
243
- import 'memorio'
219
+ const language = await memorio.memory.recall('user.language')
220
+ ```
244
221
 
245
- memorio.registerSchema('user', {
246
- type: 'object',
247
- required: ['name', 'email'],
248
- properties: {
249
- name: { type: 'string', min: 1 },
250
- email: { type: 'string', pattern: /^[^@]+@[^@]+$/ },
251
- age: { type: 'number', min: 0, max: 150 }
252
- }
253
- })
222
+ Updates don't overwrite โ€” they **supersede**, preserving history:
254
223
 
255
- state.user = { name: 'Sara', email: 'sara@test.com', age: 30 } // accepted
256
- state.user = { name: 'Sara' } // rejected: missing 'email'
257
- state.user = { name: 42 } // rejected: wrong type
224
+ ```ts
225
+ await memorio.memory.update('user.language', 'English', { confidence: 0.95 })
226
+ // old entry โ†’ status: 'superseded' | new entry โ†’ status: 'active'
258
227
  ```
259
228
 
260
- Custom validator functions are also supported:
229
+ Retrieve what's *relevant*, not everything:
261
230
 
262
- ```typescript
263
- memorio.registerSchema('theme', (val) => {
264
- return val === 'light' || val === 'dark'
265
- ? true
266
- : 'theme must be "light" or "dark"'
231
+ ```ts
232
+ const context = await memorio.memory.context({
233
+ tags: 'user',
234
+ types: ['preference', 'decision'],
235
+ minConfidence: 0.7,
236
+ maxEntries: 10,
267
237
  })
268
-
269
- state.theme = 'purple' // rejected: "theme must be light or dark"
270
- state.theme = 'dark' // accepted
271
238
  ```
272
239
 
273
- Manual validation without writing:
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.
274
241
 
275
- ```typescript
276
- memorio.validate('user', { name: 'Sara', email: 'sara@test.com' })
277
- // { valid: true }
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.
278
243
 
279
- memorio.validate('user', { name: 'Sara' })
280
- // { valid: false, errors: ["user: missing required property 'email'"] }
281
- ```
244
+ ## React integration
282
245
 
283
- Manage registered schemas:
246
+ React is an integration, not a requirement โ€” `useObserver` is a thin bridge onto the same `observer` mechanism from above:
284
247
 
285
- ```typescript
286
- memorio.listSchemas() // ['user', 'theme']
287
- memorio.unregisterSchema('theme') // removes the validator
248
+ ```tsx
249
+ function Counter() {
250
+ const [, forceUpdate] = useReducer(x => x + 1, 0)
251
+ useObserver(forceUpdate, [state.counter])
252
+ return <div>{state.counter}</div>
253
+ }
288
254
  ```
289
255
 
290
- **Typed + Schema** โ€” combine both for full safety:
256
+ ## Typed state & schema validation
257
+
258
+ TypeScript types for compile-time safety:
291
259
 
292
- ```typescript
260
+ ```ts
293
261
  interface AppState {
294
- user: { name: string; email: string }
262
+ user: { name: string; age: number; email: string }
295
263
  theme: 'light' | 'dark'
296
264
  }
297
265
 
298
266
  const app = memorio.typed<AppState>()
267
+ app.theme = 'dark'
268
+ app.theme = 'purple' // โŒ TypeScript error
269
+ ```
270
+
271
+ `app === state` โ€” same Proxy, no duplicated store.
299
272
 
273
+ Runtime validation for values crossing trust boundaries:
274
+
275
+ ```ts
300
276
  memorio.registerSchema('user', {
301
277
  type: 'object',
302
278
  required: ['name', 'email'],
303
279
  properties: {
304
280
  name: { type: 'string', min: 1 },
305
- email: { type: 'string', pattern: /^[^@]+@[^@]+$/ }
306
- }
281
+ email: { type: 'string', pattern: /^[^@]+@[^@]+$/ },
282
+ },
307
283
  })
308
284
 
309
- app.user = { names: 'Sara' } // โŒ TS: wrong shape
310
- app.user = { name: '', email: '' } // โŒ TS passes, โŒ runtime: name too short
311
- app.user = { name: 'Sara', email: 'sara@test.com' } // โœ… both pass
285
+ state.user = { name: 'Sara' } // rejected โ€” missing required "email"
312
286
  ```
313
287
 
314
- See [Schema Validation docs](markdown/SCHEMA.md) for the full schema definition reference.
315
-
316
- ---
317
-
318
- ## Memory System
288
+ ## Local-first sync
319
289
 
320
- `memorio.memory` provides a semantic memory layer โ€” a key/value store with **type safety**, **TTL**, **confidence scoring**, **tagging**, and **scope-based persistence**. It's designed for AI agents and applications that need structured, queryable memory with lifecycle management.
290
+ The local application owns its data; the cloud is optional transport.
321
291
 
322
- ### Core API
292
+ memorio syncs **operations** (`remember`, `update`, `forget`, `expire`, `confirm`, `supersede`), not database dumps, via a local journal that survives network failure:
323
293
 
324
294
  ```ts
325
- // remember(key, value, options)
326
- await memorio.memory.remember('user.language', 'Italian', {
327
- type: 'preference',
328
- confidence: 0.92,
329
- scope: 'local',
330
- ttl: null, // never expires
331
- tags: ['ui', 'user'],
332
- source: 'conversation'
295
+ memorio.memory.configure({
296
+ namespace: 'user:123:device:abc',
297
+ provider: {
298
+ push: (ops) => fetch('/api/sync', { method: 'POST', body: JSON.stringify(ops) }),
299
+ pull: (since) => fetch(`/api/sync?since=${since}`).then(r => r.json()),
300
+ },
301
+ auto: true,
333
302
  })
334
-
335
- // recall(key, options)
336
- const lang = await memorio.memory.recall('user.language')
337
- // โ†’ 'Italian'
338
-
339
- // update(key, value, options) โ€” creates a superseded version of the old entry
340
- await memorio.memory.update('user.language', 'English', { confidence: 0.95 })
341
-
342
- // forget(key) โ€” permanently deletes
343
- await memorio.memory.forget('user.language')
344
303
  ```
345
304
 
346
- ### Scopes
347
-
348
- | Scope | Storage | TTL | Cross-session | Size limit |
349
- |---|---|---|---|---|
350
- | `hot` | `state` proxy | โœ… | โŒ | ~5MB (RAM) |
351
- | `session` | `sessionStorage` | โœ… | Tab only | ~5MB |
352
- | `local` | `localStorage` | โœ… | โœ… | ~10MB |
353
- | `durable` | `IndexedDB` | โœ… | โœ… | ~1GB+ |
354
-
355
- Default scope is `local` for values โ‰ค100KB, `durable` for larger values.
356
-
357
- ### Context API
305
+ Conflict resolution defaults to *higher confidence wins, then more recent `lastConfirmedAt`* โ€” override it when you need a different rule:
358
306
 
359
307
  ```ts
360
- // context() โ€” returns ranked, relevant memories
361
- const context = await memorio.memory.context({
362
- tags: 'user',
363
- types: ['preference', 'decision'],
364
- minConfidence: 0.7,
365
- maxEntries: 10
308
+ memorio.memory.configure({
309
+ resolveConflict(local, remote) {
310
+ if (remote.source === 'user-correction') return remote
311
+ return local.confidence >= remote.confidence ? local : remote
312
+ },
366
313
  })
367
- // โ†’ [{ key, value, relevance, age, confidence, ... }]
368
314
  ```
369
315
 
370
- ### Memory Entry Model
316
+ This resolver only settles *client-side* divergence between what memorio has seen locally โ€” the provider is still responsible for the final server-side policy.
371
317
 
372
- Each memory is stored as a structured entry:
373
-
374
- ```ts
375
- {
376
- id: string // unique identifier
377
- key: string // path-like key (e.g. "user.preference.theme")
378
- value: any // the stored data
379
- type: MemoryType // 'fact' | 'preference' | 'decision' | 'task' | 'context'
380
- confidence: number // 0.0โ€“1.0
381
- scope: MemoryScope // 'hot' | 'session' | 'local' | 'durable'
382
- ttl?: number | null // milliseconds (null = never expires)
383
- tags: string[] // for filtering
384
- source?: string // where this memory came from
385
- status: MemoryStatus // 'active' | 'obsolete' | 'superseded'
386
- createdAt: number // timestamp
387
- lastConfirmedAt: number
388
- supersededId?: string | null
389
- }
390
- ```
391
-
392
- ### Memory Lifecycle
393
-
394
- | Operation | Behavior |
395
- |---|---|
396
- | `remember(key, newValue)` when entry exists | Old entry โ†’ `superseded` status, new entry โ†’ `active` |
397
- | `recall(key)` | Returns active value; expired entries return `null` unless `includeObsolete: true` |
398
- | `update(key, value)` | Creates superseded copy + updated active entry |
399
- | Entry with `ttl` expires | Status โ†’ `obsolete` (via `forgetExpired()`) |
400
- | `clear()` | Wipes all memories + index |
401
-
402
- See [Memory docs](markdown/MEMORY.md) for full reference.
403
-
404
- ---
405
-
406
- ## Cross-Platform Behavior
407
-
408
- memorio runs everywhere JavaScript does - but "everywhere" means different guarantees in different places, and we'd rather tell you now than have you find out at 2am:
318
+ ## Cross-platform support
409
319
 
410
320
  | API | Browser | Node.js | Deno | Edge / Workers |
411
- |---|---|---|---|---|
412
- | `state` | Proxy in memory | Proxy in memory | Proxy in memory | Proxy in memory |
413
- | `observer` / `useObserver` | โœ… | โœ… | โœ… | โœ… |
414
- | `cache` | โœ… in memory | โœ… in memory | โœ… in memory | โœ… in memory |
415
- | `store` | `localStorage` | `Map` fallback - **not durable across restarts** | `Map` fallback - not durable | `localStorage` where available, else `Map` |
416
- | `session` | `sessionStorage` | `Map` fallback - not durable | `Map` fallback - not durable | `sessionStorage` where available, else `Map` |
417
- | `idb` | โœ… `IndexedDB` | โŒ not available | โŒ not available | โš ๏ธ check `getCapabilities()` |
418
- | `devtools` | โœ… | โŒ | โŒ | โš ๏ธ |
419
-
420
- Same API top to bottom - that's the promise. But if your server code leans on `store.get(...)` surviving a redeploy, know that on Node/Deno it won't; the fallback is an in-memory cache with the same shape, not durable storage.
421
-
422
- ```javascript
423
- memorio.isBrowser()
424
- memorio.isNode()
425
- memorio.isDeno()
426
- memorio.isEdge()
321
+ |---|---:|---:|---:|---:|
322
+ | `state` / `observer` / `cache` | โœ… | โœ… | โœ… | โœ… |
323
+ | `store` / `session` | persistent | memory fallback | memory fallback | capability-dependent |
324
+ | `idb` | โœ… | โŒ | โŒ | capability-dependent |
325
+ | `sqlite` | โœ… | โŒ | โŒ | capability-dependent |
326
+ | `devtools` | โœ… (dev-only) | โŒ | โŒ | capability-dependent |
427
327
 
328
+ ```ts
428
329
  memorio.getCapabilities()
429
- // { platform: 'browser', hasLocalStorage: true, hasIndexedDB: true, ... }
330
+ memorio.isBrowser() / isNode() / isDeno() / isEdge()
430
331
  ```
431
332
 
432
- Check `getCapabilities()` before leaning on `idb` or persistent `store`/`session` in code that might run on more than one platform.
333
+ ## Security
433
334
 
434
- ---
335
+ - Zero production dependencies โ€” [verified by Socket.dev](https://socket.dev/npm/package/memorio) and [scanned by Snyk](https://snyk.io/test/npm/memorio)
336
+ - No `eval`, no dynamic code execution, no bundled telemetry
337
+ - Sanitized keys, validated inputs, caught module-boundary errors
338
+ - UUID-based session identifiers, bounded journal entries
435
339
 
436
- ## Context Isolation (multi-tenant)
340
+ **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.
437
341
 
438
- ```javascript
439
- const ctx = memorio.createContext('tenant-123')
342
+ **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.
440
343
 
441
- ctx.state.user = { name: 'Isolated' }
442
- ctx.store.set('settings', { theme: 'dark' })
344
+ ## Honest limitations
443
345
 
444
- console.debug(state.user) // undefined - separate namespace
445
- ```
346
+ We'd rather tell you where the edges are than have you find them in production.
446
347
 
447
- Handy for keeping per-tenant or per-request state from colliding in the same process. Under the hood it's a key prefix (`tenant-123-key`) inside the same storage - a naming convention, not a hard memory boundary. Great for a browser tab; if you're running this in a shared Node.js process or an edge isolate that might be reused across requests, make sure tenant IDs can't collide or be forged, and don't treat this as your only isolation layer if the stakes are real.
348
+ - **`memory.context()` is rule-based, not embedding-based.** No free-text semantic similarity yet โ€” see [roadmap](#when-to-use-something-else).
349
+ - **SQLite persistence is a full serialize-on-flush**, not incremental. Costly for large datasets if triggered on every write.
350
+ - **Namespaces and contexts are organizational, not authorization boundaries.**
351
+ - **Nothing is encrypted by default**, anywhere.
352
+ - **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.
448
353
 
449
- ---
354
+ ## When to use something else
450
355
 
451
- ## Security
452
-
453
- - Zero production dependencies.
454
- - No `eval`, no dynamic code execution, no bundled telemetry.
455
- - Session IDs generated via `crypto.randomUUID`.
456
- - Inputs validated, keys sanitized, errors caught at module boundaries.
356
+ - **Need strict Redux-style architecture** (action pipelines, middleware, time-travel debugging) โ†’ use a dedicated Redux-style setup.
357
+ - **Need hard security isolation** โ†’ real backend authorization, process isolation, encryption. Don't lean on memorio contexts.
358
+ - **Need durable server storage** โ†’ Node/edge runtimes don't gain browser persistence for free. Use a server database.
359
+ - **Need true semantic/embedding retrieval** โ†’ pair `memorio.memory` with a dedicated embedding store; use memorio for the lifecycle metadata on top.
457
360
 
458
- memorio does **not** encrypt what you put into `store`, `session`, or `idb` - they're thin, fast wrappers over `localStorage`/`sessionStorage`/`IndexedDB`, which store data in the clear on the user's device. Bring your own encryption layer for tokens, secrets, or regulated personal data.
361
+ ## Design philosophy
459
362
 
460
- We build with recognized engineering guidance (NIST SP 800-53 practices) as an input to how we write code - that's a design discipline, not a compliance certificate, and no third-party audit has been performed. Found an issue? Report it privately - see `SECURITY.md` - rather than opening a public issue.
461
-
462
- ---
363
+ 1. **Local first** โ€” the app stays useful when the network disappears.
364
+ 2. **Persistence is incremental** โ€” start with memory, persist only when useful.
365
+ 3. **The cloud is optional** โ€” an extension, never a prerequisite.
366
+ 4. **Choose the right primitive** โ€” don't put relational data in a key/value store, or semantic memory in ordinary state.
367
+ 5. **Memory has meaning** โ€” confidence, source, lifetime, scope, type, history.
368
+ 6. **Tell the truth about boundaries** โ€” volatile, unencrypted, not-a-security-boundary: say so, everywhere it applies.
369
+ 7. **Keep the common case tiny**:
370
+ ```ts
371
+ import 'memorio'
372
+ state.value = 42
373
+ ```
463
374
 
464
375
  ## License
465
376
 
466
- MIT ยฉ [Dario Passariello](https://dario.passariello.ca)
377
+ MIT ยฉ Dario Passariello