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/llms.txt CHANGED
@@ -8,6 +8,8 @@
8
8
  npm i memorio
9
9
  ```
10
10
 
11
+ > **For AI agents generating code against this library:** default `state` is a shared, global namespace unless a context is explicitly created with `memorio.createContext(id)`. Do not assume per-request or per-tab isolation is automatic - see [Session & Context Isolation](#session--context-isolation) before generating server-side code that handles more than one user/request.
12
+
11
13
  ## Core Concepts
12
14
 
13
15
  Memorio provides 6 storage modules plus utilities:
@@ -15,11 +17,11 @@ Memorio provides 6 storage modules plus utilities:
15
17
  | Module | Purpose | Persistence |
16
18
  |--------|---------|-------------|
17
19
  | `state` | Reactive, volatile state | In-memory (resets on refresh) |
18
- | `store` | localStorage persistence | Survives browser refresh |
19
- | `session` | sessionStorage | Dies with browser tab |
20
+ | `store` | localStorage persistence | Survives browser refresh; falls back to non-durable in-memory `Map` in Node.js/Deno |
21
+ | `session` | sessionStorage | Dies with browser tab; falls back to non-durable in-memory `Map` in Node.js/Deno |
20
22
  | `cache` | In-memory cache | Fastest read, no persistence |
21
- | `idb` | IndexedDB | Structured, async, persistent (browser-only) |
22
- | `observer` | Object watcher | Legacy |
23
+ | `idb` | IndexedDB | Structured, async, persistent (browser-only - disabled in Node.js/Deno) |
24
+ | `observer` | Object watcher | Legacy; string-based paths, not statically checked against `state`'s shape |
23
25
  | `useObserver` | React hook | Auto-discovery of state paths |
24
26
 
25
27
  ## Quick Start
@@ -40,7 +42,7 @@ useObserver(
40
42
 
41
43
  ## API Reference
42
44
 
43
- ### `state` Reactive State
45
+ ### `state` - Reactive State
44
46
 
45
47
  Global, Proxy-based, reactive state management.
46
48
 
@@ -61,21 +63,23 @@ state.remove('items')
61
63
 
62
64
  // Clear all
63
65
  state.removeAll()
64
-
65
- // Lock/unlock for frozen objects
66
- state.lock() // freeze everything
67
- state.unlock() // unfreeze
68
66
  ```
69
67
 
68
+ **Locking - VERIFY BEFORE PUBLISHING:** this library's own documents currently disagree on scope.
69
+
70
+ - One source describes **per-key** locking: `state.config.lock()` freezes only the `config` key; other keys remain writable.
71
+ - Another source describes **global** locking: `state.lock()` / `state.unlock()` freezing the entire `state` object at once.
72
+
73
+ These are materially different behaviors - confirm against the actual source which one (or both, with distinct method names) is implemented, then replace this note with the real signature(s). Do not ship a docs update, or generate code against this section, until this is resolved.
74
+
70
75
  **Features:**
71
76
  - Automatic path tracking via `__path` property
72
77
  - Nested proxy support
73
- - Lock/unlock per-key or globally
74
78
  - Auto-dispatches events on changes
75
79
 
76
- ### `store` localStorage Persistence
80
+ ### `store` - localStorage Persistence
77
81
 
78
- Persistent storage that survives browser refresh.
82
+ Persistent storage that survives browser refresh. On Node.js/Deno this falls back to an in-memory `Map` that does **not** survive a process restart - treat it as a same-shape cache there, not durable storage.
79
83
 
80
84
  ```javascript
81
85
  // Set
@@ -89,15 +93,11 @@ store.list() // { preferences: { theme: 'dark' } }
89
93
 
90
94
  // Remove
91
95
  store.remove('preferences')
92
-
93
- // Delete (alias for remove)
94
- store.delete('preferences')
96
+ store.delete('preferences') // alias
95
97
 
96
98
  // Clear all memorio items
97
99
  store.removeAll()
98
-
99
- // Clear all (alias for removeAll)
100
- store.clearAll()
100
+ store.clearAll() // alias
101
101
 
102
102
  // Check size
103
103
  console.debug(store.size(), 'chars stored')
@@ -105,13 +105,15 @@ console.debug(store.size(), 'chars stored')
105
105
  // Check if persistent (real localStorage vs memory fallback)
106
106
  console.debug(store.isPersistent) // true → real localStorage
107
107
 
108
- // Estimate quota usage (returns [0, 0] for localStorage)
108
+ // Estimate quota usage
109
109
  await store.quota() // [usage, quota] in KB
110
110
  ```
