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