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