111
111
 
112
- ### `session` sessionStorage
112
+ > **`store.quota()` currently returns `[0, 0]` for the `localStorage` backend** - it is not a real usage reading for that backend, it's a placeholder. Don't use it to make capacity decisions until it's implemented for `localStorage`; it may be meaningful for other backends (e.g. `idb`), but confirm before relying on it there too.
113
+
114
+ ### `session` - sessionStorage
113
115
 
114
- Storage that dies when browser tab closes.
116
+ Storage that dies when browser tab closes. Same Node.js/Deno `Map` fallback caveat as `store` applies here.
115
117
 
116
118
  ```javascript
117
119
  // Set
@@ -125,15 +127,11 @@ session.list() // { token: 'user-abc-123' }
125
127
 
126
128
  // Remove
127
129
  session.remove('token')
128
-
129
- // Delete (alias for remove)
130
- session.delete('token')
130
+ session.delete('token') // alias
131
131
 
132
132
  // Clear all
133
133
  session.removeAll()
134
-
135
- // Clear all (alias for removeAll)
136
- session.clearAll()
134
+ session.clearAll() // alias
137
135
 
138
136
  // Check size
139
137
  console.debug(session.size(), 'chars stored')
@@ -142,7 +140,7 @@ console.debug(session.size(), 'chars stored')
142
140
  console.debug(session.isPersistent)
143
141
  ```
144
142
 
145
- ### `cache` In-Memory Cache
143
+ ### `cache` - In-Memory Cache
146
144
 
147
145
  Fastest possible read, data lost on refresh.
148
146
 
@@ -161,9 +159,7 @@ cache.remove('temp')
161
159
 
162
160
  // Clear all
163
161
  cache.clear()
164
-
165
- // Remove all (alias for clear)
166
- cache.removeAll()
162
+ cache.removeAll() // alias
167
163
 
168
164
  // Direct access (also works)
169
165
  cache['myKey'] = value
@@ -171,9 +167,9 @@ const value = cache['myKey']
171
167
  delete cache['myKey']
172
168
  ```
173
169
 
174
- ### `idb` IndexedDB
170
+ ### `idb` - IndexedDB
175
171
 
176
- Structured, persistent, async database (browser-only).
172
+ Structured, persistent, async database (browser-only). **Disabled in Node.js/Deno** - calls will warn and no-op; use `store` or `session` there instead.
177
173
 
178
174
  ```javascript
179
175
  // Create database
@@ -200,24 +196,21 @@ const exists = await idb.db.exist('my-db')
200
196
  // Delete database
201
197
  await idb.db.delete('my-db')
202
198
 
203
- // Get database size
199
+ // Get database / table size
204
200
  const size = await idb.db.size('my-db')
205
-
206
- // Get table size
207
201
  const tableSize = await idb.table.size('my-db', 'users')
208
202
 
209
- // Check support
210
- idb.db.support() // true if IndexedDB available
211
-
212
- // Check quota
213
- const [usage, quota] = await idb.db.quota()
203
+ // Check support before calling anything else
204
+ if (idb.db.support()) {
205
+ const [usage, quota] = await idb.db.quota()
206
+ }
214
207
  ```
215
208
 
216
- **Note:** In Node.js/Deno, `idb` is disabled with a warning. Use `store` or `session` instead.
209
+ Always guard `idb` calls with `idb.db.support()` (or `memorio.getCapabilities().hasIndexedDB`) in code that might run outside a browser - don't rely on the no-op warning alone.
217
210
 
218
- ### `observer` Object Watcher
211
+ ### `observer` - Object Watcher
219
212
 
220
- Legacy observer API. Use `useObserver` instead.
213
+ Legacy observer API. Prefer `useObserver` in React code.
221
214
 
222
215
  ```javascript
