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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # 🧠 memorio
2
2
 
3
- ![image](https://raw.githubusercontent.com/passariello/container/refs/heads/main/memorio/banner.svg)
3
+ ![banner](https://raw.githubusercontent.com/passariello/container/refs/heads/main/memorio/banner.svg)
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/memorio.svg)](https://npmjs.com/package/memorio)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/memorio.svg)](https://npmjs.com/package/memorio)
@@ -13,197 +13,143 @@
13
13
  ![Tests](https://img.shields.io/badge/tests-101%20passed-green)
14
14
  ![License](https://img.shields.io/badge/License-MIT-gray)
15
15
 
16
- **State + Observer + Store + IDB. One import. Zero config.**
16
+ ### One import. Global state, persistence, and a IndexedDB layer - done.
17
17
 
18
- Memorio is a universal, cross-platform state management library for JavaScript and TypeScript. Reactive state, persistent store, session cache, IndexedDB, observer system, React hook, devtools, and logger — all from one import, zero dependencies.
18
+ ```javascript
19
+ import 'memorio'
19
20
 
20
- ---
21
+ state.user = { name: 'Sara' } // reactive, everywhere, instantly
22
+ ```
21
23
 
22
- ## Why memorio?
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
- | Feature | 🔥 memorio | Redux | Zustand |
25
- |---|---|---|---|
26
- | **Setup** | ✅ **1 import** | ❌ Boilerplate hell | ⚠️ Moderate |
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
- Zero dependencies. Lightweight. One import.
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
- ## 🚀 Features
43
+ ## Is this for you?
43
44
 
44
- | | |
45
- |---|---|
46
- | **`state`** | Reactive, Proxy-based volatile state |
47
- | **`store`** | localStorage persistence (survives refresh) |
48
- | **`session`** | sessionStorage (dies with tab) |
49
- | **`cache`** | In-memory fastest cache |
50
- | **`idb`** | IndexedDB with typed tables, structured and async |
51
- | **`observer`** | Legacy object watcher for vanilla JS |
52
- | **`useObserver`** | React hook with auto-discovery |
53
- | **`dispatch`** | Event system: listen, emit, subscribe |
54
- | **`devtools`** | Inspect everything in console |
55
- | **`logger`** | Auto-log every state change with timestamps |
56
- | **Context isolation** | Per-request / multi-tenant namespace |
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
- ## 📦 Installation
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
- # pnpm
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
- ## 🎯 Quick Start
74
+ ## Quick Start
83
75
 
84
- ### Global style (original)
76
+ ### Global style - the whole point of memorio
85
77
 
86
78
  ```typescript
87
- // import 'memorio' once at your app entry point
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
- () => { console.debug('user changed:', state.user) },
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
- ### Classic `import` style (new)
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
- // ESM
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
- Both styles share the exact same instances. Pick whichever fits your project.
107
+ Two styles, one engine underneath - use whichever reads better in your codebase.
146
108
 
147
109
  ---
148
110
 
149
- ## 📚 API Reference
150
-
151
- ### `state` — Reactive volatile state
111
+ ## API Reference
152
112
 
153
- Global, Proxy-based, reactive. Access anywhere.
113
+ ### `state` - reactive, volatile, Proxy-based
154
114
 
155
115
  ```javascript
156
- // Set
157
116
  state.user = { name: 'Sara', role: 'admin' }
158
- state.items = [1, 2, 3]
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
- // Remove one key
119
+ state.list // ['user', 'items', ...]
167
120
  state.remove('items')
168
-
169
- // Clear all
170
121
  state.removeAll()
171
122
 
172
- // Lock/unlock (prevents modifications)
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 // Error: state 'config' is locked
126
+ state.config.maxUsers = 200 // throws: state 'config' is locked
176
127
  state.config.unlock()
177
128
  ```
178
129
 
179
- ### `store` Survives refresh
130
+ ### `store` - the value that survives a refresh
180
131
 
181
132
  ```javascript
182
133
  store.set('preferences', { theme: 'dark' })
183
- const prefs = store.get('preferences') // { theme: 'dark' } or null
184
- store.remove('preferences')
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` Dies with tab
138
+ ### `session` - lives as long as the tab does
191
139
 
192
140
  ```javascript
193
141
  session.set('token', 'user-abc-123')
194
- const token = session.get('token') // 'user-abc-123' or null
195
- session.removeAll()
142
+ session.get('token')
196
143
  ```
197
144
 
198
- ### `cache` In-memory, disappears on refresh
145
+ ### `cache` - the fastest thing you own, gone on refresh
199
146
 
200
147
  ```javascript
201
148
  cache.set('temp', computeExpensiveResult())
202
- const result = cache.get('temp') // undefined or the value
203
- cache.clear() // empty it all
149
+ cache.get('temp')
204
150
  ```
205
151
 
206
- ### `idb` Structured & typed
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
- ### `observer` Object watcher (legacy)
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
- ### `useObserver` React observer hook
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
- ```jsx
226
- import { useObserver, state } from 'memorio'
173
+ ### `dispatch` - the event bus underneath it all
227
174
 
228
- function Counter() {
229
- const [, forceUpdate] = useReducer(x => x + 1, 0)
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
- useObserver(forceUpdate, [state.counter])
180
+ ### `devtools` - see everything, instantly
232
181
 
233
- return <div>Count: {state.counter}</div>
234
- }
182
+ ```javascript
183
+ memorio.devtools.inspect()
184
+ memorio.devtools.stats()
185
+ memorio.devtools.exportData()
235
186
  ```
236
187
 
237
- ### `dispatch` Event system
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
- // Listen
241
- memorio.dispatch.listen('state.user', (event) => {
242
- console.debug('user changed:', event.detail)
243
- })
193
+ memorio.logger.configure({ enabled: true, logToConsole: true })
194
+ memorio.logger.getHistory()
195
+ ```
244
196
 
245
- // Emit
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
- // Remove
249
- memorio.dispatch.remove('state.user')
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
- ### `devtools` — Inspect everything
225
+ The same data is accessible via the global `state` proxy `app` and `state` are identical instances:
253
226
 
254
- ```javascript
255
- memorio.devtools.inspect() // pretty-prints state, store, session, cache
256
- memorio.devtools.stats() // { stateKeys, storeKeys, sessionKeys, ... }
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
- > 💡 **Browser Extension**: When used with [dphelper-manager](https://chrome.google.com/webstore/detail/dphelper-manager-dev-tool/oppppldaoknfddeikfloonnialijngbk), Memorio's global state is automatically detected and visualized with time-travel debugging.
232
+ For runtime safety (rejecting invalid values even when TypeScript isn't checking), combine with [Schema Validation](#schema-validation) below.
263
233
 
264
- ### `logger` Track every change
234
+ See [Typed Stores docs](markdown/TYPED.md) for full examples.
265
235
 
266
- ```javascript
267
- memorio.logger.configure({ enabled: true, logToConsole: true })
268
- memorio.logger.getHistory() // [{ timestamp, module, action, path, value }, ...]
269
- memorio.logger.getStats() // { total, state, set, get, ... }
270
- memorio.logger.exportLogs() // JSON string of all history
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
- ## 🌍 Platform detection
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
- ```javascript
278
- memorio.isBrowser() // true in Chrome, Firefox, Safari
279
- memorio.isNode() // true in Node.js
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
- const caps = memorio.getCapabilities()
284
- // { platform: 'browser', hasLocalStorage: true, hasIndexedDB: true, ... }
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
- Named exports work too:
283
+ Manage registered schemas:
288
284
 
289
285
  ```typescript
290
- import { isBrowser, isNode, getCapabilities } from 'memorio'
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
- ## 🏢 Context isolation (multi-tenant)
318
+ ## Memory System
296
319
 
297
- ```javascript
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
- // Use context storage (prefix: 'tenant-123-key')
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
- // Context is completely isolated from global state
307
- console.debug(state.user) // undefined
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
- // Manage contexts
310
- memorio.listContexts() // ['tenant-123']
311
- memorio.deleteContext('tenant-123')
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
- Named exports:
346
+ ### Scopes
315
347
 
316
- ```typescript
317
- import { createContext, listContexts, deleteContext, isolate } from 'memorio'
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
- ## 🖥️ Cross-Platform
406
+ ## Cross-Platform Behavior
323
407
 
324
- Memorio runs in every JavaScript environment, with automatic fallbacks.
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
- | Tool | Browser | Node.js | Deno | Edge / Workers |
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 | memory | memory | localStorage |
332
- | `session` | sessionStorage | memory | memory | 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
- > **Why memory fallbacks on the server?** There is no browser. `store` and `session` gracefully fall back to `Map`. You still get the same API. Same `state`, same `cache`, same `useObserver`. No extra config required.
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
- ## 🔒 Security
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
- - Zero production dependencies no supply chain surprises
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
- ## 📄 License
464
+ ## License
351
465
 
352
466
  MIT © [Dario Passariello](https://dario.passariello.ca)