memorio 4.7.1 → 4.8.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/README.md +447 -34
- package/SECURITY.md +10 -10
- package/SUMMARY.md +21 -0
- package/index.cjs +170 -31
- package/index.d.ts +4 -0
- package/index.js +125 -8
- package/llms.txt +119 -28
- package/markdown/CHANGELOG.md +8 -8
- package/markdown/HISTORY.md +192 -0
- package/markdown/INSPECT.md +116 -0
- package/markdown/MEMORY-ATTACHMENT.md +95 -0
- package/markdown/MEMORY.md +155 -0
- package/markdown/SCHEMA.md +169 -0
- package/markdown/SECURITY.md +1 -1
- package/markdown/SQLITE.md +181 -0
- package/markdown/SYNC.md +170 -0
- package/markdown/TYPED.md +158 -0
- package/package.json +15 -9
- package/types/exports.d.ts +27 -0
- package/types/history.d.ts +27 -0
- package/types/inspect.d.ts +14 -0
- package/types/memorio.d.ts +77 -2
- package/types/memory.d.ts +127 -0
- package/types/schema.d.ts +53 -0
- package/types/sqlite.d.ts +35 -0
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|

|
|
14
14
|

|
|
15
15
|
|
|
16
|
-
### One import. Global state, persistence, and a IndexedDB layer
|
|
16
|
+
### One import. Global state, persistence, and a IndexedDB layer - done.
|
|
17
17
|
|
|
18
18
|
```javascript
|
|
19
19
|
import 'memorio'
|
|
@@ -21,7 +21,7 @@ import 'memorio'
|
|
|
21
21
|
state.user = { name: 'Sara' } // reactive, everywhere, instantly
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
No provider tree. No reducers. No actions to dispatch before you can change a number. If you've ever set up a global store for an app that just needed *"a value, shared, that updates the UI when it changes,"* memorio is the toolkit for that thirty-second job
|
|
24
|
+
No provider tree. No reducers. No actions to dispatch before you can change a number. If you've ever set up a global store for an app that just needed *"a value, shared, that updates the UI when it changes,"* memorio is the toolkit for that thirty-second job - and it keeps growing with you: session storage, a cache, typed IndexedDB tables, an observer system, a React hook, devtools, and a change logger, all sharing one mental model.
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
@@ -31,26 +31,28 @@ No provider tree. No reducers. No actions to dispatch before you can change a nu
|
|
|
31
31
|
2. [Installation](#installation)
|
|
32
32
|
3. [Quick Start](#quick-start)
|
|
33
33
|
4. [API Reference](#api-reference)
|
|
34
|
-
5. [
|
|
35
|
-
6. [
|
|
36
|
-
7. [
|
|
37
|
-
8. [
|
|
34
|
+
5. [Typed Stores](#typed-stores)
|
|
35
|
+
6. [Schema Validation](#schema-validation)
|
|
36
|
+
7. [Cross-Platform Behavior](#cross-platform-behavior)
|
|
37
|
+
8. [Context Isolation (multi-tenant)](#context-isolation-multi-tenant)
|
|
38
|
+
9. [Security](#security)
|
|
39
|
+
10. [License](#license)
|
|
38
40
|
|
|
39
41
|
---
|
|
40
42
|
|
|
41
43
|
## Is this for you?
|
|
42
44
|
|
|
43
|
-
memorio is built for speed of thought: you think "I need this value everywhere," you write one line, it works. That's the entire pitch
|
|
45
|
+
memorio is built for speed of thought: you think "I need this value everywhere," you write one line, it works. That's the entire pitch - and it's a real one, not a rewritten complaint about how much Redux boilerplate you've had to write.
|
|
44
46
|
|
|
45
47
|
**Reach for memorio when:**
|
|
46
|
-
- You want state that's simply *there*
|
|
48
|
+
- You want state that's simply *there* - no store setup, no provider wrapping your app.
|
|
47
49
|
- You want `localStorage`, `sessionStorage`, and `IndexedDB` behind one consistent API instead of learning three.
|
|
48
50
|
- You're prototyping, building an internal tool, or shipping something small-to-medium where iteration speed matters more than architectural ceremony.
|
|
49
51
|
|
|
50
52
|
**Reach for something else when:**
|
|
51
|
-
- You need every state path statically type-checked against every observer
|
|
52
|
-
- You need Redux-style middleware, action logs, or time-travel debugging as a hard requirement for a large team
|
|
53
|
-
- Your isolation requirements are a security boundary, not a convenience
|
|
53
|
+
- You need every state path statically type-checked against every observer - memorio's `state.foo.bar` and `observer('state.foo.bar', ...)` are connected by a string, not by the compiler, so a rename won't be caught for you. However, if you want compiler-checked state access, use `memorio.typed<T>()` (see [Typed Stores](#typed-stores)) which gives you full static types on the global `state` proxy. For runtime validation as well, combine with `memorio.registerSchema()` (see [Schema Validation](#schema-validation)).
|
|
54
|
+
- You need Redux-style middleware, action logs, or time-travel debugging as a hard requirement for a large team - those tools exist because that discipline solves real problems at scale, not because their authors enjoy boilerplate. Memorio logs through `memorio.logger` and inspects via `memorio.devtools`, but doesn't have Redux DevTools time-travel.
|
|
55
|
+
- Your isolation requirements are a security boundary, not a convenience - see [Context Isolation](#context-isolation-multi-tenant) before you rely on it for that.
|
|
54
56
|
|
|
55
57
|
Pick the right tool. memorio is at its best when the answer to "how much state architecture do I need here?" is genuinely "not much."
|
|
56
58
|
|
|
@@ -63,15 +65,18 @@ npm i memorio
|
|
|
63
65
|
# pnpm add memorio
|
|
64
66
|
# yarn add memorio
|
|
65
67
|
|
|
66
|
-
# Optional
|
|
68
|
+
# Optional - only if you use the React hook
|
|
67
69
|
npm i react react-dom
|
|
70
|
+
|
|
71
|
+
# Optional - only if you want a local sql.js for `sqlite` (default uses CDN)
|
|
72
|
+
npm i sql.js
|
|
68
73
|
```
|
|
69
74
|
|
|
70
75
|
---
|
|
71
76
|
|
|
72
77
|
## Quick Start
|
|
73
78
|
|
|
74
|
-
### Global style
|
|
79
|
+
### Global style - the whole point of memorio
|
|
75
80
|
|
|
76
81
|
```typescript
|
|
77
82
|
import 'memorio' // once, at your entry point
|
|
@@ -85,7 +90,7 @@ useObserver(
|
|
|
85
90
|
)
|
|
86
91
|
```
|
|
87
92
|
|
|
88
|
-
### Named imports
|
|
93
|
+
### Named imports - same instances, explicit about it
|
|
89
94
|
|
|
90
95
|
```typescript
|
|
91
96
|
import { state, store, session, cache, idb, observer, useObserver, dispatch, memorio } from 'memorio'
|
|
@@ -102,13 +107,13 @@ function Counter() {
|
|
|
102
107
|
}
|
|
103
108
|
```
|
|
104
109
|
|
|
105
|
-
Two styles, one engine underneath
|
|
110
|
+
Two styles, one engine underneath - use whichever reads better in your codebase.
|
|
106
111
|
|
|
107
112
|
---
|
|
108
113
|
|
|
109
114
|
## API Reference
|
|
110
115
|
|
|
111
|
-
### `state`
|
|
116
|
+
### `state` - reactive, volatile, Proxy-based
|
|
112
117
|
|
|
113
118
|
```javascript
|
|
114
119
|
state.user = { name: 'Sara', role: 'admin' }
|
|
@@ -125,7 +130,7 @@ state.config.maxUsers = 200 // throws: state 'config' is locked
|
|
|
125
130
|
state.config.unlock()
|
|
126
131
|
```
|
|
127
132
|
|
|
128
|
-
### `store`
|
|
133
|
+
### `store` - the value that survives a refresh
|
|
129
134
|
|
|
130
135
|
```javascript
|
|
131
136
|
store.set('preferences', { theme: 'dark' })
|
|
@@ -133,21 +138,21 @@ store.get('preferences') // { theme: 'dark' } or null
|
|
|
133
138
|
store.isPersistent // true when backed by real localStorage
|
|
134
139
|
```
|
|
135
140
|
|
|
136
|
-
### `session`
|
|
141
|
+
### `session` - lives as long as the tab does
|
|
137
142
|
|
|
138
143
|
```javascript
|
|
139
144
|
session.set('token', 'user-abc-123')
|
|
140
145
|
session.get('token')
|
|
141
146
|
```
|
|
142
147
|
|
|
143
|
-
### `cache`
|
|
148
|
+
### `cache` - the fastest thing you own, gone on refresh
|
|
144
149
|
|
|
145
150
|
```javascript
|
|
146
151
|
cache.set('temp', computeExpensiveResult())
|
|
147
152
|
cache.get('temp')
|
|
148
153
|
```
|
|
149
154
|
|
|
150
|
-
### `idb`
|
|
155
|
+
### `idb` - typed, async, structured tables without the ceremony
|
|
151
156
|
|
|
152
157
|
```javascript
|
|
153
158
|
await idb.db.create('my-db')
|
|
@@ -156,9 +161,91 @@ await idb.data.set('my-db', 'users', { id: 1, name: 'Sara' })
|
|
|
156
161
|
const user = await idb.data.get('my-db', 'users', 1)
|
|
157
162
|
```
|
|
158
163
|
|
|
159
|
-
> IndexedDB is a browser-only primitive
|
|
164
|
+
> IndexedDB is a browser-only primitive - see [Cross-Platform Behavior](#cross-platform-behavior) for what happens off the browser.
|
|
165
|
+
|
|
166
|
+
### `sqlite` - in-memory SQLite, zero config
|
|
167
|
+
|
|
168
|
+
`memorio.sqlite` is an optional, lazily-loaded SQLite engine backed by [`sql.js`](https://sql.js.org) (the optional `sql.js` npm package is **not** a build-time dependency). It runs entirely in memory — ideal for ad-hoc SQL, structured queryable data, and local relational lookups. The engine is fetched on first use and never loaded when you don't touch `sqlite`.
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
import 'memorio' // or: import { sqlite } from 'memorio'
|
|
172
|
+
|
|
173
|
+
await sqlite.ready // waits until the sql.js engine is loaded (or rejected)
|
|
174
|
+
sqlite.db.support() // true when the current platform can run sql.js
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Bases (databases) are addressed by name, like `idb`:
|
|
178
|
+
const SQL = await sqlite.db.getSQL() // the underlying sql.js engine (loaded once, cached)
|
|
179
|
+
sqlite.db.version() // engine version string, or null before load
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Bases (databases) are addressed by name, like `idb`. `sqlite.db.create('app')` opens (or creates) an in-memory database, while `sqlite.db.get('app')` retrieves an already-open handle (`sqlite.db.list/delete/size` round out the lifecycle):
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
await sqlite.db.create('app') // in-memory database handle
|
|
186
|
+
await sqlite.query.run('app', 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)')
|
|
187
|
+
``` `sqlite.data` gives a small shortcut layer, and `sqlite.query` runs SQL:
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
// Write (shortcut over raw prepare/bind)
|
|
191
|
+
await sqlite.data.set('app', 'users', { id: 1, name: 'Sara' })
|
|
192
|
+
await sqlite.data.get('app', 'users', 1) // { id: 1, name: 'Sara' }
|
|
193
|
+
|
|
194
|
+
// SQL
|
|
195
|
+
await sqlite.query.run('app', 'CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)')
|
|
196
|
+
const rows = await sqlite.query.select('app', 'SELECT * FROM items', [1])
|
|
197
|
+
|
|
198
|
+
// Portability: export / import a whole base as a binary blob
|
|
199
|
+
const blob = await sqlite.db.export('app')
|
|
200
|
+
await sqlite.db.import('app', blob)
|
|
201
|
+
|
|
202
|
+
// Clean up
|
|
203
|
+
await sqlite.db.delete('app')
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
#### Loading the engine
|
|
207
|
+
|
|
208
|
+
The default loader injects the sql.js UMD build (`sql-wasm-browser.js`) from the
|
|
209
|
+
jsDelivr CDN as a classic `<script>`; on load it exposes the `initSqlJs` factory
|
|
210
|
+
on `globalThis`, which memorio then calls with your `locateFile`. This avoids a
|
|
211
|
+
bare `import('sql.js')` so bundlers never resolve the optional `sql.js`
|
|
212
|
+
dependency at build time:
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
// Zero config: CDN <script> loader (default)
|
|
216
|
+
await sqlite.ready
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
If you prefer a local/bundled `sql.js` (offline, private network, or to pin a
|
|
220
|
+
version), install it and supply your own loader:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
npm i sql.js # optional peer
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
```javascript
|
|
227
|
+
import { sqlite } from 'memorio'
|
|
228
|
+
|
|
229
|
+
sqlite.config({ loader: () => import('sql.js') }) // local/npm build of sql.js
|
|
230
|
+
await sqlite.ready
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
You can also point the WASM `locateFile` step at your own CDN/base via
|
|
234
|
+
`sqliteWasmBase` on `globalThis`, or pass a custom `locateFile` through
|
|
235
|
+
`sqlite.config({ locateFile })`:
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
globalThis.sqliteWasmBase = 'https://your-cdn.example.com/sql.js/dist/'
|
|
239
|
+
sqlite.config({ locateFile: (file) => `https://your-cdn.example.com/sql.js/dist/${file}` })
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
When the engine can't load (e.g. no WASM support), `sqlite.ready` rejects, `sqlite._disabled` becomes `true`, and `sqlite._warning` explains why — `db.support()` simply returns `false` so you can degrade gracefully.
|
|
243
|
+
|
|
244
|
+
- `sqlite.config({ persistence: true })` / `sqlite.db.create('app', { persistence: true })` snapshot a database to `store` (localStorage, namespaced) and restore it on reopen — sql.js databases are in-memory by default; see [SQLite docs – Persistence & dev download](markdown/SQLITE.md#example-5-persistence--dev-download).
|
|
160
245
|
|
|
161
|
-
|
|
246
|
+
See [SQLite docs](markdown/SQLITE.md) for the full reference.
|
|
247
|
+
|
|
248
|
+
### `observer` / `useObserver` - watch a path, react to it
|
|
162
249
|
|
|
163
250
|
```javascript
|
|
164
251
|
observer('state.user', (newVal, oldVal) => {
|
|
@@ -168,14 +255,14 @@ observer('state.user', (newVal, oldVal) => {
|
|
|
168
255
|
|
|
169
256
|
> 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.
|
|
170
257
|
|
|
171
|
-
### `dispatch`
|
|
258
|
+
### `dispatch` - the event bus underneath it all
|
|
172
259
|
|
|
173
260
|
```javascript
|
|
174
261
|
memorio.dispatch.listen('state.user', (event) => console.debug(event.detail))
|
|
175
262
|
memorio.dispatch.set('state.user', { detail: { name: 'Sara' } })
|
|
176
263
|
```
|
|
177
264
|
|
|
178
|
-
### `devtools`
|
|
265
|
+
### `devtools` - see everything, instantly
|
|
179
266
|
|
|
180
267
|
```javascript
|
|
181
268
|
memorio.devtools.inspect()
|
|
@@ -185,32 +272,358 @@ memorio.devtools.exportData()
|
|
|
185
272
|
|
|
186
273
|
Pairs with [dphelper-manager](https://chrome.google.com/webstore/detail/dphelper-manager-dev-tool/oppppldaoknfddeikfloonnialijngbk) for visual, time-travel inspection of your global state straight from devtools.
|
|
187
274
|
|
|
188
|
-
### `logger`
|
|
275
|
+
### `logger` - a black box for every change
|
|
189
276
|
|
|
190
277
|
```javascript
|
|
191
278
|
memorio.logger.configure({ enabled: true, logToConsole: true })
|
|
192
279
|
memorio.logger.getHistory()
|
|
193
280
|
```
|
|
194
281
|
|
|
195
|
-
> It logs *everything* written to `state`/`store`/`session`
|
|
282
|
+
> It logs *everything* written to `state`/`store`/`session` - including anything sensitive you put there. Great for debugging, but don't leave it on unconditionally in production if secrets or PII pass through your state. See [Security](#security).
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Typed Stores
|
|
287
|
+
|
|
288
|
+
`memorio.typed<T>()` returns the global `state` proxy cast to a TypeScript type `T`, giving you **compile-time** type safety on every access and mutation. The returned object is the *exact same* Proxy as `globalThis.state` — no duplication, no overhead.
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
import 'memorio'
|
|
292
|
+
|
|
293
|
+
interface AppState {
|
|
294
|
+
user: { name: string; age: number; email: string }
|
|
295
|
+
theme: 'light' | 'dark'
|
|
296
|
+
items: string[]
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const app = memorio.typed<AppState>()
|
|
300
|
+
|
|
301
|
+
// Type-checked at compile time:
|
|
302
|
+
app.user = { name: 'Sara', age: 30, email: 'sara@test.com' }
|
|
303
|
+
app.theme = 'dark'
|
|
304
|
+
|
|
305
|
+
// TypeScript errors:
|
|
306
|
+
// app.user = { name: 42 } // age missing, name wrong type
|
|
307
|
+
// app.theme = 'purple' // not a valid literal
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
The same data is accessible via the global `state` proxy — `app` and `state` are identical instances:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
state.user = { name: 'Sara', age: 30, email: 'sara@test.com' }
|
|
314
|
+
app.user.name // 'Sara' — same Proxy
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
For runtime safety (rejecting invalid values even when TypeScript isn't checking), combine with [Schema Validation](#schema-validation) below.
|
|
318
|
+
|
|
319
|
+
See [Typed Stores docs](markdown/TYPED.md) for full examples.
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Schema Validation
|
|
324
|
+
|
|
325
|
+
`memorio.registerSchema(path, schema)` lets you register runtime validators for state paths. When a write to `state.somePath = value` violates a registered schema, the write is **rejected** before the value is stored.
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
import 'memorio'
|
|
329
|
+
|
|
330
|
+
memorio.registerSchema('user', {
|
|
331
|
+
type: 'object',
|
|
332
|
+
required: ['name', 'email'],
|
|
333
|
+
properties: {
|
|
334
|
+
name: { type: 'string', min: 1 },
|
|
335
|
+
email: { type: 'string', pattern: /^[^@]+@[^@]+$/ },
|
|
336
|
+
age: { type: 'number', min: 0, max: 150 }
|
|
337
|
+
}
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
state.user = { name: 'Sara', email: 'sara@test.com', age: 30 } // accepted
|
|
341
|
+
state.user = { name: 'Sara' } // rejected: missing 'email'
|
|
342
|
+
state.user = { name: 42 } // rejected: wrong type
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Custom validator functions are also supported:
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
memorio.registerSchema('theme', (val) => {
|
|
349
|
+
return val === 'light' || val === 'dark'
|
|
350
|
+
? true
|
|
351
|
+
: 'theme must be "light" or "dark"'
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
state.theme = 'purple' // rejected: "theme must be light or dark"
|
|
355
|
+
state.theme = 'dark' // accepted
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Manual validation without writing:
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
memorio.validate('user', { name: 'Sara', email: 'sara@test.com' })
|
|
362
|
+
// { valid: true }
|
|
363
|
+
|
|
364
|
+
memorio.validate('user', { name: 'Sara' })
|
|
365
|
+
// { valid: false, errors: ["user: missing required property 'email'"] }
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Manage registered schemas:
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
memorio.listSchemas() // ['user', 'theme']
|
|
372
|
+
memorio.unregisterSchema('theme') // removes the validator
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
**Typed + Schema** — combine both for full safety:
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
interface AppState {
|
|
379
|
+
user: { name: string; email: string }
|
|
380
|
+
theme: 'light' | 'dark'
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const app = memorio.typed<AppState>()
|
|
384
|
+
|
|
385
|
+
memorio.registerSchema('user', {
|
|
386
|
+
type: 'object',
|
|
387
|
+
required: ['name', 'email'],
|
|
388
|
+
properties: {
|
|
389
|
+
name: { type: 'string', min: 1 },
|
|
390
|
+
email: { type: 'string', pattern: /^[^@]+@[^@]+$/ }
|
|
391
|
+
}
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
app.user = { names: 'Sara' } // ❌ TS: wrong shape
|
|
395
|
+
app.user = { name: '', email: '' } // ❌ TS passes, ❌ runtime: name too short
|
|
396
|
+
app.user = { name: 'Sara', email: 'sara@test.com' } // ✅ both pass
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
See [Schema Validation docs](markdown/SCHEMA.md) for the full schema definition reference.
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## Memory System
|
|
404
|
+
|
|
405
|
+
`memorio.memory` provides a semantic memory layer — a key/value store with **type safety**, **TTL**, **confidence scoring**, **tagging**, and **scope-based persistence**. It's designed for AI agents and applications that need structured, queryable memory with lifecycle management.
|
|
406
|
+
|
|
407
|
+
### Core API
|
|
408
|
+
|
|
409
|
+
```ts
|
|
410
|
+
// remember(key, value, options)
|
|
411
|
+
await memorio.memory.remember('user.language', 'Italian', {
|
|
412
|
+
type: 'preference',
|
|
413
|
+
confidence: 0.92,
|
|
414
|
+
scope: 'local',
|
|
415
|
+
ttl: null, // never expires
|
|
416
|
+
tags: ['ui', 'user'],
|
|
417
|
+
source: 'conversation'
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
// recall(key, options)
|
|
421
|
+
const lang = await memorio.memory.recall('user.language')
|
|
422
|
+
// → 'Italian'
|
|
423
|
+
|
|
424
|
+
// update(key, value, options) — creates a superseded version of the old entry
|
|
425
|
+
await memorio.memory.update('user.language', 'English', { confidence: 0.95 })
|
|
426
|
+
|
|
427
|
+
// forget(key) — permanently deletes
|
|
428
|
+
await memorio.memory.forget('user.language')
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Scopes
|
|
432
|
+
|
|
433
|
+
| Scope | Storage | TTL | Cross-session | Size limit |
|
|
434
|
+
|---|---|---|---|---|
|
|
435
|
+
| `hot` | `state` proxy | ✅ | ❌ | ~5MB (RAM) |
|
|
436
|
+
| `session` | `sessionStorage` | ✅ | Tab only | ~5MB |
|
|
437
|
+
| `local` | `localStorage` | ✅ | ✅ | ~10MB |
|
|
438
|
+
| `durable` | `IndexedDB` | ✅ | ✅ | ~1GB+ |
|
|
439
|
+
|
|
440
|
+
Default scope is `local` for values ≤100KB, `durable` for larger values.
|
|
441
|
+
|
|
442
|
+
### Context API
|
|
443
|
+
|
|
444
|
+
```ts
|
|
445
|
+
// context() — returns ranked, relevant memories
|
|
446
|
+
const context = await memorio.memory.context({
|
|
447
|
+
tags: 'user',
|
|
448
|
+
types: ['preference', 'decision'],
|
|
449
|
+
minConfidence: 0.7,
|
|
450
|
+
maxEntries: 10
|
|
451
|
+
})
|
|
452
|
+
// → [{ key, value, relevance, age, confidence, ... }]
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Memory Entry Model
|
|
456
|
+
|
|
457
|
+
Each memory is stored as a structured entry:
|
|
458
|
+
|
|
459
|
+
```ts
|
|
460
|
+
{
|
|
461
|
+
id: string // unique identifier
|
|
462
|
+
key: string // path-like key (e.g. "user.preference.theme")
|
|
463
|
+
value: any // the stored data
|
|
464
|
+
type: MemoryType // 'fact' | 'preference' | 'decision' | 'task' | 'context'
|
|
465
|
+
confidence: number // 0.0–1.0
|
|
466
|
+
scope: MemoryScope // 'hot' | 'session' | 'local' | 'durable'
|
|
467
|
+
ttl?: number | null // milliseconds (null = never expires)
|
|
468
|
+
tags: string[] // for filtering
|
|
469
|
+
source?: string // where this memory came from
|
|
470
|
+
status: MemoryStatus // 'active' | 'obsolete' | 'superseded'
|
|
471
|
+
createdAt: number // timestamp
|
|
472
|
+
lastConfirmedAt: number
|
|
473
|
+
supersededId?: string | null
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Memory Lifecycle
|
|
478
|
+
|
|
479
|
+
| Operation | Behavior |
|
|
480
|
+
|---|---|
|
|
481
|
+
| `remember(key, newValue)` when entry exists | Old entry → `superseded` status, new entry → `active` |
|
|
482
|
+
| `recall(key)` | Returns active value; expired entries return `null` unless `includeObsolete: true` |
|
|
483
|
+
| `update(key, value)` | Creates superseded copy + updated active entry |
|
|
484
|
+
| Entry with `ttl` expires | Status → `obsolete` (via `forgetExpired()`) |
|
|
485
|
+
| `clear()` | Wipes all memories + index |
|
|
486
|
+
|
|
487
|
+
See [Memory docs](markdown/MEMORY.md) for full reference.
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
## Synchronization & Cloud (optional)
|
|
492
|
+
|
|
493
|
+
`memorio.memory` is **local-first**. The data is created and served from the
|
|
494
|
+
device; the cloud is only ever a **transport/persistence provider**, never the
|
|
495
|
+
source of truth. Enabling sync does not replace local storage — it *mirrors* it.
|
|
496
|
+
|
|
497
|
+
```
|
|
498
|
+
memorio
|
|
499
|
+
│
|
|
500
|
+
┌────────┴────────┐
|
|
501
|
+
│ Memory Engine │
|
|
502
|
+
└────────┬────────┘
|
|
503
|
+
┌────────────┼────────────┐
|
|
504
|
+
▼ ▼ ▼
|
|
505
|
+
local SQLite cloud
|
|
506
|
+
memory durable sync
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Configuring a backend
|
|
510
|
+
|
|
511
|
+
Sync is **opt-in**. You supply an application-owned `provider` that knows how to
|
|
512
|
+
talk to your backend (REST, WebSocket, Supabase, a custom agent server, a PostgreSQL
|
|
513
|
+
database, etc.). **Memorio never handles credentials** — authentication and
|
|
514
|
+
authorization live in your backend/provider (OWASP A01: Broken Access Control):
|
|
515
|
+
|
|
516
|
+
```ts
|
|
517
|
+
memorio.memory.configure({
|
|
518
|
+
namespace: 'user:123:device:abc', // tenant/user/device — partitions the journal
|
|
519
|
+
provider: { // application-owned transport
|
|
520
|
+
push(ops) { return fetch('/api/sync', { method: 'POST', body: JSON.stringify(ops), headers: auth }) }
|
|
521
|
+
pull(since) { return fetch(`/api/sync?since=${since}`).then(r => r.json()) }
|
|
522
|
+
},
|
|
523
|
+
auto: true // auto-replay pending ops on focus/online (default true)
|
|
524
|
+
})
|
|
525
|
+
|
|
526
|
+
await memorio.memory.ready // waits until the local journal substrate is chosen
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
Once configured, `memorio.memory` records every operation locally and keeps it
|
|
530
|
+
until the provider acknowledges it.
|
|
531
|
+
|
|
532
|
+
### The local journal
|
|
533
|
+
|
|
534
|
+
Every mutation (`remember`, `update`, `forget`, `expire`, `confirm`, `supersede`)
|
|
535
|
+
is written to a local, **namespaced** operation journal:
|
|
536
|
+
|
|
537
|
+
```ts
|
|
538
|
+
await memory.remember('user.language', 'Italian', { scope: 'local' })
|
|
539
|
+
|
|
540
|
+
memory.journal.pending() // -> [{ id, key:'user.language', value:'Italian', operation:'remember', sync:'pending', version: 1, updatedAt: ... }]
|
|
541
|
+
memory.journal.markSynced([id])
|
|
542
|
+
memory.journal.replay() // pushes pending() to provider.provider, marks synced, optional pull+apply
|
|
543
|
+
memory.journal.status() // 'sqlite' | 'store' (substrate in use)
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
The journal stores the full entry plus `operation`, `version`, `updatedAt` and
|
|
547
|
+
`sync` status, so conflicts can be resolved when connectivity returns.
|
|
548
|
+
|
|
549
|
+
### Substrates
|
|
550
|
+
|
|
551
|
+
The journal is persisted on `store` (localStorage / in-memory Map fallback) — **not** on a sql.js database, because sql.js databases are volatile (in-memory) and would lose pending operations on reload:
|
|
552
|
+
|
|
553
|
+
| Substrate | Used for | Notes |
|
|
554
|
+
|---|---|---|
|
|
555
|
+
| `store` (localStorage) | sync journal | persistent across reloads; namespaced; Map fallback in Node |
|
|
556
|
+
| `sqlite` (sql.js) | ad-hoc SQL / value storage | in-memory by default — use `persistence: true` to snapshot to `store` |
|
|
557
|
+
| `idb` (IndexedDB) | `memorio.memory` durable scope values | persistent; persistent across reloads |
|
|
558
|
+
|
|
559
|
+
The `sqlite.db.download(name)` dev helper also triggers a browser `.sqlite` download of an in-memory database on demand.
|
|
560
|
+
|
|
561
|
+
### Sync = operations, not database dumps
|
|
562
|
+
|
|
563
|
+
Memorio syncronizza **operazioni di memoria**, never a raw database dump. When
|
|
564
|
+
two devices diverge, conflict resolution is based on `confidence`,
|
|
565
|
+
`lastConfirmedAt`, `source`, `version` and `scope` — not just "last write wins":
|
|
566
|
+
|
|
567
|
+
```ts
|
|
568
|
+
// Device A: user.language = Italian, confidence 0.92
|
|
569
|
+
// Device B: user.language = English, confidence 0.61
|
|
570
|
+
// → the higher-confidence entry wins locally; the provider decides for shared.
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
The `namespace` (tenant / user / device) is the single partition key: local
|
|
574
|
+
journal reads and writes are scoped to it, and they can never cross namespaces —
|
|
575
|
+
a client holding a fake/forged namespace simply sees its own empty journal.
|
|
576
|
+
|
|
577
|
+
### Scopes (isolation, not a security boundary)
|
|
578
|
+
|
|
579
|
+
```
|
|
580
|
+
scope: 'device' // only this browser/device
|
|
581
|
+
scope: 'user' // follows the user across devices (via sync)
|
|
582
|
+
scope: 'shared' // shared across users/tenant
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
`scope: 'user'`/`'shared'` require a provider + namespace. `scope: 'device'`
|
|
586
|
+
is local only. As with `memorio.createContext`, **scoping is a naming convention,
|
|
587
|
+
not a security boundary** — enforce real isolation in your backend.
|
|
588
|
+
|
|
589
|
+
### Security posture
|
|
590
|
+
|
|
591
|
+
- **NIST SP 800-53 / OWASP**: no credentials, tokens, or secrets are read from or
|
|
592
|
+
stored by `memorio`; sensitive state you place in `state`/`store`/`session`/`idb`
|
|
593
|
+
is not encrypted by memorio (see [Security](#security)).
|
|
594
|
+
- **Namespace isolation**: journal reads/writes are keyed by
|
|
595
|
+
`namespace:id` at the storage layer; there is no API to enumerate or open
|
|
596
|
+
another namespace's journal (defense-in-depth).
|
|
597
|
+
- **No dynamic code**: journal entries are strictly JSON-round-tripped and
|
|
598
|
+
size-capped (10 MB/entry); no `eval`/template-injection of provider data.
|
|
599
|
+
- **Trust boundary**: the provider/backend owns authentication, authorization,
|
|
600
|
+
and remote-side conflict resolution. Memorio owns the local durable copy and
|
|
601
|
+
the operation log; it surfaces `conflict`/`error` rows via `journal.pending()`.
|
|
602
|
+
- **NSA/CISA advice (data-at-rest/secrets)**: if you persist `state`/`store`
|
|
603
|
+
server-side or ship user data through your backend, encrypt it server-side with
|
|
604
|
+
keys you manage; memorio treats the local journal as untrusted-from-the-browser
|
|
605
|
+
and does not attest its own integrity.
|
|
606
|
+
|
|
607
|
+
See [SQLite docs](markdown/SQLITE.md), [Memory docs](markdown/MEMORY.md), and the [Synchronization & Cloud guide](markdown/SYNC.md).
|
|
196
608
|
|
|
197
609
|
---
|
|
198
610
|
|
|
199
611
|
## Cross-Platform Behavior
|
|
200
612
|
|
|
201
|
-
memorio runs everywhere JavaScript does
|
|
613
|
+
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:
|
|
202
614
|
|
|
203
615
|
| API | Browser | Node.js | Deno | Edge / Workers |
|
|
204
616
|
|---|---|---|---|---|
|
|
205
617
|
| `state` | Proxy in memory | Proxy in memory | Proxy in memory | Proxy in memory |
|
|
206
618
|
| `observer` / `useObserver` | ✅ | ✅ | ✅ | ✅ |
|
|
207
619
|
| `cache` | ✅ in memory | ✅ in memory | ✅ in memory | ✅ in memory |
|
|
208
|
-
| `store` | `localStorage` | `Map` fallback
|
|
209
|
-
| `session` | `sessionStorage` | `Map` fallback
|
|
620
|
+
| `store` | `localStorage` | `Map` fallback - **not durable across restarts** | `Map` fallback - not durable | `localStorage` where available, else `Map` |
|
|
621
|
+
| `session` | `sessionStorage` | `Map` fallback - not durable | `Map` fallback - not durable | `sessionStorage` where available, else `Map` |
|
|
210
622
|
| `idb` | ✅ `IndexedDB` | ❌ not available | ❌ not available | ⚠️ check `getCapabilities()` |
|
|
623
|
+
| `sqlite` | ✅ `sql.js` (lazy, CDN by default) | ❌ not available | ❌ not available | ⚠️ check `sqlite.db.support()` |
|
|
211
624
|
| `devtools` | ✅ | ❌ | ❌ | ⚠️ |
|
|
212
625
|
|
|
213
|
-
Same API top to bottom
|
|
626
|
+
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.
|
|
214
627
|
|
|
215
628
|
```javascript
|
|
216
629
|
memorio.isBrowser()
|
|
@@ -234,10 +647,10 @@ const ctx = memorio.createContext('tenant-123')
|
|
|
234
647
|
ctx.state.user = { name: 'Isolated' }
|
|
235
648
|
ctx.store.set('settings', { theme: 'dark' })
|
|
236
649
|
|
|
237
|
-
console.debug(state.user) // undefined
|
|
650
|
+
console.debug(state.user) // undefined - separate namespace
|
|
238
651
|
```
|
|
239
652
|
|
|
240
|
-
Handy for keeping per-tenant or per-request state from colliding in the same process. Under the hood it's a key prefix (`tenant-123-key`) inside the same storage
|
|
653
|
+
Handy for keeping per-tenant or per-request state from colliding in the same process. Under the hood it's a key prefix (`tenant-123-key`) inside the same storage - a naming convention, not a hard memory boundary. Great for a browser tab; if you're running this in a shared Node.js process or an edge isolate that might be reused across requests, make sure tenant IDs can't collide or be forged, and don't treat this as your only isolation layer if the stakes are real.
|
|
241
654
|
|
|
242
655
|
---
|
|
243
656
|
|
|
@@ -248,9 +661,9 @@ Handy for keeping per-tenant or per-request state from colliding in the same pro
|
|
|
248
661
|
- Session IDs generated via `crypto.randomUUID`.
|
|
249
662
|
- Inputs validated, keys sanitized, errors caught at module boundaries.
|
|
250
663
|
|
|
251
|
-
memorio does **not** encrypt what you put into `store`, `session`, or `idb`
|
|
664
|
+
memorio does **not** encrypt what you put into `store`, `session`, or `idb` - they're thin, fast wrappers over `localStorage`/`sessionStorage`/`IndexedDB`, which store data in the clear on the user's device. Bring your own encryption layer for tokens, secrets, or regulated personal data.
|
|
252
665
|
|
|
253
|
-
We build with recognized engineering guidance (NIST SP 800-53 practices) as an input to how we write code
|
|
666
|
+
We build with recognized engineering guidance (NIST SP 800-53 practices) as an input to how we write code - that's a design discipline, not a compliance certificate, and no third-party audit has been performed. Found an issue? Report it privately - see `SECURITY.md` - rather than opening a public issue.
|
|
254
667
|
|
|
255
668
|
---
|
|
256
669
|
|
package/SECURITY.md
CHANGED
|
@@ -24,15 +24,15 @@ Memorio follows NIST and NSA security standards at the enterprise level.
|
|
|
24
24
|
## OWASP Compliance
|
|
25
25
|
|
|
26
26
|
Addresses OWASP Top 10 (2021):
|
|
27
|
-
- A01:2021
|
|
28
|
-
- A02:2021
|
|
29
|
-
- A03:2021
|
|
30
|
-
- A05:2021
|
|
31
|
-
- A06:2021
|
|
32
|
-
- A07:2021
|
|
33
|
-
- A08:2021
|
|
34
|
-
- A09:2021
|
|
35
|
-
- A10:2021
|
|
27
|
+
- A01:2021 - Broken Access Control (global object protection, property locks)
|
|
28
|
+
- A02:2021 - Cryptographic Failures (crypto.randomUUID for session IDs)
|
|
29
|
+
- A03:2021 - Injection (CSS sanitization in devtools)
|
|
30
|
+
- A05:2021 - Security Misconfiguration (minimal surface area, no bundled secrets)
|
|
31
|
+
- A06:2021 - Vulnerable and Outdated Components (regular npm audit, Socket.dev)
|
|
32
|
+
- A07:2021 - Identification and Authentication Failures (N/A - library, no auth)
|
|
33
|
+
- A08:2021 - Software and Data Integrity Failures (strict tsconfig, lock files)
|
|
34
|
+
- A09:2021 - Security Logging and Monitoring Failures (DevTools inspect, Logger module)
|
|
35
|
+
- A10:2021 - Server-Side Request Forgery (N/A - no network requests)
|
|
36
36
|
|
|
37
37
|
## Reporting Security Issues
|
|
38
38
|
|
|
@@ -44,5 +44,5 @@ If you find a security vulnerability:
|
|
|
44
44
|
Do not open public issues for security vulnerabilities.
|
|
45
45
|
|
|
46
46
|
---
|
|
47
|
-
*Document version: 2.0
|
|
47
|
+
*Document version: 2.0 - Last updated: 2026-05-19*
|
|
48
48
|
*Owner: BigLogic Security Team*
|