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 +242 -331
- package/SUMMARY.md +3 -0
- package/index.cjs +87 -147
- package/index.d.ts +1 -0
- package/index.js +46 -100
- package/llms.txt +1 -1
- package/package.json +9 -3
- package/types/env.d.ts +9 -0
- package/types/exports.d.ts +52 -1
- package/types/memorio.d.ts +43 -10
- package/types/memory.d.ts +95 -0
- package/types/schema.d.ts +53 -53
- package/types/sqlite.d.ts +35 -0
- package/markdown/CACHE.md +0 -90
- package/markdown/CHANGELOG.md +0 -161
- package/markdown/DEVTOOLS.md +0 -122
- package/markdown/DISPATCH.md +0 -168
- package/markdown/HISTORY.md +0 -192
- package/markdown/IDB.md +0 -169
- package/markdown/IMPORT.md +0 -139
- package/markdown/INSPECT.md +0 -116
- package/markdown/LOGGER.md +0 -147
- package/markdown/MEMORY.md +0 -155
- package/markdown/OBSERVER.md +0 -200
- package/markdown/PLATFORM.md +0 -265
- package/markdown/SCHEMA.md +0 -169
- package/markdown/SECURITY.md +0 -323
- package/markdown/SESSION.md +0 -154
- package/markdown/STATE.md +0 -153
- package/markdown/STORE.md +0 -164
- package/markdown/TYPED.md +0 -158
- package/markdown/USEOBSERVER.md +0 -259
package/README.md
CHANGED
|
@@ -1,466 +1,377 @@
|
|
|
1
1
|
# ๐ง memorio
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
[
|
|
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
|
+
[](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)
|
|
12
|
+
|
|
13
|
+
```ts
|
|
19
14
|
import 'memorio'
|
|
20
15
|
|
|
21
|
-
state.user = { name: 'Sara'
|
|
16
|
+
state.user = { name: 'Sara', role: 'admin' }
|
|
17
|
+
state.counter++
|
|
22
18
|
```
|
|
23
19
|
|
|
24
|
-
No provider tree. No reducers. No actions
|
|
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
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
##
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
**
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
96
|
+
**Zero production dependencies.** See [Security](#security).
|
|
108
97
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
## API Reference
|
|
98
|
+
## Quick start
|
|
112
99
|
|
|
113
|
-
|
|
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
|
-
|
|
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
|
-
|
|
109
|
+
```ts
|
|
124
110
|
state.config = { maxUsers: 100 }
|
|
125
111
|
state.config.lock()
|
|
126
|
-
state.config.maxUsers = 200 // throws
|
|
112
|
+
state.config.maxUsers = 200 // throws
|
|
127
113
|
state.config.unlock()
|
|
128
114
|
```
|
|
129
115
|
|
|
130
|
-
|
|
116
|
+
Prefer explicit imports over the global? Same runtime either way:
|
|
131
117
|
|
|
132
|
-
```
|
|
133
|
-
store
|
|
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
|
-
|
|
122
|
+
## Observing changes
|
|
139
123
|
|
|
140
|
-
|
|
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
|
-
|
|
126
|
+
```ts
|
|
127
|
+
observer('state.user', (next, previous) => {
|
|
128
|
+
console.log('user changed:', next, previous)
|
|
129
|
+
})
|
|
146
130
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
cache.get('temp')
|
|
131
|
+
// nested paths work too
|
|
132
|
+
observer('state.user.name', callback)
|
|
150
133
|
```
|
|
151
134
|
|
|
152
|
-
|
|
135
|
+
`dispatch` is the event mechanism underneath it, if you need to hook in lower-level:
|
|
153
136
|
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
142
|
+
`observer` paths are runtime strings โ for compiler-checked access, see [typed state](#typed-state--schema-validation).
|
|
162
143
|
|
|
163
|
-
|
|
144
|
+
React apps get a dedicated hook, `useObserver` โ see [React integration](#react-integration).
|
|
164
145
|
|
|
165
|
-
|
|
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
|
-
### `
|
|
148
|
+
### `store` โ persistent key/value
|
|
174
149
|
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
|
|
150
|
+
```ts
|
|
151
|
+
store.set('preferences', { theme: 'dark' })
|
|
152
|
+
const preferences = store.get('preferences')
|
|
178
153
|
```
|
|
179
154
|
|
|
180
|
-
|
|
155
|
+
Backed by `localStorage` in the browser (`store.isPersistent === true`); falls back to memory elsewhere.
|
|
156
|
+
|
|
157
|
+
### `session` โ follows the tab
|
|
181
158
|
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
memorio.devtools.stats()
|
|
185
|
-
memorio.devtools.exportData()
|
|
159
|
+
```ts
|
|
160
|
+
session.set('token', 'user-abc-123')
|
|
186
161
|
```
|
|
187
162
|
|
|
188
|
-
|
|
163
|
+
Backed by `sessionStorage`. Good for auth state, wizards, tab-scoped data.
|
|
189
164
|
|
|
190
|
-
### `
|
|
165
|
+
### `cache` โ volatile, fast
|
|
191
166
|
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
memorio.logger.getHistory()
|
|
167
|
+
```ts
|
|
168
|
+
cache.set('expensive-result', computeExpensiveResult())
|
|
195
169
|
```
|
|
196
170
|
|
|
197
|
-
|
|
171
|
+
Disappears when the runtime disappears. No persistence guarantee, ever.
|
|
198
172
|
|
|
199
|
-
|
|
173
|
+
### `idb` โ durable, structured
|
|
200
174
|
|
|
201
|
-
|
|
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
|
-
|
|
182
|
+
Check before relying on it in portable code: `memorio.getCapabilities()`.
|
|
204
183
|
|
|
205
|
-
|
|
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
|
-
|
|
186
|
+
```ts
|
|
187
|
+
await sqlite.ready
|
|
188
|
+
await sqlite.db.create('app')
|
|
215
189
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
228
|
-
|
|
229
|
-
app.user.name // 'Sara' โ same Proxy
|
|
200
|
+
```ts
|
|
201
|
+
await sqlite.db.create('app', { persistence: true })
|
|
230
202
|
```
|
|
231
203
|
|
|
232
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
243
|
-
|
|
219
|
+
const language = await memorio.memory.recall('user.language')
|
|
220
|
+
```
|
|
244
221
|
|
|
245
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
229
|
+
Retrieve what's *relevant*, not everything:
|
|
261
230
|
|
|
262
|
-
```
|
|
263
|
-
memorio.
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
280
|
-
// { valid: false, errors: ["user: missing required property 'email'"] }
|
|
281
|
-
```
|
|
244
|
+
## React integration
|
|
282
245
|
|
|
283
|
-
|
|
246
|
+
React is an integration, not a requirement โ `useObserver` is a thin bridge onto the same `observer` mechanism from above:
|
|
284
247
|
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
256
|
+
## Typed state & schema validation
|
|
257
|
+
|
|
258
|
+
TypeScript types for compile-time safety:
|
|
291
259
|
|
|
292
|
-
```
|
|
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
|
-
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
---
|
|
317
|
-
|
|
318
|
-
## Memory System
|
|
288
|
+
## Local-first sync
|
|
319
289
|
|
|
320
|
-
|
|
290
|
+
The local application owns its data; the cloud is optional transport.
|
|
321
291
|
|
|
322
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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`
|
|
413
|
-
| `
|
|
414
|
-
| `
|
|
415
|
-
| `
|
|
416
|
-
| `
|
|
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
|
-
|
|
330
|
+
memorio.isBrowser() / isNode() / isDeno() / isEdge()
|
|
430
331
|
```
|
|
431
332
|
|
|
432
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
442
|
-
ctx.store.set('settings', { theme: 'dark' })
|
|
344
|
+
## Honest limitations
|
|
443
345
|
|
|
444
|
-
|
|
445
|
-
```
|
|
346
|
+
We'd rather tell you where the edges are than have you find them in production.
|
|
446
347
|
|
|
447
|
-
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
-
|
|
454
|
-
-
|
|
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
|
-
|
|
361
|
+
## Design philosophy
|
|
459
362
|
|
|
460
|
-
|
|
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 ยฉ
|
|
377
|
+
MIT ยฉ Dario Passariello
|