223
216
  // Listen to state changes
@@ -238,7 +231,9 @@ console.debug(observer.list)
238
231
  observer.removeAll()
239
232
  ```
240
233
 
241
- ### `useObserver` React Hook
234
+ > Paths are plain strings and are not checked against `state`'s actual shape at compile time or at registration time. A typo or a later rename of the corresponding `state` key will fail silently - the observer simply never fires again.
235
+
236
+ ### `useObserver` - React Hook
242
237
 
243
238
  Primary way to observe state changes in React components.
244
239
 
@@ -266,67 +261,48 @@ function Counter() {
266
261
 
267
262
  **Features:**
268
263
  - Auto-discovery of state paths during render
269
- - Returns cleanup function to stop monitoring
264
+ - Returns a cleanup function to stop monitoring
270
265
  - Supports both Proxy objects and string paths
271
266
 
272
- ### `devtools` Inspection Tools
267
+ ### `devtools` - Inspection Tools
273
268
 
274
- Inspect all memorio data in console.
269
+ Browser-only (see [Cross-Platform Support](#cross-platform-support)); no-ops or unavailable in Node.js/Deno.
275
270
 
276
271
  ```javascript
277
- // Pretty-print state, store, session, cache
278
- memorio.devtools.inspect()
279
-
280
- // Get stats
281
- memorio.devtools.stats() // { stateKeys, storeKeys, sessionKeys, ... }
282
-
283
- // Clear specific module
272
+ memorio.devtools.inspect() // pretty-print state, store, session, cache
273
+ memorio.devtools.stats() // { stateKeys, storeKeys, sessionKeys, ... }
284
274
  memorio.devtools.clear('state')
285
-
286
- // Clear all modules
287
275
  memorio.devtools.clearAll()
288
-
289
- // Export all data as JSON
290
276
  memorio.devtools.exportData()
291
-
292
- // Import data from JSON
293
277
  memorio.devtools.importData(jsonString)
294
-
295
- // Show help
296
278
  memorio.devtools.help()
297
279
 
298
280
  // Console shortcuts
299
- $state // same as globalThis.state
300
- $store // same as globalThis.store
301
- $session // same as globalThis.session
302
- $cache // same as globalThis.cache
281
+ $state // globalThis.state
282
+ $store // globalThis.store
283
+ $session // globalThis.session
284
+ $cache // globalThis.cache
303
285
  ```
304
286
 
305
- ### `logger` Change Tracking
287
+ ### `logger` - Change Tracking
306
288
 
307
- Track every state change with timestamps.
289
+ Records every write it's configured to track, with timestamps - including whatever values you pass in.
308
290
 
309
291
  ```javascript
310
- // Configure logger
311
292
  memorio.logger.configure({
312
293
  enabled: true,
313
294
  logToConsole: true,
314
295
  modules: ['state', 'store', 'session', 'cache', 'idb']
315
296
  })
316
297
 
317
- // Get history
318
- memorio.logger.getHistory() // [{ timestamp, module, action, path, value }, ...]
319
-
320
- // Get stats
321
- memorio.logger.getStats() // { total, state, store, session, cache, idb, set, get, ... }
322
-
323
- // Clear history
298
+ memorio.logger.getHistory() // [{ timestamp, module, action, path, value }, ...]
299
+ memorio.logger.getStats() // { total, state, store, session, cache, idb, set, get, ... }
324
300
  memorio.logger.clearHistory()
