memorio 4.9.35 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +3 -3
- package/README.md +95 -516
- package/SECURITY.md +159 -33
- package/SUMMARY.md +59 -45
- package/adr/001-state-proxy-model.md +95 -0
- package/adr/002-observer-semantics.md +179 -0
- package/adr/003-deep-mutation-semantics.md +128 -0
- package/adr/004-array-mutation-semantics.md +127 -0
- package/adr/005-scheduler-contract.md +148 -0
- package/adr/006-context-isolation.md +91 -0
- package/adr/007-mutation-records.md +117 -0
- package/adr/008-transactions.md +105 -0
- package/adr/009-history-model.md +109 -0
- package/adr/README.md +46 -0
- package/adr/template.md +48 -0
- package/bin/cli.js +68 -0
- package/examples/basic.ts +115 -115
- package/examples/browser-vanilla.html +358 -358
- package/examples/cache.ts +72 -72
- package/examples/cross-platform-guards.ts +57 -57
- package/examples/history.ts +104 -0
- package/examples/idb.ts +109 -109
- package/examples/multi-tenant-context.ts +44 -44
- package/examples/node-server.ts +308 -308
- package/examples/observer.ts +60 -60
- package/examples/platform.ts +115 -115
- package/examples/react-app.tsx +362 -362
- package/examples/react-observer.tsx +63 -63
- package/examples/semantic-memory.ts +60 -60
- package/examples/session-advanced.ts +91 -91
- package/examples/sqlite-batched-writes.ts +57 -57
- package/examples/state-advanced.ts +89 -89
- package/examples/store-advanced.ts +117 -117
- package/examples/sync.ts +90 -0
- package/examples/typed-and-schema.ts +102 -100
- package/examples/useObserver.tsx +140 -141
- package/global.cjs +5733 -0
- package/global.d.ts +8 -0
- package/global.js +5667 -0
- package/index.cjs +2202 -1041
- package/index.d.ts +2 -0
- package/index.js +2178 -1040
- package/llms.txt +113 -8
- package/markdown/AUDIT-REPORT.md +134 -0
- package/markdown/CACHE.md +191 -0
- package/markdown/DEVTOOLS.md +128 -0
- package/markdown/DISPATCH.md +176 -0
- package/markdown/HISTORY.md +198 -0
- package/markdown/IDB.md +177 -0
- package/markdown/IMPORT.md +152 -0
- package/markdown/INSPECT.md +122 -0
- package/markdown/LOGGER.md +153 -0
- package/markdown/MEMORY-ATTACHMENT.md +95 -0
- package/markdown/MEMORY.md +161 -0
- package/markdown/OBSERVER.md +208 -0
- package/markdown/PLATFORM.md +277 -0
- package/markdown/REDUX.md +54 -0
- package/markdown/SCHEMA.md +175 -0
- package/markdown/SESSION.md +164 -0
- package/markdown/SQLITE.md +189 -0
- package/markdown/STATE.md +159 -0
- package/markdown/STORE.md +170 -0
- package/markdown/SYNC.md +318 -0
- package/markdown/TYPED.md +164 -0
- package/markdown/USEOBSERVER.md +256 -0
- package/modules/redux.cjs +701 -177
- package/modules/redux.cjs.map +1 -1
- package/modules/redux.js +701 -177
- package/modules/redux.js.map +1 -1
- package/package.json +26 -4
- package/types/broadcast.d.ts +61 -0
- package/types/computed.d.ts +96 -0
- package/types/encryption.d.ts +129 -0
- package/types/env.d.ts +19 -9
- package/types/exports.d.ts +29 -0
- package/types/history.d.ts +13 -1
- package/types/memorio.d.ts +25 -6
- package/types/mutation.d.ts +75 -0
- package/types/security.d.ts +67 -0
- package/types/session.d.ts +23 -5
- package/types/store.d.ts +19 -3
- package/vsix/memorio.vsix +0 -0
package/README.md
CHANGED
|
@@ -1,115 +1,39 @@
|
|
|
1
1
|
# 🧠 memorio
|
|
2
2
|
|
|
3
|
-
**
|
|
4
|
-
One import. Global state, local persistence, SQLite, semantic memory, optional sync.
|
|
5
|
-
|
|
6
|
-
[](https://www.npmjs.com/package/memorio)
|
|
7
|
-
[](https://www.npmjs.com/package/memorio)
|
|
8
|
-
[](https://socket.dev/npm/package/memorio)
|
|
9
|
-
[](https://snyk.io/test/npm/memorio)
|
|
10
|
-
[](#security)
|
|
11
|
-
[](#license)
|
|
3
|
+
**The memory layer for AI agents and apps — owned by the user, not the vendor.**
|
|
12
4
|
|
|
13
5
|
```ts
|
|
14
|
-
import 'memorio'
|
|
6
|
+
import 'memorio/global'
|
|
15
7
|
|
|
16
8
|
state.user = { name: 'Sara', role: 'admin' }
|
|
17
9
|
state.counter++
|
|
10
|
+
|
|
11
|
+
console.log(state.user.name)
|
|
12
|
+
console.log(state.counter)
|
|
18
13
|
```
|
|
19
14
|
|
|
20
15
|
No provider tree. No reducers. No actions. No boilerplate.
|
|
21
16
|
Just data, available where your application needs it.
|
|
22
17
|
|
|
23
|
-
**And if you don't want a global API, don't use one:**
|
|
24
|
-
|
|
25
|
-
```ts
|
|
26
|
-
import { state, store, session, cache, idb, sqlite, memorio } from 'memorio'
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
The global API is a **choice**, not an architectural requirement.
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## Table of contents
|
|
34
|
-
|
|
35
|
-
- [Why memorio](#why-memorio)
|
|
36
|
-
- [Global is optional](#global-is-optional)
|
|
37
|
-
- [Before / After](#before--after)
|
|
38
|
-
- [Which layer should I use](#which-layer-should-i-use)
|
|
39
|
-
- [Install](#install)
|
|
40
|
-
- [Quick start](#quick-start)
|
|
41
|
-
- [Observing changes](#observing-changes)
|
|
42
|
-
- [The layers](#the-layers) — store · session · cache · idb · sqlite · memory
|
|
43
|
-
- [Redux and other state managers](#redux-and-other-state-managers)
|
|
44
|
-
- [memorio vs Redux, at a glance](#memorio-vs-redux-at-a-glance)
|
|
45
|
-
- [React integration](#react-integration)
|
|
46
|
-
- [Typed state & schema validation](#typed-state--schema-validation)
|
|
47
|
-
- [Local-first sync](#local-first-sync)
|
|
48
|
-
- [Cross-platform support](#cross-platform-support)
|
|
49
|
-
- [Security](#security)
|
|
50
|
-
- [Honest limitations](#honest-limitations)
|
|
51
|
-
- [When to use something else](#when-to-use-something-else)
|
|
52
|
-
- [Design philosophy](#design-philosophy)
|
|
53
|
-
- [A mental model](#a-mental-model)
|
|
54
|
-
- [Recipes](#recipes)
|
|
55
|
-
- [License](#license)
|
|
56
|
-
|
|
57
18
|
---
|
|
58
19
|
|
|
59
20
|
## Why memorio
|
|
60
21
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
memorio brings these concerns into **one runtime and one consistent model**, while keeping each layer independent.
|
|
22
|
+
Most AI-powered apps (and most apps in general) eventually need more than "just state": reactive values, persistent data, session data, caches, structured browser storage, local SQL, application memory, history, and optional sync.
|
|
64
23
|
|
|
65
|
-
|
|
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
|
|
24
|
+
Usually that means a different library — and a different mental model — for each. Memorio gives them one consistent runtime, without making the simple case complicated:
|
|
79
25
|
|
|
80
26
|
```ts
|
|
81
|
-
|
|
82
|
-
state.user = { name: 'Sara' }
|
|
27
|
+
state.value = 42
|
|
83
28
|
```
|
|
84
29
|
|
|
85
|
-
|
|
30
|
+
But the reason memorio exists isn't just "fewer dependencies." It's this: **the memory an AI agent builds about a user shouldn't be locked inside a vendor's server, opaque, and impossible to inspect or move.** Memorio's memory layer is structured, inspectable, editable, and — when you turn on encryption — readable only with a key the vendor doesn't hold. It runs client-side, in the browser, on the edge, or in Node.
|
|
86
31
|
|
|
87
|
-
|
|
88
|
-
import { state } from 'memorio'
|
|
89
|
-
state.user = { name: 'Sara' }
|
|
90
|
-
```
|
|
32
|
+
That said, this covers *structured* memory - preferences, decisions, facts with confidence and provenance - not semantic similarity search. If your agent needs "find things like this conversation," pair memorio with a dedicated vector store; see [`markdown/MEMORY.md`](./markdown/MEMORY.md).
|
|
91
33
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
## Which layer should I use
|
|
97
|
-
|
|
98
|
-
```text
|
|
99
|
-
Does the UI need to react automatically to changes?
|
|
100
|
-
│
|
|
101
|
-
├─ Yes → state
|
|
102
|
-
│
|
|
103
|
-
└─ No
|
|
104
|
-
│
|
|
105
|
-
├─ Only while the runtime exists? → cache
|
|
106
|
-
├─ Only for this browser tab/session? → session
|
|
107
|
-
├─ Small persistent key/value data? → store
|
|
108
|
-
├─ Larger or structured browser data? → idb
|
|
109
|
-
├─ Relations, joins, or SQL? → sqlite
|
|
110
|
-
└─ Knowledge the application should
|
|
111
|
-
remember (confidence, source, expiry)? → memory
|
|
112
|
-
```
|
|
34
|
+
Add persistence, memory, history, or sync only when your application actually needs them. The common case stays tiny; the runtime grows with you.
|
|
35
|
+
|
|
36
|
+
---
|
|
113
37
|
|
|
114
38
|
## Install
|
|
115
39
|
|
|
@@ -117,466 +41,121 @@ Does the UI need to react automatically to changes?
|
|
|
117
41
|
npm install memorio
|
|
118
42
|
```
|
|
119
43
|
|
|
120
|
-
Optional
|
|
121
|
-
|
|
122
|
-
```bash
|
|
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')
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
Same result, one reactive line instead of two effects and manual JSON serialization.
|
|
152
|
-
|
|
153
|
-
## Quick start
|
|
154
|
-
|
|
155
|
-
```ts
|
|
156
|
-
import 'memorio'
|
|
44
|
+
Zero production dependencies in core. Optional integrations (`react`, `sql.js`, etc.) are installed only when you use them.
|
|
157
45
|
|
|
158
|
-
|
|
159
|
-
state.counter++
|
|
160
|
-
|
|
161
|
-
console.log(state.user.name)
|
|
162
|
-
console.log(state.counter)
|
|
163
|
-
```
|
|
46
|
+
---
|
|
164
47
|
|
|
165
|
-
|
|
48
|
+
## Start here
|
|
166
49
|
|
|
167
|
-
|
|
50
|
+
Three things cover most apps - reactive state, persistence, and observation:
|
|
168
51
|
|
|
169
52
|
```ts
|
|
170
|
-
state
|
|
171
|
-
state.config.lock()
|
|
172
|
-
|
|
173
|
-
state.config.maxUsers = 200 // throws
|
|
174
|
-
|
|
175
|
-
state.config.unlock()
|
|
176
|
-
state.config.maxUsers = 200 // ok
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
Prefer explicit imports over the global? Same runtime either way:
|
|
53
|
+
import { state, persist, observer } from 'memorio'
|
|
180
54
|
|
|
181
|
-
|
|
182
|
-
import { state, store, session, cache, idb, sqlite, memorio } from 'memorio'
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
## Observing changes
|
|
55
|
+
state.user = { name: 'Sara' }
|
|
186
56
|
|
|
187
|
-
|
|
57
|
+
persist('state.user')
|
|
188
58
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
console.log('user changed:', next, previous)
|
|
59
|
+
observer('state.user', user => {
|
|
60
|
+
console.log(user)
|
|
192
61
|
})
|
|
193
|
-
|
|
194
|
-
// nested paths work too
|
|
195
|
-
observer('state.user.name', callback)
|
|
196
62
|
```
|
|
197
63
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
```ts
|
|
201
|
-
const off = memorio.dispatch.listen('state.user', event => console.debug(event.detail))
|
|
202
|
-
memorio.dispatch.set('state.user', { detail: { name: 'Sara' } })
|
|
203
|
-
|
|
204
|
-
off() // remove only this subscription
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
Each subscription owns its own unsubscribe function. Prefer the returned `off()` over `memorio.dispatch.remove('state.user')`, which removes **all** listeners registered under that name.
|
|
208
|
-
|
|
209
|
-
React apps get a dedicated hook, `useObserver` — see [React integration](#react-integration).
|
|
210
|
-
|
|
211
|
-
## The layers
|
|
212
|
-
|
|
213
|
-
### `store` — persistent key/value data
|
|
64
|
+
And this is what makes memorio a *memory* layer, not just a state manager - structured, inspectable facts about a user or agent, not just ephemeral UI state:
|
|
214
65
|
|
|
215
66
|
```ts
|
|
216
|
-
|
|
217
|
-
const preferences = store.get('preferences')
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
Backed by `localStorage` in the browser. Falls back to memory in environments without persistent storage — check `store.isPersistent` when portability matters.
|
|
221
|
-
|
|
222
|
-
### `session` — data for the current session
|
|
223
|
-
|
|
224
|
-
```ts
|
|
225
|
-
session.set('wizard-step', 3)
|
|
226
|
-
const step = session.get('wizard-step')
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
Backed by `sessionStorage`. Good for multi-step workflows, temporary preferences, tab-scoped data.
|
|
230
|
-
|
|
231
|
-
### `cache` — volatile runtime data
|
|
232
|
-
|
|
233
|
-
```ts
|
|
234
|
-
cache.set('expensive-result', computeExpensiveResult())
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
Disappears when the runtime disappears. No persistence guarantee, ever.
|
|
67
|
+
import { memorio } from 'memorio'
|
|
238
68
|
|
|
239
|
-
### `idb` — durable structured browser data
|
|
240
|
-
|
|
241
|
-
```ts
|
|
242
|
-
await idb.db.create('app')
|
|
243
|
-
await idb.table.create('app', 'users')
|
|
244
|
-
await idb.data.set('app', 'users', { id: 1, name: 'Sara' })
|
|
245
|
-
|
|
246
|
-
const user = await idb.data.get('app', 'users', 1)
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
Check runtime support before depending on it: `memorio.getCapabilities()`.
|
|
250
|
-
|
|
251
|
-
### `sqlite` — local SQL
|
|
252
|
-
|
|
253
|
-
```ts
|
|
254
|
-
await sqlite.ready
|
|
255
|
-
await sqlite.db.create('app')
|
|
256
|
-
|
|
257
|
-
await sqlite.query.run('app', `
|
|
258
|
-
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, role TEXT)
|
|
259
|
-
`)
|
|
260
|
-
await sqlite.query.run('app', `INSERT INTO users (name, role) VALUES (?, ?)`, ['Sara', 'admin'])
|
|
261
|
-
|
|
262
|
-
const admins = await sqlite.query.select('app', `SELECT * FROM users WHERE role = ?`, ['admin'])
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
Runs **in memory by default**, using `sql.js`. Persistence is explicit:
|
|
266
|
-
|
|
267
|
-
```ts
|
|
268
|
-
await sqlite.db.create('app', { persistence: true })
|
|
269
|
-
```
|
|
270
|
-
|
|
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.
|
|
272
|
-
|
|
273
|
-
### `memory` — application memory
|
|
274
|
-
|
|
275
|
-
The layer that makes memorio more than a state manager. `memory` represents information the application wants to **remember**, not just store — it can carry type, confidence, source, scope, tags, lifetime, status, and history.
|
|
276
|
-
|
|
277
|
-
```ts
|
|
278
69
|
await memorio.memory.remember('user.language', 'Italian', {
|
|
279
70
|
type: 'preference',
|
|
280
71
|
confidence: 0.92,
|
|
281
|
-
|
|
282
|
-
tags: ['user', 'ui'],
|
|
283
|
-
source: 'conversation',
|
|
72
|
+
source: 'conversation'
|
|
284
73
|
})
|
|
285
74
|
|
|
286
75
|
const language = await memorio.memory.recall('user.language')
|
|
76
|
+
// { value: 'Italian', confidence: 0.92, source: 'conversation', ... }
|
|
287
77
|
```
|
|
288
78
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
```ts
|
|
292
|
-
await memorio.memory.update('user.language', 'English', { confidence: 0.95 })
|
|
293
|
-
// previous entry → status: 'superseded' | new entry → status: 'active'
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
Retrieve what's relevant to the current operation, not everything:
|
|
79
|
+
For most apps, that's enough. Everything below is an optional capability you can add when your application needs it - each one documented in its own reference file.
|
|
297
80
|
|
|
298
|
-
|
|
299
|
-
const context = await memorio.memory.context({
|
|
300
|
-
tags: 'user',
|
|
301
|
-
types: ['preference', 'decision'],
|
|
302
|
-
minConfidence: 0.7,
|
|
303
|
-
maxEntries: 10,
|
|
304
|
-
})
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
The context system considers tags, type, confidence, and recency.
|
|
308
|
-
|
|
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.
|
|
383
|
-
|
|
384
|
-
## React integration
|
|
385
|
-
|
|
386
|
-
React is an integration, not a requirement — `useObserver` connects to the same observer system described above.
|
|
387
|
-
|
|
388
|
-
```tsx
|
|
389
|
-
function Counter() {
|
|
390
|
-
const [, forceUpdate] = useReducer(x => x + 1, 0)
|
|
391
|
-
useObserver(forceUpdate, [state.counter])
|
|
392
|
-
return <div>{state.counter}</div>
|
|
393
|
-
}
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
The runtime itself remains independent of React.
|
|
397
|
-
|
|
398
|
-
## Typed state & schema validation
|
|
399
|
-
|
|
400
|
-
TypeScript types give compile-time guarantees without creating another state container:
|
|
401
|
-
|
|
402
|
-
```ts
|
|
403
|
-
interface AppState {
|
|
404
|
-
user: { name: string; age: number; email: string }
|
|
405
|
-
theme: 'light' | 'dark'
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
const app = memorio.typed<AppState>()
|
|
409
|
-
app.theme = 'dark'
|
|
410
|
-
app.theme = 'purple' // TypeScript error
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
`app === state` — same proxy, no duplicated store.
|
|
414
|
-
|
|
415
|
-
Runtime validation protects values crossing trust boundaries:
|
|
416
|
-
|
|
417
|
-
```ts
|
|
418
|
-
memorio.registerSchema('user', {
|
|
419
|
-
type: 'object',
|
|
420
|
-
required: ['name', 'email'],
|
|
421
|
-
properties: {
|
|
422
|
-
name: { type: 'string', min: 1 },
|
|
423
|
-
email: { type: 'string', pattern: /^[^@]+@[^@]+$/ },
|
|
424
|
-
},
|
|
425
|
-
})
|
|
426
|
-
|
|
427
|
-
state.user = { name: 'Sara' } // rejected: missing required "email"
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
Arrays validate each element via `items`:
|
|
431
|
-
|
|
432
|
-
```ts
|
|
433
|
-
memorio.registerSchema('tags', { type: 'array', items: { type: 'string' } })
|
|
434
|
-
|
|
435
|
-
state.tags = ['a', 'b'] // accepted
|
|
436
|
-
state.tags = [1, 2, 3] // rejected
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
## Local-first sync
|
|
440
|
-
|
|
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.
|
|
442
|
-
|
|
443
|
-
```ts
|
|
444
|
-
memorio.memory.configure({
|
|
445
|
-
namespace: 'user:123:device:abc',
|
|
446
|
-
provider: {
|
|
447
|
-
push: ops => fetch('/api/sync', { method: 'POST', body: JSON.stringify(ops) }),
|
|
448
|
-
pull: since => fetch(`/api/sync?since=${since}`).then(r => r.json()),
|
|
449
|
-
},
|
|
450
|
-
auto: true,
|
|
451
|
-
})
|
|
452
|
-
```
|
|
453
|
-
|
|
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:
|
|
461
|
-
|
|
462
|
-
```ts
|
|
463
|
-
memorio.memory.configure({
|
|
464
|
-
resolveConflict(local, remote) {
|
|
465
|
-
if (remote.source === 'user-correction') return remote
|
|
466
|
-
return local.confidence >= remote.confidence ? local : remote
|
|
467
|
-
},
|
|
468
|
-
})
|
|
469
|
-
```
|
|
470
|
-
|
|
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.
|
|
472
|
-
|
|
473
|
-
## Cross-platform support
|
|
474
|
-
|
|
475
|
-
| API | Browser | Node.js | Deno | Edge / Workers |
|
|
476
|
-
|---|:---:|:---:|:---:|:---:|
|
|
477
|
-
| `state` / `observer` / `cache` | ✅ | ✅ | ✅ | ✅ |
|
|
478
|
-
| `store` / `session` | persistent | memory fallback | memory fallback | capability-dependent |
|
|
479
|
-
| `idb` | ✅ | ❌ | ❌ | capability-dependent |
|
|
480
|
-
| `sqlite` | ✅ | ❌ | ❌ | capability-dependent |
|
|
481
|
-
| `devtools` | dev-only | ❌ | ❌ | capability-dependent |
|
|
482
|
-
|
|
483
|
-
```ts
|
|
484
|
-
memorio.getCapabilities()
|
|
485
|
-
memorio.isBrowser() / memorio.isNode() / memorio.isDeno() / memorio.isEdge()
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
Do not assume every persistence backend exists in every runtime.
|
|
489
|
-
|
|
490
|
-
## Security
|
|
491
|
-
|
|
492
|
-
- Zero production dependencies — [checked by Socket.dev](https://socket.dev/npm/package/memorio) and [scanned by Snyk](https://snyk.io/test/npm/memorio)
|
|
493
|
-
- No `eval`, no dynamic code execution, no bundled telemetry
|
|
494
|
-
- Sanitized keys, validated inputs, caught module-boundary errors
|
|
495
|
-
- Bounded journal entries, UUID-based session identifiers
|
|
496
|
-
|
|
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.
|
|
498
|
-
|
|
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.
|
|
502
|
-
|
|
503
|
-
## Honest limitations
|
|
81
|
+
---
|
|
504
82
|
|
|
505
|
-
|
|
83
|
+
## Where to go next
|
|
84
|
+
|
|
85
|
+
### Core
|
|
86
|
+
| Topic | Reference |
|
|
87
|
+
| --- | --- |
|
|
88
|
+
| Reactive state | [`markdown/STATE.md`](./markdown/STATE.md) |
|
|
89
|
+
| Persistent key/value store | [`markdown/STORE.md`](./markdown/STORE.md) |
|
|
90
|
+
| Observing changes | [`markdown/OBSERVER.md`](./markdown/OBSERVER.md) |
|
|
91
|
+
| Structured application memory | [`markdown/MEMORY.md`](./markdown/MEMORY.md) |
|
|
92
|
+
|
|
93
|
+
### Storage
|
|
94
|
+
| Topic | Reference |
|
|
95
|
+
| --- | --- |
|
|
96
|
+
| Session storage (tab-scoped) | [`markdown/SESSION.md`](./markdown/SESSION.md) |
|
|
97
|
+
| In-memory cache | [`markdown/CACHE.md`](./markdown/CACHE.md) |
|
|
98
|
+
| IndexedDB | [`markdown/IDB.md`](./markdown/IDB.md) |
|
|
99
|
+
| SQLite (`sql.js` / `bun:sqlite`) | [`markdown/SQLITE.md`](./markdown/SQLITE.md) |
|
|
100
|
+
|
|
101
|
+
### Sync & history
|
|
102
|
+
| Topic | Reference |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| Local-first sync & cloud | [`markdown/SYNC.md`](./markdown/SYNC.md) |
|
|
105
|
+
| Memory attachments (linking memory entries) | [`markdown/MEMORY-ATTACHMENT.md`](./markdown/MEMORY-ATTACHMENT.md) |
|
|
106
|
+
| History, undo/redo, snapshot, diff, trace | [`markdown/HISTORY.md`](./markdown/HISTORY.md) |
|
|
107
|
+
|
|
108
|
+
### Integrations
|
|
109
|
+
| Topic | Reference |
|
|
110
|
+
| --- | --- |
|
|
111
|
+
| React (`useObserver`) | [`markdown/USEOBSERVER.md`](./markdown/USEOBSERVER.md) |
|
|
112
|
+
| Redux bridge | [`markdown/REDUX.md`](./markdown/REDUX.md) |
|
|
113
|
+
| Typed state | [`markdown/TYPED.md`](./markdown/TYPED.md) |
|
|
114
|
+
| Schema validation | [`markdown/SCHEMA.md`](./markdown/SCHEMA.md) |
|
|
115
|
+
| Pub/sub events outside React | [`markdown/DISPATCH.md`](./markdown/DISPATCH.md) |
|
|
116
|
+
| Console logging middleware | [`markdown/LOGGER.md`](./markdown/LOGGER.md) |
|
|
117
|
+
|
|
118
|
+
### Ops & security
|
|
119
|
+
| Topic | Reference |
|
|
120
|
+
| --- | --- |
|
|
121
|
+
| Platform detection & multi-tenant context isolation | [`markdown/PLATFORM.md`](./markdown/PLATFORM.md) |
|
|
122
|
+
| Classic `import` vs `memorio/global` | [`markdown/IMPORT.md`](./markdown/IMPORT.md) |
|
|
123
|
+
| Runtime introspection | [`markdown/INSPECT.md`](./markdown/INSPECT.md) |
|
|
124
|
+
| Browser DevTools | [`markdown/DEVTOOLS.md`](./markdown/DEVTOOLS.md) |
|
|
125
|
+
| Security, encryption, threat model | [`SECURITY.md`](./SECURITY.md) |
|
|
126
|
+
| Internal self-assessment *(not a third-party audit)* | [`markdown/SELF-ASSESSMENT.md`](./markdown/SELF-ASSESSMENT.md) |
|
|
127
|
+
|
|
128
|
+
### Other
|
|
129
|
+
| Topic | Reference |
|
|
130
|
+
| --- | --- |
|
|
131
|
+
| Version history | [`CHANGELOG.md`](./CHANGELOG.md) |
|
|
132
|
+
| Working examples - each file has a `Run:` comment at the top (typically `npx ts-node examples/<name>.ts`, or `npx ts-node --esm examples/<name>.tsx` for React examples) | [`examples/`](./examples/) |
|
|
133
|
+
| Architecture decisions | [`adr/`](./adr/) |
|
|
506
134
|
|
|
507
|
-
|
|
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.
|
|
135
|
+
---
|
|
512
136
|
|
|
513
137
|
## When to use something else
|
|
514
138
|
|
|
515
|
-
|
|
139
|
+
- **A strict Redux-style architecture** - when you need action pipelines, extensive middleware, event-sourcing, or mature time-travel tooling. Memorio can integrate rather than replace - see [`markdown/REDUX.md`](./markdown/REDUX.md).
|
|
140
|
+
- **A server database** - for authoritative persistence, multi-user authorization, or server-side transactions.
|
|
141
|
+
- **A dedicated secrets manager** - for high-privilege credentials, encryption key management, or regulated data at rest. Memorio's encryption is client-side and key-management is your responsibility - see [`SECURITY.md`](./SECURITY.md).
|
|
142
|
+
- **A dedicated vector database** - for true embedding similarity search. Memorio manages structured memory (preferences, decisions, facts), not embeddings.
|
|
516
143
|
|
|
517
|
-
|
|
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.
|
|
144
|
+
---
|
|
520
145
|
|
|
521
146
|
## Design philosophy
|
|
522
147
|
|
|
523
|
-
1. **Local first**
|
|
524
|
-
2. **Persistence is optional**
|
|
525
|
-
3. **The cloud is optional**
|
|
526
|
-
4. **Use the right primitive**
|
|
527
|
-
5. **Memory has meaning**
|
|
528
|
-
6. **
|
|
529
|
-
7. **
|
|
530
|
-
8. **
|
|
531
|
-
|
|
532
|
-
import 'memorio'
|
|
533
|
-
state.value = 42
|
|
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
|
|
148
|
+
1. **Local first** - the app stays useful when the network disappears.
|
|
149
|
+
2. **Persistence is optional** - start in memory, persist only when it adds value.
|
|
150
|
+
3. **The cloud is optional** - sync extends the local app; it doesn't define it.
|
|
151
|
+
4. **Use the right primitive** - relational data belongs in SQL, not a kv store; memory isn't the same as ordinary state.
|
|
152
|
+
5. **Memory has meaning** - confidence, source, type, scope, TTL, tags, history, status: more than "just a value."
|
|
153
|
+
6. **Memory belongs to whoever it's about** - structured and inspectable by default, encryptable with a key the vendor doesn't have to hold.
|
|
154
|
+
7. **Global access is explicit** - opt in with `import 'memorio/global'`, or keep it explicit with named imports.
|
|
155
|
+
8. **Tell the truth about boundaries** - encryption is opt-in (not default), contexts aren't authorization, and there's no embedding search unless paired with one.
|
|
156
|
+
9. **Keep the common case tiny** - `state.value = 42` is a complete, valid program.
|
|
559
157
|
|
|
560
|
-
|
|
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.
|
|
158
|
+
---
|
|
580
159
|
|
|
581
160
|
## License
|
|
582
161
|
|