memorio 4.7.0 → 4.7.3
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 +302 -188
- package/SECURITY.md +10 -10
- package/SUMMARY.md +14 -0
- package/index.cjs +166 -6
- package/index.d.ts +3 -0
- package/index.js +119 -6
- package/llms.txt +176 -101
- package/markdown/CHANGELOG.md +8 -8
- package/markdown/HISTORY.md +192 -0
- package/markdown/INSPECT.md +116 -0
- package/markdown/MEMORY.md +155 -0
- package/markdown/SCHEMA.md +169 -0
- package/markdown/SECURITY.md +1 -1
- package/markdown/TYPED.md +158 -0
- package/package.json +7 -7
- package/types/exports.d.ts +25 -0
- package/types/history.d.ts +27 -0
- package/types/inspect.d.ts +14 -0
- package/types/memorio.d.ts +48 -2
- package/types/memory.d.ts +70 -0
- package/types/schema.d.ts +53 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 🧠 memorio
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
[](https://npmjs.com/package/memorio)
|
|
6
6
|
[](https://npmjs.com/package/memorio)
|
|
@@ -13,197 +13,143 @@
|
|
|
13
13
|

|
|
14
14
|

|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
### One import. Global state, persistence, and a IndexedDB layer - done.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
```javascript
|
|
19
|
+
import 'memorio'
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
state.user = { name: 'Sara' } // reactive, everywhere, instantly
|
|
22
|
+
```
|
|
21
23
|
|
|
22
|
-
|
|
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.
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
| **Dependencies** | ✅ **Zero** | ❌ Many | ⚠️ Few |
|
|
28
|
-
| **TypeScript** | ✅ Native | ✅ Yes | ✅ Yes |
|
|
29
|
-
| **Binary storage** | ✅ Built-in IDB | ❌ Add-on | ❌ Add-on |
|
|
30
|
-
| **Observer** | ✅ Built-in | ❌ Add-on | ❌ Add-on |
|
|
31
|
-
| **DevTools** | ✅ Built-in + dphelper-manager | ⚠️ Extension | ⚠️ Extension |
|
|
32
|
-
| **Edge runtime** | ✅ Workers, Deno | ❌ Limited | ❌ Limited |
|
|
33
|
-
| **Learning curve** | ✅ **5 minutes** | ❌ Hours | ⚠️ 30 min |
|
|
34
|
-
| **Boilerplate** | ✅ **None** | ❌ Tons | ⚠️ Some |
|
|
35
|
-
| **React support** | ✅ `useObserver` hook | ✅ `connect` | ✅ `useSyncExternalStore` |
|
|
36
|
-
| **Context isolation** | ✅ Multi-tenant | ⚠️ Manual | ⚠️ Manual |
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Table of Contents
|
|
37
29
|
|
|
38
|
-
|
|
30
|
+
1. [Is this for you?](#is-this-for-you)
|
|
31
|
+
2. [Installation](#installation)
|
|
32
|
+
3. [Quick Start](#quick-start)
|
|
33
|
+
4. [API Reference](#api-reference)
|
|
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)
|
|
39
40
|
|
|
40
41
|
---
|
|
41
42
|
|
|
42
|
-
##
|
|
43
|
+
## Is this for you?
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
| **Platform detection** | `isBrowser`, `isNode`, `isDeno`, `isEdge` |
|
|
58
|
-
|
|
59
|
-
No Zustand. No Redux. No provider boilerplate.
|
|
60
|
-
**Import → assign → done.**
|
|
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.
|
|
46
|
+
|
|
47
|
+
**Reach for memorio when:**
|
|
48
|
+
- You want state that's simply *there* - no store setup, no provider wrapping your app.
|
|
49
|
+
- You want `localStorage`, `sessionStorage`, and `IndexedDB` behind one consistent API instead of learning three.
|
|
50
|
+
- You're prototyping, building an internal tool, or shipping something small-to-medium where iteration speed matters more than architectural ceremony.
|
|
51
|
+
|
|
52
|
+
**Reach for something else when:**
|
|
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.
|
|
56
|
+
|
|
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."
|
|
61
58
|
|
|
62
59
|
---
|
|
63
60
|
|
|
64
|
-
##
|
|
61
|
+
## Installation
|
|
65
62
|
|
|
66
63
|
```bash
|
|
67
|
-
# npm
|
|
68
64
|
npm i memorio
|
|
65
|
+
# pnpm add memorio
|
|
66
|
+
# yarn add memorio
|
|
69
67
|
|
|
70
|
-
#
|
|
71
|
-
pnpm add memorio
|
|
72
|
-
|
|
73
|
-
# yarn
|
|
74
|
-
yarn add memorio
|
|
75
|
-
|
|
76
|
-
# React peer dep (optional, React >= 16.8)
|
|
68
|
+
# Optional - only if you use the React hook
|
|
77
69
|
npm i react react-dom
|
|
78
70
|
```
|
|
79
71
|
|
|
80
72
|
---
|
|
81
73
|
|
|
82
|
-
##
|
|
74
|
+
## Quick Start
|
|
83
75
|
|
|
84
|
-
### Global style
|
|
76
|
+
### Global style - the whole point of memorio
|
|
85
77
|
|
|
86
78
|
```typescript
|
|
87
|
-
|
|
88
|
-
import 'memorio'
|
|
79
|
+
import 'memorio' // once, at your entry point
|
|
89
80
|
|
|
90
|
-
// state is now available everywhere
|
|
91
81
|
state.user = { name: 'Sara', role: 'admin' }
|
|
92
82
|
state.counter++
|
|
93
|
-
state.settings = { theme: 'dark', lang: 'it' }
|
|
94
83
|
|
|
95
|
-
// React - automatic dependency discovery
|
|
96
84
|
useObserver(
|
|
97
|
-
() =>
|
|
85
|
+
() => console.debug('user changed:', state.user),
|
|
98
86
|
[state.user]
|
|
99
87
|
)
|
|
100
|
-
|
|
101
|
-
// Vanilla JS - event system
|
|
102
|
-
memorio.dispatch.listen('state.user', (event) => {
|
|
103
|
-
console.debug('user changed:', event.detail)
|
|
104
|
-
})
|
|
105
88
|
```
|
|
106
89
|
|
|
107
|
-
###
|
|
108
|
-
|
|
109
|
-
Every module is also a named export. Same instances, explicit dependencies.
|
|
90
|
+
### Named imports - same instances, explicit about it
|
|
110
91
|
|
|
111
92
|
```typescript
|
|
112
|
-
|
|
113
|
-
import {
|
|
114
|
-
state,
|
|
115
|
-
store,
|
|
116
|
-
session,
|
|
117
|
-
cache,
|
|
118
|
-
idb,
|
|
119
|
-
observer,
|
|
120
|
-
useObserver,
|
|
121
|
-
dispatch,
|
|
122
|
-
memorio
|
|
123
|
-
} from 'memorio'
|
|
93
|
+
import { state, store, session, cache, idb, observer, useObserver, dispatch, memorio } from 'memorio'
|
|
124
94
|
|
|
125
95
|
state.user = { name: 'Sara' }
|
|
126
96
|
store.set('theme', 'dark')
|
|
127
|
-
|
|
128
|
-
// CJS
|
|
129
|
-
const { state, store, memorio } = require('memorio')
|
|
130
97
|
```
|
|
131
98
|
|
|
132
99
|
```tsx
|
|
133
|
-
// React with named imports
|
|
134
|
-
import { useObserver, state } from 'memorio'
|
|
135
|
-
|
|
136
100
|
function Counter() {
|
|
137
101
|
const [, forceUpdate] = useReducer(x => x + 1, 0)
|
|
138
|
-
|
|
139
102
|
useObserver(forceUpdate, [state.counter])
|
|
140
|
-
|
|
141
103
|
return <div>Count: {state.counter}</div>
|
|
142
104
|
}
|
|
143
105
|
```
|
|
144
106
|
|
|
145
|
-
|
|
107
|
+
Two styles, one engine underneath - use whichever reads better in your codebase.
|
|
146
108
|
|
|
147
109
|
---
|
|
148
110
|
|
|
149
|
-
##
|
|
150
|
-
|
|
151
|
-
### `state` — Reactive volatile state
|
|
111
|
+
## API Reference
|
|
152
112
|
|
|
153
|
-
|
|
113
|
+
### `state` - reactive, volatile, Proxy-based
|
|
154
114
|
|
|
155
115
|
```javascript
|
|
156
|
-
// Set
|
|
157
116
|
state.user = { name: 'Sara', role: 'admin' }
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
// Get
|
|
161
|
-
const name = state.user.name // 'Sara'
|
|
162
|
-
|
|
163
|
-
// List all keys
|
|
164
|
-
console.debug(state.list) // ['user', 'items']
|
|
117
|
+
const name = state.user.name
|
|
165
118
|
|
|
166
|
-
//
|
|
119
|
+
state.list // ['user', 'items', ...]
|
|
167
120
|
state.remove('items')
|
|
168
|
-
|
|
169
|
-
// Clear all
|
|
170
121
|
state.removeAll()
|
|
171
122
|
|
|
172
|
-
//
|
|
123
|
+
// Freeze a slice of state when you need to stop guessing who mutated it
|
|
173
124
|
state.config = { maxUsers: 100 }
|
|
174
125
|
state.config.lock()
|
|
175
|
-
state.config.maxUsers = 200
|
|
126
|
+
state.config.maxUsers = 200 // throws: state 'config' is locked
|
|
176
127
|
state.config.unlock()
|
|
177
128
|
```
|
|
178
129
|
|
|
179
|
-
### `store`
|
|
130
|
+
### `store` - the value that survives a refresh
|
|
180
131
|
|
|
181
132
|
```javascript
|
|
182
133
|
store.set('preferences', { theme: 'dark' })
|
|
183
|
-
|
|
184
|
-
store.
|
|
185
|
-
store.removeAll()
|
|
186
|
-
console.debug(store.size(), 'chars stored')
|
|
187
|
-
console.debug(store.isPersistent) // true -> real localStorage
|
|
134
|
+
store.get('preferences') // { theme: 'dark' } or null
|
|
135
|
+
store.isPersistent // true when backed by real localStorage
|
|
188
136
|
```
|
|
189
137
|
|
|
190
|
-
### `session`
|
|
138
|
+
### `session` - lives as long as the tab does
|
|
191
139
|
|
|
192
140
|
```javascript
|
|
193
141
|
session.set('token', 'user-abc-123')
|
|
194
|
-
|
|
195
|
-
session.removeAll()
|
|
142
|
+
session.get('token')
|
|
196
143
|
```
|
|
197
144
|
|
|
198
|
-
### `cache`
|
|
145
|
+
### `cache` - the fastest thing you own, gone on refresh
|
|
199
146
|
|
|
200
147
|
```javascript
|
|
201
148
|
cache.set('temp', computeExpensiveResult())
|
|
202
|
-
|
|
203
|
-
cache.clear() // empty it all
|
|
149
|
+
cache.get('temp')
|
|
204
150
|
```
|
|
205
151
|
|
|
206
|
-
### `idb`
|
|
152
|
+
### `idb` - typed, async, structured tables without the ceremony
|
|
207
153
|
|
|
208
154
|
```javascript
|
|
209
155
|
await idb.db.create('my-db')
|
|
@@ -212,7 +158,9 @@ await idb.data.set('my-db', 'users', { id: 1, name: 'Sara' })
|
|
|
212
158
|
const user = await idb.data.get('my-db', 'users', 1)
|
|
213
159
|
```
|
|
214
160
|
|
|
215
|
-
|
|
161
|
+
> IndexedDB is a browser-only primitive - see [Cross-Platform Behavior](#cross-platform-behavior) for what happens off the browser.
|
|
162
|
+
|
|
163
|
+
### `observer` / `useObserver` - watch a path, react to it
|
|
216
164
|
|
|
217
165
|
```javascript
|
|
218
166
|
observer('state.user', (newVal, oldVal) => {
|
|
@@ -220,133 +168,299 @@ observer('state.user', (newVal, oldVal) => {
|
|
|
220
168
|
})
|
|
221
169
|
```
|
|
222
170
|
|
|
223
|
-
|
|
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.
|
|
224
172
|
|
|
225
|
-
|
|
226
|
-
import { useObserver, state } from 'memorio'
|
|
173
|
+
### `dispatch` - the event bus underneath it all
|
|
227
174
|
|
|
228
|
-
|
|
229
|
-
|
|
175
|
+
```javascript
|
|
176
|
+
memorio.dispatch.listen('state.user', (event) => console.debug(event.detail))
|
|
177
|
+
memorio.dispatch.set('state.user', { detail: { name: 'Sara' } })
|
|
178
|
+
```
|
|
230
179
|
|
|
231
|
-
|
|
180
|
+
### `devtools` - see everything, instantly
|
|
232
181
|
|
|
233
|
-
|
|
234
|
-
|
|
182
|
+
```javascript
|
|
183
|
+
memorio.devtools.inspect()
|
|
184
|
+
memorio.devtools.stats()
|
|
185
|
+
memorio.devtools.exportData()
|
|
235
186
|
```
|
|
236
187
|
|
|
237
|
-
|
|
188
|
+
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.
|
|
189
|
+
|
|
190
|
+
### `logger` - a black box for every change
|
|
238
191
|
|
|
239
192
|
```javascript
|
|
240
|
-
|
|
241
|
-
memorio.
|
|
242
|
-
|
|
243
|
-
})
|
|
193
|
+
memorio.logger.configure({ enabled: true, logToConsole: true })
|
|
194
|
+
memorio.logger.getHistory()
|
|
195
|
+
```
|
|
244
196
|
|
|
245
|
-
|
|
246
|
-
memorio.dispatch.set('state.user', { detail: { name: 'state.user' } })
|
|
197
|
+
> 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).
|
|
247
198
|
|
|
248
|
-
|
|
249
|
-
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Typed Stores
|
|
202
|
+
|
|
203
|
+
`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.
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import 'memorio'
|
|
207
|
+
|
|
208
|
+
interface AppState {
|
|
209
|
+
user: { name: string; age: number; email: string }
|
|
210
|
+
theme: 'light' | 'dark'
|
|
211
|
+
items: string[]
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const app = memorio.typed<AppState>()
|
|
215
|
+
|
|
216
|
+
// Type-checked at compile time:
|
|
217
|
+
app.user = { name: 'Sara', age: 30, email: 'sara@test.com' }
|
|
218
|
+
app.theme = 'dark'
|
|
219
|
+
|
|
220
|
+
// TypeScript errors:
|
|
221
|
+
// app.user = { name: 42 } // age missing, name wrong type
|
|
222
|
+
// app.theme = 'purple' // not a valid literal
|
|
250
223
|
```
|
|
251
224
|
|
|
252
|
-
|
|
225
|
+
The same data is accessible via the global `state` proxy — `app` and `state` are identical instances:
|
|
253
226
|
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
memorio.devtools.clear('state')
|
|
258
|
-
memorio.devtools.exportData() // JSON snapshot
|
|
259
|
-
$state // console shortcut -> globalThis.state
|
|
227
|
+
```typescript
|
|
228
|
+
state.user = { name: 'Sara', age: 30, email: 'sara@test.com' }
|
|
229
|
+
app.user.name // 'Sara' — same Proxy
|
|
260
230
|
```
|
|
261
231
|
|
|
262
|
-
|
|
232
|
+
For runtime safety (rejecting invalid values even when TypeScript isn't checking), combine with [Schema Validation](#schema-validation) below.
|
|
263
233
|
|
|
264
|
-
|
|
234
|
+
See [Typed Stores docs](markdown/TYPED.md) for full examples.
|
|
265
235
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
memorio.
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Schema Validation
|
|
239
|
+
|
|
240
|
+
`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.
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
import 'memorio'
|
|
244
|
+
|
|
245
|
+
memorio.registerSchema('user', {
|
|
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
|
+
})
|
|
254
|
+
|
|
255
|
+
state.user = { name: 'Sara', email: 'sara@test.com', age: 30 } // accepted
|
|
256
|
+
state.user = { name: 'Sara' } // rejected: missing 'email'
|
|
257
|
+
state.user = { name: 42 } // rejected: wrong type
|
|
271
258
|
```
|
|
272
259
|
|
|
273
|
-
|
|
260
|
+
Custom validator functions are also supported:
|
|
274
261
|
|
|
275
|
-
|
|
262
|
+
```typescript
|
|
263
|
+
memorio.registerSchema('theme', (val) => {
|
|
264
|
+
return val === 'light' || val === 'dark'
|
|
265
|
+
? true
|
|
266
|
+
: 'theme must be "light" or "dark"'
|
|
267
|
+
})
|
|
276
268
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
memorio.isDeno() // true in Deno
|
|
281
|
-
memorio.isEdge() // true in Cloudflare Workers, Vercel Edge
|
|
269
|
+
state.theme = 'purple' // rejected: "theme must be light or dark"
|
|
270
|
+
state.theme = 'dark' // accepted
|
|
271
|
+
```
|
|
282
272
|
|
|
283
|
-
|
|
284
|
-
|
|
273
|
+
Manual validation without writing:
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
memorio.validate('user', { name: 'Sara', email: 'sara@test.com' })
|
|
277
|
+
// { valid: true }
|
|
278
|
+
|
|
279
|
+
memorio.validate('user', { name: 'Sara' })
|
|
280
|
+
// { valid: false, errors: ["user: missing required property 'email'"] }
|
|
285
281
|
```
|
|
286
282
|
|
|
287
|
-
|
|
283
|
+
Manage registered schemas:
|
|
288
284
|
|
|
289
285
|
```typescript
|
|
290
|
-
|
|
286
|
+
memorio.listSchemas() // ['user', 'theme']
|
|
287
|
+
memorio.unregisterSchema('theme') // removes the validator
|
|
291
288
|
```
|
|
292
289
|
|
|
290
|
+
**Typed + Schema** — combine both for full safety:
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
interface AppState {
|
|
294
|
+
user: { name: string; email: string }
|
|
295
|
+
theme: 'light' | 'dark'
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const app = memorio.typed<AppState>()
|
|
299
|
+
|
|
300
|
+
memorio.registerSchema('user', {
|
|
301
|
+
type: 'object',
|
|
302
|
+
required: ['name', 'email'],
|
|
303
|
+
properties: {
|
|
304
|
+
name: { type: 'string', min: 1 },
|
|
305
|
+
email: { type: 'string', pattern: /^[^@]+@[^@]+$/ }
|
|
306
|
+
}
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
app.user = { names: 'Sara' } // ❌ TS: wrong shape
|
|
310
|
+
app.user = { name: '', email: '' } // ❌ TS passes, ❌ runtime: name too short
|
|
311
|
+
app.user = { name: 'Sara', email: 'sara@test.com' } // ✅ both pass
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
See [Schema Validation docs](markdown/SCHEMA.md) for the full schema definition reference.
|
|
315
|
+
|
|
293
316
|
---
|
|
294
317
|
|
|
295
|
-
##
|
|
318
|
+
## Memory System
|
|
296
319
|
|
|
297
|
-
|
|
298
|
-
// Create isolated context
|
|
299
|
-
const ctx = memorio.createContext('tenant-123')
|
|
320
|
+
`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.
|
|
300
321
|
|
|
301
|
-
|
|
302
|
-
ctx.state.user = { name: 'Isolated' }
|
|
303
|
-
ctx.store.set('settings', { theme: 'dark' })
|
|
304
|
-
ctx.session.set('token', 'abc123')
|
|
322
|
+
### Core API
|
|
305
323
|
|
|
306
|
-
|
|
307
|
-
|
|
324
|
+
```ts
|
|
325
|
+
// remember(key, value, options)
|
|
326
|
+
await memorio.memory.remember('user.language', 'Italian', {
|
|
327
|
+
type: 'preference',
|
|
328
|
+
confidence: 0.92,
|
|
329
|
+
scope: 'local',
|
|
330
|
+
ttl: null, // never expires
|
|
331
|
+
tags: ['ui', 'user'],
|
|
332
|
+
source: 'conversation'
|
|
333
|
+
})
|
|
308
334
|
|
|
309
|
-
//
|
|
310
|
-
memorio.
|
|
311
|
-
|
|
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')
|
|
312
344
|
```
|
|
313
345
|
|
|
314
|
-
|
|
346
|
+
### Scopes
|
|
315
347
|
|
|
316
|
-
|
|
317
|
-
|
|
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
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
// context() — returns ranked, relevant memories
|
|
361
|
+
const context = await memorio.memory.context({
|
|
362
|
+
tags: 'user',
|
|
363
|
+
types: ['preference', 'decision'],
|
|
364
|
+
minConfidence: 0.7,
|
|
365
|
+
maxEntries: 10
|
|
366
|
+
})
|
|
367
|
+
// → [{ key, value, relevance, age, confidence, ... }]
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Memory Entry Model
|
|
371
|
+
|
|
372
|
+
Each memory is stored as a structured entry:
|
|
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
|
+
}
|
|
318
390
|
```
|
|
319
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
|
+
|
|
320
404
|
---
|
|
321
405
|
|
|
322
|
-
##
|
|
406
|
+
## Cross-Platform Behavior
|
|
323
407
|
|
|
324
|
-
|
|
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:
|
|
325
409
|
|
|
326
|
-
|
|
|
410
|
+
| API | Browser | Node.js | Deno | Edge / Workers |
|
|
327
411
|
|---|---|---|---|---|
|
|
328
|
-
| `state` |
|
|
412
|
+
| `state` | Proxy in memory | Proxy in memory | Proxy in memory | Proxy in memory |
|
|
329
413
|
| `observer` / `useObserver` | ✅ | ✅ | ✅ | ✅ |
|
|
330
|
-
| `cache` | ✅ | ✅ | ✅ | ✅ |
|
|
331
|
-
| `store` | localStorage |
|
|
332
|
-
| `session` | sessionStorage |
|
|
333
|
-
| `idb` | IndexedDB | ❌ | ❌ | ⚠️ |
|
|
414
|
+
| `cache` | ✅ in memory | ✅ in memory | ✅ in memory | ✅ in memory |
|
|
415
|
+
| `store` | `localStorage` | `Map` fallback - **not durable across restarts** | `Map` fallback - not durable | `localStorage` where available, else `Map` |
|
|
416
|
+
| `session` | `sessionStorage` | `Map` fallback - not durable | `Map` fallback - not durable | `sessionStorage` where available, else `Map` |
|
|
417
|
+
| `idb` | ✅ `IndexedDB` | ❌ not available | ❌ not available | ⚠️ check `getCapabilities()` |
|
|
334
418
|
| `devtools` | ✅ | ❌ | ❌ | ⚠️ |
|
|
335
419
|
|
|
336
|
-
|
|
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()
|
|
427
|
+
|
|
428
|
+
memorio.getCapabilities()
|
|
429
|
+
// { platform: 'browser', hasLocalStorage: true, hasIndexedDB: true, ... }
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Check `getCapabilities()` before leaning on `idb` or persistent `store`/`session` in code that might run on more than one platform.
|
|
337
433
|
|
|
338
434
|
---
|
|
339
435
|
|
|
340
|
-
##
|
|
436
|
+
## Context Isolation (multi-tenant)
|
|
437
|
+
|
|
438
|
+
```javascript
|
|
439
|
+
const ctx = memorio.createContext('tenant-123')
|
|
440
|
+
|
|
441
|
+
ctx.state.user = { name: 'Isolated' }
|
|
442
|
+
ctx.store.set('settings', { theme: 'dark' })
|
|
443
|
+
|
|
444
|
+
console.debug(state.user) // undefined - separate namespace
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
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.
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## Security
|
|
452
|
+
|
|
453
|
+
- Zero production dependencies.
|
|
454
|
+
- No `eval`, no dynamic code execution, no bundled telemetry.
|
|
455
|
+
- Session IDs generated via `crypto.randomUUID`.
|
|
456
|
+
- Inputs validated, keys sanitized, errors caught at module boundaries.
|
|
457
|
+
|
|
458
|
+
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.
|
|
341
459
|
|
|
342
|
-
-
|
|
343
|
-
- NIST & NSA aligned — enterprise-grade security standards
|
|
344
|
-
- No `eval`, no obfuscation, no hardcoded secrets
|
|
345
|
-
- All inputs validated, keys sanitized, errors caught
|
|
346
|
-
- Secure random session IDs via `crypto.randomUUID`
|
|
460
|
+
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.
|
|
347
461
|
|
|
348
462
|
---
|
|
349
463
|
|
|
350
|
-
##
|
|
464
|
+
## License
|
|
351
465
|
|
|
352
466
|
MIT © [Dario Passariello](https://dario.passariello.ca)
|