325
-
326
- // Export logs
327
- memorio.logger.exportLogs() // JSON string of all history
301
+ memorio.logger.exportLogs() // JSON string of all history
328
302
  ```
329
303
 
304
+ > The `value` field in each history entry is whatever was written - tokens, PII, anything. Don't enable `logger` unconditionally in production paths that handle sensitive data, and don't wire `exportLogs()` output anywhere it could leak (analytics, error reporters, support tooling) without redaction.
305
+
330
306
  ## Platform Detection
331
307
 
332
308
  Access via `memorio.*` after `import 'memorio'`:
@@ -339,14 +315,10 @@ memorio.isEdge() // true in Cloudflare Workers, Vercel Edge
339
315
 
340
316
  const caps = memorio.getCapabilities()
341
317
  // { platform: 'browser', hasLocalStorage: true, hasIndexedDB: true, sessionId: 'uuid', ... }
342
-
343
- // Create isolated context
344
- memorio.createContext('tenant-name')
345
- memorio.listContexts()
346
- memorio.deleteContext('context-id')
347
- memorio.isolate('tenant-name') // alias for createContext
348
318
  ```
349
319
 
320
+ Prefer checking `getCapabilities()` over assuming a platform from context - especially before calling `idb` or relying on `store`/`session` durability.
321
+
350
322
  ## Cross-Platform Support
351
323
 
352
324
  | Module | Browser | Node.js | Deno | Edge/Workers |
@@ -354,32 +326,135 @@ memorio.isolate('tenant-name') // alias for createContext
354
326
  | `state` | ✅ | ✅ | ✅ | ✅ |
355
327
  | `observer` / `useObserver` | ✅ | ✅ | ✅ | ✅ |
356
328
  | `cache` | ✅ | ✅ | ✅ | ✅ |
357
- | `store` | ✅ localStorage | ⚠️ memory | ⚠️ memory | ✅ localStorage |
358
- | `session` | ✅ sessionStorage | ⚠️ memory | ⚠️ memory | ✅ sessionStorage |
359
- | `idb` | ✅ IndexedDB | ❌ | ❌ | ⚠️ |
329
+ | `store` | ✅ `localStorage` | ⚠️ `Map`, not durable | ⚠️ `Map`, not durable | ✅ `localStorage` where available, else `Map` |
330
+ | `session` | ✅ `sessionStorage` | ⚠️ `Map`, not durable | ⚠️ `Map`, not durable | ✅ `sessionStorage` where available, else `Map` |
331
+ | `idb` | ✅ `IndexedDB` | ❌ disabled | ❌ disabled | ⚠️ check `getCapabilities()` |
360
332
  | `devtools` | ✅ | ❌ | ❌ | ⚠️ |
361
333
 
362
- **Note:** `store` and `session` fall back to in-memory `Map` in Node.js/Deno.
334
+ ## Session & Context Isolation
363
335
 
364
- ## Session Isolation
336
+ By default, `state` is a **shared global namespace** - a value set in one place is visible everywhere else that reads `state` in the same process. There is no automatic per-tab or per-request isolation of `state` itself.
365
337
 
366
- Each browser tab and server request gets an isolated namespace automatically via session IDs.
338
+ To isolate a slice of state (e.g. per tenant, per request), create an explicit context:
367
339
 
368
340
  ```javascript
369
- // Create isolated context
370
- memorio.createContext('tenant-name')
341
+ const ctx = memorio.createContext('tenant-name')
342
+ ctx.state.user = { name: 'Isolated' }
343
+
344
+ console.debug(state.user) // undefined - separate namespace from ctx.state
345
+
371
346
  memorio.listContexts()
372
347
  memorio.deleteContext('context-id')
373
348
  memorio.isolate('tenant-name') // alias for createContext
374
349
  ```
375
350
 
