memorio 4.8.0 โ 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 +232 -527
- package/SUMMARY.md +1 -5
- package/index.cjs +84 -152
- package/index.js +44 -107
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/types/env.d.ts +9 -0
- package/types/exports.d.ts +50 -1
- package/types/memorio.d.ts +15 -11
- package/types/memory.d.ts +40 -2
- package/types/schema.d.ts +53 -53
- 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-ATTACHMENT.md +0 -95
- 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/SQLITE.md +0 -181
- package/markdown/STATE.md +0 -153
- package/markdown/STORE.md +0 -164
- package/markdown/SYNC.md +0 -170
- package/markdown/TYPED.md +0 -158
- package/markdown/USEOBSERVER.md +0 -259
package/README.md
CHANGED
|
@@ -1,672 +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
|
-
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.
|
|
45
|
+
## Why memorio
|
|
46
46
|
|
|
47
|
-
|
|
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.
|
|
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.
|
|
51
48
|
|
|
52
|
-
**
|
|
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.
|
|
49
|
+
memorio gives these concerns **one runtime and one mental model** โ without forcing you to use all of it.
|
|
56
50
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
+
```
|
|
82
|
+
|
|
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
|
-
|
|
71
|
-
# Optional - only if you want a local sql.js for `sqlite` (default uses CDN)
|
|
72
|
-
npm i sql.js
|
|
73
87
|
```
|
|
74
88
|
|
|
75
|
-
|
|
89
|
+
Optional peers, only loaded when used:
|
|
76
90
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
import 'memorio' // once, at your entry point
|
|
83
|
-
|
|
84
|
-
state.user = { name: 'Sara', role: 'admin' }
|
|
85
|
-
state.counter++
|
|
86
|
-
|
|
87
|
-
useObserver(
|
|
88
|
-
() => console.debug('user changed:', state.user),
|
|
89
|
-
[state.user]
|
|
90
|
-
)
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### Named imports - same instances, explicit about it
|
|
94
|
-
|
|
95
|
-
```typescript
|
|
96
|
-
import { state, store, session, cache, idb, observer, useObserver, dispatch, memorio } from 'memorio'
|
|
97
|
-
|
|
98
|
-
state.user = { name: 'Sara' }
|
|
99
|
-
store.set('theme', 'dark')
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
```tsx
|
|
103
|
-
function Counter() {
|
|
104
|
-
const [, forceUpdate] = useReducer(x => x + 1, 0)
|
|
105
|
-
useObserver(forceUpdate, [state.counter])
|
|
106
|
-
return <div>Count: {state.counter}</div>
|
|
107
|
-
}
|
|
91
|
+
```bash
|
|
92
|
+
npm i react react-dom # React integration
|
|
93
|
+
npm i sql.js # SQLite engine
|
|
108
94
|
```
|
|
109
95
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
---
|
|
96
|
+
**Zero production dependencies.** See [Security](#security).
|
|
113
97
|
|
|
114
|
-
##
|
|
98
|
+
## Quick start
|
|
115
99
|
|
|
116
|
-
|
|
100
|
+
```ts
|
|
101
|
+
import 'memorio'
|
|
117
102
|
|
|
118
|
-
```javascript
|
|
119
103
|
state.user = { name: 'Sara', role: 'admin' }
|
|
120
104
|
const name = state.user.name
|
|
105
|
+
```
|
|
121
106
|
|
|
122
|
-
|
|
123
|
-
state.remove('items')
|
|
124
|
-
state.removeAll()
|
|
107
|
+
Reactive, in-memory, Proxy-based, globally accessible. Lock a slice you don't want mutated by accident:
|
|
125
108
|
|
|
126
|
-
|
|
109
|
+
```ts
|
|
127
110
|
state.config = { maxUsers: 100 }
|
|
128
111
|
state.config.lock()
|
|
129
|
-
state.config.maxUsers = 200 // throws
|
|
112
|
+
state.config.maxUsers = 200 // throws
|
|
130
113
|
state.config.unlock()
|
|
131
114
|
```
|
|
132
115
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
```javascript
|
|
136
|
-
store.set('preferences', { theme: 'dark' })
|
|
137
|
-
store.get('preferences') // { theme: 'dark' } or null
|
|
138
|
-
store.isPersistent // true when backed by real localStorage
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### `session` - lives as long as the tab does
|
|
142
|
-
|
|
143
|
-
```javascript
|
|
144
|
-
session.set('token', 'user-abc-123')
|
|
145
|
-
session.get('token')
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### `cache` - the fastest thing you own, gone on refresh
|
|
149
|
-
|
|
150
|
-
```javascript
|
|
151
|
-
cache.set('temp', computeExpensiveResult())
|
|
152
|
-
cache.get('temp')
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
### `idb` - typed, async, structured tables without the ceremony
|
|
156
|
-
|
|
157
|
-
```javascript
|
|
158
|
-
await idb.db.create('my-db')
|
|
159
|
-
await idb.table.create('my-db', 'users')
|
|
160
|
-
await idb.data.set('my-db', 'users', { id: 1, name: 'Sara' })
|
|
161
|
-
const user = await idb.data.get('my-db', 'users', 1)
|
|
162
|
-
```
|
|
163
|
-
|
|
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
|
|
116
|
+
Prefer explicit imports over the global? Same runtime either way:
|
|
167
117
|
|
|
168
|
-
|
|
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}` })
|
|
118
|
+
```ts
|
|
119
|
+
import { state, store, session, cache, idb, sqlite, memorio } from 'memorio'
|
|
240
120
|
```
|
|
241
121
|
|
|
242
|
-
|
|
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).
|
|
245
|
-
|
|
246
|
-
See [SQLite docs](markdown/SQLITE.md) for the full reference.
|
|
122
|
+
## Observing changes
|
|
247
123
|
|
|
248
|
-
|
|
124
|
+
Reactivity isn't a React add-on โ it's built into the runtime. Watch any state path directly, in any environment:
|
|
249
125
|
|
|
250
|
-
```
|
|
251
|
-
observer('state.user', (
|
|
252
|
-
console.
|
|
126
|
+
```ts
|
|
127
|
+
observer('state.user', (next, previous) => {
|
|
128
|
+
console.log('user changed:', next, previous)
|
|
253
129
|
})
|
|
254
|
-
```
|
|
255
130
|
|
|
256
|
-
|
|
131
|
+
// nested paths work too
|
|
132
|
+
observer('state.user.name', callback)
|
|
133
|
+
```
|
|
257
134
|
|
|
258
|
-
|
|
135
|
+
`dispatch` is the event mechanism underneath it, if you need to hook in lower-level:
|
|
259
136
|
|
|
260
|
-
```
|
|
261
|
-
memorio.dispatch.listen('state.user',
|
|
137
|
+
```ts
|
|
138
|
+
memorio.dispatch.listen('state.user', event => console.debug(event.detail))
|
|
262
139
|
memorio.dispatch.set('state.user', { detail: { name: 'Sara' } })
|
|
263
140
|
```
|
|
264
141
|
|
|
265
|
-
|
|
142
|
+
`observer` paths are runtime strings โ for compiler-checked access, see [typed state](#typed-state--schema-validation).
|
|
266
143
|
|
|
267
|
-
|
|
268
|
-
memorio.devtools.inspect()
|
|
269
|
-
memorio.devtools.stats()
|
|
270
|
-
memorio.devtools.exportData()
|
|
271
|
-
```
|
|
144
|
+
React apps get a dedicated hook, `useObserver` โ see [React integration](#react-integration).
|
|
272
145
|
|
|
273
|
-
|
|
146
|
+
## The layers
|
|
274
147
|
|
|
275
|
-
### `
|
|
148
|
+
### `store` โ persistent key/value
|
|
276
149
|
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
|
|
150
|
+
```ts
|
|
151
|
+
store.set('preferences', { theme: 'dark' })
|
|
152
|
+
const preferences = store.get('preferences')
|
|
280
153
|
```
|
|
281
154
|
|
|
282
|
-
|
|
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
|
-
```
|
|
155
|
+
Backed by `localStorage` in the browser (`store.isPersistent === true`); falls back to memory elsewhere.
|
|
309
156
|
|
|
310
|
-
|
|
157
|
+
### `session` โ follows the tab
|
|
311
158
|
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
app.user.name // 'Sara' โ same Proxy
|
|
159
|
+
```ts
|
|
160
|
+
session.set('token', 'user-abc-123')
|
|
315
161
|
```
|
|
316
162
|
|
|
317
|
-
|
|
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.
|
|
163
|
+
Backed by `sessionStorage`. Good for auth state, wizards, tab-scoped data.
|
|
326
164
|
|
|
327
|
-
|
|
328
|
-
import 'memorio'
|
|
165
|
+
### `cache` โ volatile, fast
|
|
329
166
|
|
|
330
|
-
|
|
331
|
-
|
|
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
|
|
167
|
+
```ts
|
|
168
|
+
cache.set('expensive-result', computeExpensiveResult())
|
|
343
169
|
```
|
|
344
170
|
|
|
345
|
-
|
|
171
|
+
Disappears when the runtime disappears. No persistence guarantee, ever.
|
|
346
172
|
|
|
347
|
-
|
|
348
|
-
memorio.registerSchema('theme', (val) => {
|
|
349
|
-
return val === 'light' || val === 'dark'
|
|
350
|
-
? true
|
|
351
|
-
: 'theme must be "light" or "dark"'
|
|
352
|
-
})
|
|
173
|
+
### `idb` โ durable, structured
|
|
353
174
|
|
|
354
|
-
|
|
355
|
-
|
|
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)
|
|
356
180
|
```
|
|
357
181
|
|
|
358
|
-
|
|
182
|
+
Check before relying on it in portable code: `memorio.getCapabilities()`.
|
|
359
183
|
|
|
360
|
-
|
|
361
|
-
memorio.validate('user', { name: 'Sara', email: 'sara@test.com' })
|
|
362
|
-
// { valid: true }
|
|
184
|
+
### `sqlite` โ a real local SQL engine
|
|
363
185
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
186
|
+
```ts
|
|
187
|
+
await sqlite.ready
|
|
188
|
+
await sqlite.db.create('app')
|
|
367
189
|
|
|
368
|
-
|
|
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'])
|
|
369
194
|
|
|
370
|
-
|
|
371
|
-
memorio.listSchemas() // ['user', 'theme']
|
|
372
|
-
memorio.unregisterSchema('theme') // removes the validator
|
|
195
|
+
const admins = await sqlite.query.select('app', `SELECT * FROM users WHERE role = ?`, ['admin'])
|
|
373
196
|
```
|
|
374
197
|
|
|
375
|
-
**
|
|
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:
|
|
376
199
|
|
|
377
|
-
```
|
|
378
|
-
|
|
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
|
|
200
|
+
```ts
|
|
201
|
+
await sqlite.db.create('app', { persistence: true })
|
|
397
202
|
```
|
|
398
203
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
---
|
|
402
|
-
|
|
403
|
-
## Memory System
|
|
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).
|
|
404
205
|
|
|
405
|
-
`
|
|
206
|
+
### `memory` โ semantic application memory
|
|
406
207
|
|
|
407
|
-
|
|
208
|
+
The layer that makes memorio more than a state manager. Structured memory with type, confidence, TTL, tags, source, scope, and a real lifecycle:
|
|
408
209
|
|
|
409
210
|
```ts
|
|
410
|
-
// remember(key, value, options)
|
|
411
211
|
await memorio.memory.remember('user.language', 'Italian', {
|
|
412
212
|
type: 'preference',
|
|
413
213
|
confidence: 0.92,
|
|
414
214
|
scope: 'local',
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
source: 'conversation'
|
|
215
|
+
tags: ['user', 'ui'],
|
|
216
|
+
source: 'conversation',
|
|
418
217
|
})
|
|
419
218
|
|
|
420
|
-
|
|
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')
|
|
219
|
+
const language = await memorio.memory.recall('user.language')
|
|
429
220
|
```
|
|
430
221
|
|
|
431
|
-
|
|
222
|
+
Updates don't overwrite โ they **supersede**, preserving history:
|
|
432
223
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
| `local` | `localStorage` | โ
| โ
| ~10MB |
|
|
438
|
-
| `durable` | `IndexedDB` | โ
| โ
| ~1GB+ |
|
|
439
|
-
|
|
440
|
-
Default scope is `local` for values โค100KB, `durable` for larger values.
|
|
224
|
+
```ts
|
|
225
|
+
await memorio.memory.update('user.language', 'English', { confidence: 0.95 })
|
|
226
|
+
// old entry โ status: 'superseded' | new entry โ status: 'active'
|
|
227
|
+
```
|
|
441
228
|
|
|
442
|
-
|
|
229
|
+
Retrieve what's *relevant*, not everything:
|
|
443
230
|
|
|
444
231
|
```ts
|
|
445
|
-
// context() โ returns ranked, relevant memories
|
|
446
232
|
const context = await memorio.memory.context({
|
|
447
233
|
tags: 'user',
|
|
448
234
|
types: ['preference', 'decision'],
|
|
449
235
|
minConfidence: 0.7,
|
|
450
|
-
maxEntries: 10
|
|
236
|
+
maxEntries: 10,
|
|
451
237
|
})
|
|
452
|
-
// โ [{ key, value, relevance, age, confidence, ... }]
|
|
453
238
|
```
|
|
454
239
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
Each memory is stored as a structured entry:
|
|
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.
|
|
458
241
|
|
|
459
|
-
|
|
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
|
-
```
|
|
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.
|
|
476
243
|
|
|
477
|
-
|
|
244
|
+
## React integration
|
|
478
245
|
|
|
479
|
-
|
|
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 |
|
|
246
|
+
React is an integration, not a requirement โ `useObserver` is a thin bridge onto the same `observer` mechanism from above:
|
|
486
247
|
|
|
487
|
-
|
|
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
|
+
}
|
|
254
|
+
```
|
|
488
255
|
|
|
489
|
-
|
|
256
|
+
## Typed state & schema validation
|
|
490
257
|
|
|
491
|
-
|
|
258
|
+
TypeScript types for compile-time safety:
|
|
492
259
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
260
|
+
```ts
|
|
261
|
+
interface AppState {
|
|
262
|
+
user: { name: string; age: number; email: string }
|
|
263
|
+
theme: 'light' | 'dark'
|
|
264
|
+
}
|
|
496
265
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
โโโโโโโโโโดโโโโโโโโโ
|
|
501
|
-
โ Memory Engine โ
|
|
502
|
-
โโโโโโโโโโฌโโโโโโโโโ
|
|
503
|
-
โโโโโโโโโโโโโโผโโโโโโโโโโโโโ
|
|
504
|
-
โผ โผ โผ
|
|
505
|
-
local SQLite cloud
|
|
506
|
-
memory durable sync
|
|
266
|
+
const app = memorio.typed<AppState>()
|
|
267
|
+
app.theme = 'dark'
|
|
268
|
+
app.theme = 'purple' // โ TypeScript error
|
|
507
269
|
```
|
|
508
270
|
|
|
509
|
-
|
|
271
|
+
`app === state` โ same Proxy, no duplicated store.
|
|
510
272
|
|
|
511
|
-
|
|
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):
|
|
273
|
+
Runtime validation for values crossing trust boundaries:
|
|
515
274
|
|
|
516
275
|
```ts
|
|
517
|
-
memorio.
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
276
|
+
memorio.registerSchema('user', {
|
|
277
|
+
type: 'object',
|
|
278
|
+
required: ['name', 'email'],
|
|
279
|
+
properties: {
|
|
280
|
+
name: { type: 'string', min: 1 },
|
|
281
|
+
email: { type: 'string', pattern: /^[^@]+@[^@]+$/ },
|
|
522
282
|
},
|
|
523
|
-
auto: true // auto-replay pending ops on focus/online (default true)
|
|
524
283
|
})
|
|
525
284
|
|
|
526
|
-
|
|
285
|
+
state.user = { name: 'Sara' } // rejected โ missing required "email"
|
|
527
286
|
```
|
|
528
287
|
|
|
529
|
-
|
|
530
|
-
until the provider acknowledges it.
|
|
288
|
+
## Local-first sync
|
|
531
289
|
|
|
532
|
-
|
|
290
|
+
The local application owns its data; the cloud is optional transport.
|
|
533
291
|
|
|
534
|
-
|
|
535
|
-
is written to a local, **namespaced** operation journal:
|
|
292
|
+
memorio syncs **operations** (`remember`, `update`, `forget`, `expire`, `confirm`, `supersede`), not database dumps, via a local journal that survives network failure:
|
|
536
293
|
|
|
537
294
|
```ts
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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,
|
|
302
|
+
})
|
|
544
303
|
```
|
|
545
304
|
|
|
546
|
-
|
|
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":
|
|
305
|
+
Conflict resolution defaults to *higher confidence wins, then more recent `lastConfirmedAt`* โ override it when you need a different rule:
|
|
566
306
|
|
|
567
307
|
```ts
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
|
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
|
+
},
|
|
313
|
+
})
|
|
583
314
|
```
|
|
584
315
|
|
|
585
|
-
|
|
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).
|
|
608
|
-
|
|
609
|
-
---
|
|
610
|
-
|
|
611
|
-
## Cross-Platform Behavior
|
|
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.
|
|
612
317
|
|
|
613
|
-
|
|
318
|
+
## Cross-platform support
|
|
614
319
|
|
|
615
320
|
| API | Browser | Node.js | Deno | Edge / Workers |
|
|
616
|
-
|
|
617
|
-
| `state`
|
|
618
|
-
| `
|
|
619
|
-
| `
|
|
620
|
-
| `
|
|
621
|
-
| `
|
|
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()` |
|
|
624
|
-
| `devtools` | โ
| โ | โ | โ ๏ธ |
|
|
625
|
-
|
|
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.
|
|
627
|
-
|
|
628
|
-
```javascript
|
|
629
|
-
memorio.isBrowser()
|
|
630
|
-
memorio.isNode()
|
|
631
|
-
memorio.isDeno()
|
|
632
|
-
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 |
|
|
633
327
|
|
|
328
|
+
```ts
|
|
634
329
|
memorio.getCapabilities()
|
|
635
|
-
|
|
330
|
+
memorio.isBrowser() / isNode() / isDeno() / isEdge()
|
|
636
331
|
```
|
|
637
332
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
---
|
|
641
|
-
|
|
642
|
-
## Context Isolation (multi-tenant)
|
|
333
|
+
## Security
|
|
643
334
|
|
|
644
|
-
|
|
645
|
-
|
|
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
|
|
646
339
|
|
|
647
|
-
|
|
648
|
-
ctx.store.set('settings', { theme: 'dark' })
|
|
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.
|
|
649
341
|
|
|
650
|
-
|
|
651
|
-
```
|
|
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.
|
|
652
343
|
|
|
653
|
-
|
|
344
|
+
## Honest limitations
|
|
654
345
|
|
|
655
|
-
|
|
346
|
+
We'd rather tell you where the edges are than have you find them in production.
|
|
656
347
|
|
|
657
|
-
|
|
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.
|
|
658
353
|
|
|
659
|
-
|
|
660
|
-
- No `eval`, no dynamic code execution, no bundled telemetry.
|
|
661
|
-
- Session IDs generated via `crypto.randomUUID`.
|
|
662
|
-
- Inputs validated, keys sanitized, errors caught at module boundaries.
|
|
354
|
+
## When to use something else
|
|
663
355
|
|
|
664
|
-
|
|
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.
|
|
665
360
|
|
|
666
|
-
|
|
361
|
+
## Design philosophy
|
|
667
362
|
|
|
668
|
-
|
|
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
|
+
```
|
|
669
374
|
|
|
670
375
|
## License
|
|
671
376
|
|
|
672
|
-
MIT ยฉ
|
|
377
|
+
MIT ยฉ Dario Passariello
|