351
+ Isolation is implemented as a **key-prefix convention** inside the same underlying storage, not a hard memory or process boundary. In a shared Node.js process or an edge isolate that may be reused across requests:
352
+
353
+ - generate context IDs from trusted server-side data, never directly from client-controlled input, to prevent collisions or spoofing;
354
+ - don't treat this as your only isolation layer for data that must not cross tenants - enforce that at the process/request level as well.
355
+
356
+ `getCapabilities().sessionId` provides a per-session identifier for browser contexts but is not itself an isolation mechanism - use `createContext` for that.
357
+
358
+ ### `memorio.typed<T>()` - Typed Store (compile-time safety)
359
+
360
+ Returns the global `state` proxy cast to type `T`. The same Proxy instance — no overhead. Use for TypeScript autocomplete and static type checking.
361
+
362
+ ```javascript
363
+ const app = memorio.typed<AppState>()
364
+ app.user = { name: 'Sara', age: 30 } // type-checked
365
+ app.user = { name: 42 } // ❌ compile error
366
+ ```
367
+
368
+ ### `memorio.registerSchema()` - Schema Validation (runtime safety)
369
+
370
+ Register validators for state paths. Writes that violate a schema are rejected before being stored.
371
+
372
+ ```javascript
373
+ memorio.registerSchema('user', {
374
+ type: 'object',
375
+ required: ['name', 'email'],
376
+ properties: {
377
+ name: { type: 'string', min: 1 },
378
+ email: { type: 'string', pattern: /^[^@]+@[^@]+$/ }
379
+ }
380
+ })
381
+
382
+ state.user = { name: 'Sara' } // rejected: missing 'email'
383
+ state.user = { name: 'Sara', email: 'sara@test.com' } // accepted
384
+ ```
385
+
386
+ Custom validators:
387
+
388
+ ```javascript
389
+ memorio.registerSchema('theme', (val) =>
390
+ val === 'light' || val === 'dark' ? true : 'must be light or dark'
391
+ )
392
+ ```
393
+
394
+ Manual validation and management:
395
+
396
+ ```javascript
397
+ memorio.validate('user', value) // { valid: true } or { valid: false, errors: [...] }
398
+ memorio.listSchemas() // ['user', 'theme']
399
+ memorio.unregisterSchema('theme') // removes validator
400
+ ```
401
+
402
+ Combine `typed<T>()` + `registerSchema()` for both compile-time and runtime safety.
403
+
404
+ ### `memorio.snapshot()` / `memorio.diff()` - Time Travel
405
+
406
+ ```javascript
407
+ memorio.enableHistory()
408
+
409
+ const snap = memorio.snapshot()
410
+ state.user.name = 'Luigi'
411
+ state.counter = 100
412
+
413
+ const changes = memorio.diff(snap)
414
+ // [{ path: 'user.name', oldValue: 'Sara', newValue: 'Luigi' }, ...]
415
+
416
+ memorio.rollback(snap) // restore to snapshot
417
+ ```
418
+
419
+ ### `memorio.undo()` / `memorio.redo()` - Undo/Redo
420
+
421
+ ```javascript
422
+ state.a = 1; state.b = 2; state.c = 3
423
+ memorio.undo() // removes state.c
424
+ memorio.undo() // removes state.b
425
+ memorio.redo() // restores state.b
426
+ memorio.canUndo() // true
427
+ memorio.canRedo() // true
428
+ ```
429
+
430
+ ### `memorio.trace()` - Mutation Log
431
+
432
+ ```javascript
433
+ memorio.trace()
434
+ // [{ path, action, newValue, previousValue, timestamp }, ...]
435
+ memorio.clearHistory()
436
+ ```
437
+
438
+ ### `memorio.stateKeys()` / `memorio.pathExists()` / `memorio.stateSchema()` - Introspection
439
+
440
+ ```javascript
441
+ memorio.stateKeys() // ['user', 'counter']
442
+ memorio.pathExists('user.name') // true or false
443
+ memorio.stateType('user.name') // 'string'
444
+ memorio.stateSchema() // [{ path, type, defined }, ...] - full tree report
445
+ ```
446
+
447
+ ---
448
+
376
449
  ## Security
377
450
 
378
- - Zero production dependencies
379
- - NIST & NSA aligned security standards
380
- - No `eval`, no obfuscation, no hardcoded secrets
381
- - All inputs validated, keys sanitized
382
- - Secure random session IDs via `crypto.randomUUID`
451
+ - Zero production dependencies.
452
+ - No `eval`, no dynamic code execution, no obfuscation, no hardcoded secrets.
453
+ - Inputs validated, keys sanitized before use.
454
+ - Secure random session IDs via `crypto.randomUUID`.
455
+ - Data in `store`, `session`, and `idb` is **not encrypted** - these are thin wrappers over browser storage APIs that persist data in the clear on the user's device. Add your own encryption layer before storing tokens, secrets, or regulated personal data there.
456
+
457
+ Engineering practices are informed by recognized guidance (e.g. NIST SP 800-53 practices) as a design input - this is a statement about how the library is built, not a compliance certification, and no third-party audit has been performed. Report security issues privately (see `SECURITY.md`) rather than in a public issue.
383
458
 
384
459
  ## License
385
460
 
@@ -387,9 +462,9 @@ MIT © Dario Passariello (BigLogic Inc Canada)
387
462
 
388
463
  ## Utilities
389
464
 
390
- ### `memorio.dispatch` Event Dispatch System
465
+ ### `memorio.dispatch` - Event Dispatch System
391
466
 
392
- Internal event system used by state changes.
467
+ Internal event system used by state changes; also usable directly for custom events.
393
468
 
394
469
  ```javascript
395
470
  // Dispatch a custom event
@@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ---
6
6
 
7
- ## v4.6.1 (Security Patch) - 2026-08-14 CRITICAL Security Fix
7
+ ## v4.6.1 (Security Patch) - 2026-08-14 - CRITICAL Security Fix
8
8
 
9
9
  ### 🔐 Security NOTICE (v4.6.0)
10
10
 
@@ -32,7 +32,7 @@ The hardcoded token `2f398d5d7a734781e96108fdd0dbbabad41ef77a` has been removed
32
32
 
33
33
  ---
34
34
 
35
- ## v4.6.0 (Previous - SECURITY ISSUE) - 2026-08-13 Refactoring & Documentation
35
+ ## v4.6.0 (Previous - SECURITY ISSUE) - 2026-08-13 - Refactoring & Documentation
36
36
 
37
37
  **⚠️ WARNING**: This version had a hardcoded PAT token that was later remediated in v4.6.1**
38
38
 
@@ -75,7 +75,7 @@ The hardcoded token `2f398d5d7a734781e96108fdd0dbbabad41ef77a` has been removed
75
75
 
76
76
  ---
77
77
 
78
- ## v3.0.2 (Current) - 2026-05-19 Bug Fix, Security & API Expansion
78
+ ## v3.0.2 (Current) - 2026-05-19 - Bug Fix, Security & API Expansion
79
79
 
80
80
  ### 🐛 Bug Fixes
81
81
 
@@ -98,9 +98,9 @@ The hardcoded token `2f398d5d7a734781e96108fdd0dbbabad41ef77a` has been removed
98
98
 
99
99
  - Added JSDoc to `observerFunction` in `functions/observer/index.ts`
100
100
  - Added JSDoc to `cache` global in `functions/cache/index.ts`
101
- - `lint` and `tsc` pass clean 0 vulnerabilities from `npm audit`
101
+ - `lint` and `tsc` pass clean - 0 vulnerabilities from `npm audit`
102
102
 
103
- ### 🆕 API New in 3.0.2
103
+ ### 🆕 API - New in 3.0.2
104
104
 
105
105
  | Function | Description |
106
106
  |----------|-------------|
@@ -136,10 +136,10 @@ The hardcoded token `2f398d5d7a734781e96108fdd0dbbabad41ef77a` has been removed
136
136
 
137
137
  ---
138
138
 
139
- ## v2.9.0 2026-05-13
139
+ ## v2.9.0 - 2026-05-13
140
140
 
141
141
  ### Added
142
- - DevTools `memorio.devtools.inspect()`, `stats()`, `exportData()`
142
+ - DevTools - `memorio.devtools.inspect()`, `stats()`, `exportData()`
143
143
  - Logger with full history, stats and export
144
144
  - Platform detection (`isBrowser`, `isNode`, `isDeno`, `isEdge`, `getCapabilities`)
145
145
  - Session isolation via `crypto.randomUUID()`
@@ -154,7 +154,7 @@ The hardcoded token `2f398d5d7a734781e96108fdd0dbbabad41ef77a` has been removed
154
154
 
155
155
  ---
156
156
 
157
- ## v2.5.0 2026-02-17
157
+ ## v2.5.0 - 2026-02-17
158
158
 
159
159
  - Initial release of memorio (state, store, session, cache, idb)
160
160
  - Observer pattern (`observer`)