memorio 4.7.1 → 4.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/llms.txt CHANGED
@@ -8,7 +8,7 @@
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.
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
12
 
13
13
  ## Core Concepts
14
14
 
@@ -20,7 +20,7 @@ Memorio provides 6 storage modules plus utilities:
20
20
  | `store` | localStorage persistence | Survives browser refresh; falls back to non-durable in-memory `Map` in Node.js/Deno |
21
21
  | `session` | sessionStorage | Dies with browser tab; falls back to non-durable in-memory `Map` in Node.js/Deno |
22
22
  | `cache` | In-memory cache | Fastest read, no persistence |
23
- | `idb` | IndexedDB | Structured, async, persistent (browser-only disabled in Node.js/Deno) |
23
+ | `idb` | IndexedDB | Structured, async, persistent (browser-only - disabled in Node.js/Deno) |
24
24
  | `observer` | Object watcher | Legacy; string-based paths, not statically checked against `state`'s shape |
25
25
  | `useObserver` | React hook | Auto-discovery of state paths |
26
26
 
@@ -42,7 +42,7 @@ useObserver(
42
42
 
43
43
  ## API Reference
44
44
 
45
- ### `state` Reactive State
45
+ ### `state` - Reactive State
46
46
 
47
47
  Global, Proxy-based, reactive state management.
48
48
 
@@ -65,21 +65,21 @@ state.remove('items')
65
65
  state.removeAll()
66
66
  ```
67
67
 
68
- **Locking VERIFY BEFORE PUBLISHING:** this library's own documents currently disagree on scope.
68
+ **Locking - VERIFY BEFORE PUBLISHING:** this library's own documents currently disagree on scope.
69
69
 
70
70
  - One source describes **per-key** locking: `state.config.lock()` freezes only the `config` key; other keys remain writable.
71
71
  - Another source describes **global** locking: `state.lock()` / `state.unlock()` freezing the entire `state` object at once.
72
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.
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
74
 
75
75
  **Features:**
76
76
  - Automatic path tracking via `__path` property
77
77
  - Nested proxy support
78
78
  - Auto-dispatches events on changes
79
79
 
80
- ### `store` localStorage Persistence
80
+ ### `store` - localStorage Persistence
81
81
 
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.
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.
83
83
 
84
84
  ```javascript
85
85
  // Set
@@ -109,9 +109,9 @@ console.debug(store.isPersistent) // true → real localStorage
109
109
  await store.quota() // [usage, quota] in KB
110
110
  ```
111
111
 
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.
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
113
 
114
- ### `session` sessionStorage
114
+ ### `session` - sessionStorage
115
115
 
116
116
  Storage that dies when browser tab closes. Same Node.js/Deno `Map` fallback caveat as `store` applies here.
117
117
 
@@ -140,7 +140,7 @@ console.debug(session.size(), 'chars stored')
140
140
  console.debug(session.isPersistent)
141
141
  ```
142
142
 
143
- ### `cache` In-Memory Cache
143
+ ### `cache` - In-Memory Cache
144
144
 
145
145
  Fastest possible read, data lost on refresh.
146
146
 
@@ -167,9 +167,9 @@ const value = cache['myKey']
167
167
  delete cache['myKey']
168
168
  ```
169
169
 
170
- ### `idb` IndexedDB
170
+ ### `idb` - IndexedDB
171
171
 
172
- Structured, persistent, async database (browser-only). **Disabled in Node.js/Deno** calls will warn and no-op; use `store` or `session` there instead.
172
+ Structured, persistent, async database (browser-only). **Disabled in Node.js/Deno** - calls will warn and no-op; use `store` or `session` there instead.
173
173
 
174
174
  ```javascript
175
175
  // Create database
@@ -206,9 +206,9 @@ if (idb.db.support()) {
206
206
  }
207
207
  ```
208
208
 
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.
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.
210
210
 
211
- ### `observer` Object Watcher
211
+ ### `observer` - Object Watcher
212
212
 
213
213
  Legacy observer API. Prefer `useObserver` in React code.
214
214
 
@@ -231,9 +231,9 @@ console.debug(observer.list)
231
231
  observer.removeAll()
232
232
  ```
233
233
 
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.
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
235
 
236
- ### `useObserver` React Hook
236
+ ### `useObserver` - React Hook
237
237
 
238
238
  Primary way to observe state changes in React components.
239
239
 
@@ -264,7 +264,7 @@ function Counter() {
264
264
  - Returns a cleanup function to stop monitoring
265
265
  - Supports both Proxy objects and string paths
266
266
 
267
- ### `devtools` Inspection Tools
267
+ ### `devtools` - Inspection Tools
268
268
 
269
269
  Browser-only (see [Cross-Platform Support](#cross-platform-support)); no-ops or unavailable in Node.js/Deno.
270
270
 
@@ -284,9 +284,9 @@ $session // globalThis.session
284
284
  $cache // globalThis.cache
285
285
  ```
286
286
 
287
- ### `logger` Change Tracking
287
+ ### `logger` - Change Tracking
288
288
 
289
- Records every write it's configured to track, with timestamps including whatever values you pass in.
289
+ Records every write it's configured to track, with timestamps - including whatever values you pass in.
290
290
 
291
291
  ```javascript
292
292
  memorio.logger.configure({
@@ -301,7 +301,7 @@ memorio.logger.clearHistory()
301
301
  memorio.logger.exportLogs() // JSON string of all history
302
302
  ```
303
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.
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
305
 
306
306
  ## Platform Detection
307
307
 
@@ -317,7 +317,7 @@ const caps = memorio.getCapabilities()
317
317
  // { platform: 'browser', hasLocalStorage: true, hasIndexedDB: true, sessionId: 'uuid', ... }
318
318
  ```
319
319
 
320
- Prefer checking `getCapabilities()` over assuming a platform from context especially before calling `idb` or relying on `store`/`session` durability.
320
+ Prefer checking `getCapabilities()` over assuming a platform from context - especially before calling `idb` or relying on `store`/`session` durability.
321
321
 
322
322
  ## Cross-Platform Support
323
323
 
@@ -333,7 +333,7 @@ Prefer checking `getCapabilities()` over assuming a platform from context — es
333
333
 
334
334
  ## Session & Context Isolation
335
335
 
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.
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.
337
337
 
338
338
  To isolate a slice of state (e.g. per tenant, per request), create an explicit context:
339
339
 
@@ -341,7 +341,7 @@ To isolate a slice of state (e.g. per tenant, per request), create an explicit c
341
341
  const ctx = memorio.createContext('tenant-name')
342
342
  ctx.state.user = { name: 'Isolated' }
343
343
 
344
- console.debug(state.user) // undefined separate namespace from ctx.state
344
+ console.debug(state.user) // undefined - separate namespace from ctx.state
345
345
 
346
346
  memorio.listContexts()
347
347
  memorio.deleteContext('context-id')
@@ -351,9 +351,100 @@ memorio.isolate('tenant-name') // alias for createContext
351
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
352
 
353
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.
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
355
 
356
- `getCapabilities().sessionId` provides a per-session identifier for browser contexts but is not itself an isolation mechanism use `createContext` for that.
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
+ ---
357
448
 
358
449
  ## Security
359
450
 
@@ -361,9 +452,9 @@ Isolation is implemented as a **key-prefix convention** inside the same underlyi
361
452
  - No `eval`, no dynamic code execution, no obfuscation, no hardcoded secrets.
362
453
  - Inputs validated, keys sanitized before use.
363
454
  - Secure random session IDs via `crypto.randomUUID`.
364
- - 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.
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.
365
456
 
366
- 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.
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.
367
458
 
368
459
  ## License
369
460
 
@@ -371,7 +462,7 @@ MIT © Dario Passariello (BigLogic Inc Canada)
371
462
 
372
463
  ## Utilities
373
464
 
374
- ### `memorio.dispatch` Event Dispatch System
465
+ ### `memorio.dispatch` - Event Dispatch System
375
466
 
376
467
  Internal event system used by state changes; also usable directly for custom events.
377
468
 
@@ -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`)
@@ -0,0 +1,192 @@
1
+ # History, Undo / Redo, Snapshot, Diff, Trace - Memorio
2
+
3
+ > ✅ **Universal**: Works in Browser, Node.js, Deno, and Edge Workers
4
+
5
+ Memorio provides a lightweight time-travel system for `state` mutations: snapshots, diffs, undo/redo, and a full mutation trace log.
6
+
7
+ History tracking is **opt-in** — it is disabled by default to avoid overhead. Enable it when you need undo/redo or trace capabilities.
8
+
9
+ ---
10
+
11
+ ## Enable History
12
+
13
+ ```javascript
14
+ import 'memorio'
15
+
16
+ memorio.enableHistory() // enable tracking
17
+ // ... mutate state ...
18
+ memorio.state.user = { name: 'Sara' }
19
+ memorio.state.counter = 42
20
+ ```
21
+
22
+ > Without `enableHistory()`, mutations are not recorded and `undo()`/`redo()`/`trace()` return empty results.
23
+
24
+ ---
25
+
26
+ ## Snapshot & Diff
27
+
28
+ Snapshot captures the entire `state` tree at a point in time. Diff compares a snapshot against current state to see what changed.
29
+
30
+ ```javascript
31
+ // Enable history (snapshots work regardless, but trace/undo need it)
32
+ memorio.enableHistory()
33
+
34
+ // Take a snapshot
35
+ state.user = { name: 'Sara', age: 30 }
36
+ const snap = memorio.snapshot()
37
+
38
+ // Make changes
39
+ state.user.name = 'Luigi'
40
+ state.counter = 100
41
+ state.items = ['a', 'b']
42
+
43
+ // Diff against the snapshot
44
+ const changes = memorio.diff(snap)
45
+ console.debug(changes)
46
+ // [
47
+ // { path: 'user.name', oldValue: 'Sara', newValue: 'Luigi' },
48
+ // { path: 'counter', oldValue: undefined, newValue: 100 },
49
+ // { path: 'items', oldValue: undefined, newValue: ['a', 'b'] }
50
+ // ]
51
+ ```
52
+
53
+ This is essential for AI agents: take a snapshot, make changes, inspect the diff, and decide whether to commit or rollback.
54
+
55
+ ---
56
+
57
+ ## Undo / Redo
58
+
59
+ ```javascript
60
+ state.user = { name: 'Sara' }
61
+ state.counter = 100
62
+ state.items = ['a', 'b']
63
+
64
+ memorio.undo() // removes state.items
65
+ memorio.undo() // counter → undefined
66
+ memorio.redo() // counter → 100 again
67
+
68
+ memorio.canUndo() // true
69
+ memorio.canRedo() // true (after above undo + redo cycle)
70
+ ```
71
+
72
+ - `undo()`: Restores the previous state by inverting the most recent mutation.
73
+ - `redo()`: Re-applies the most recently undone mutation.
74
+ - `canUndo()` / `canRedo()`: Check availability before calling.
75
+
76
+ ### Max history depth
77
+
78
+ ```javascript
79
+ memorio.setMaxHistory(50) // keep at most 50 mutations per stack (default: 100)
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Rollback (full state restore)
85
+
86
+ Unlike undo (which works one step at a time), `rollback` replaces the entire state from a snapshot:
87
+
88
+ ```javascript
89
+ const snap = memorio.snapshot()
90
+
91
+ state.experiment = { result: 'failed' }
92
+ state.counter = 999
93
+
94
+ // Discard everything and restore to snapshot
95
+ memorio.rollback(snap)
96
+ // state.experiment is now gone
97
+ // state.counter is back to its snapshot value
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Trace (mutation log)
103
+
104
+ The trace log records every mutation with timestamp, path, action, and before/after values:
105
+
106
+ ```javascript
107
+ memorio.enableHistory()
108
+
109
+ state.user.name = 'Sara'
110
+ state.counter = 1
111
+ state.counter = 2
112
+
113
+ const log = memorio.trace()
114
+ console.debug(log)
115
+ // [
116
+ // { path: 'user.name', action: 'set', newValue: 'Sara', previousValue: undefined, timestamp: 1725... },
117
+ // { path: 'counter', action: 'set', newValue: 1, previousValue: undefined, timestamp: 1725... },
118
+ // { path: 'counter', action: 'set', newValue: 2, previousValue: 1, timestamp: 1725... }
119
+ // ]
120
+ ```
121
+
122
+ This is useful for:
123
+ - **AI debugging**: inspect what changed and when
124
+ - **Event sourcing**: export the log and replay state from scratch
125
+ - **Audit trails**: log all mutations for compliance
126
+
127
+ ### Export / import trace
128
+
129
+ ```javascript
130
+ const log = memorio.trace()
131
+ localStorage.setItem('memorio-trace', JSON.stringify(log))
132
+
133
+ // Later, replay:
134
+ const saved = JSON.parse(localStorage.getItem('memorio-trace'))
135
+ for (const record of saved) {
136
+ if (record.action === 'set') {
137
+ state[record.path] = record.newValue
138
+ }
139
+ }
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Clear History
145
+
146
+ ```javascript
147
+ memorio.clearHistory() // wipe undo/redo stacks + trace log
148
+ memorio.clearRedo() // clear only the redo stack (undo stack preserved)
149
+ ```
150
+
151
+ > `clearHistory()` does NOT reset the current `state` — only the history tracking data.
152
+
153
+ ---
154
+
155
+ ## Full API
156
+
157
+ | Method | Parameters | Returns | Description |
158
+ |--------|-----------|---------|-------------|
159
+ | `memorio.snapshot()` | none | `Record<string, any>` | Deep clone of current state |
160
+ | `memorio.diff(snap)` | `snap` | `DiffEntry[]` | Changed paths with old/new values |
161
+ | `memorio.undo()` | none | `MutationRecord \| undefined` | Undo last mutation |
162
+ | `memorio.redo()` | none | `MutationRecord \| undefined` | Redo last undone mutation |
163
+ | `memorio.canUndo()` | none | `boolean` | Whether undo is available |
164
+ | `memorio.canRedo()` | none | `boolean` | Whether redo is available |
165
+ | `memorio.rollback(snap)` | `snap` | `void` | Restore full state from snapshot |
166
+ | `memorio.trace()` | none | `MutationRecord[]` | List of all recorded mutations |
167
+ | `memorio.enableHistory(enabled?)` | `boolean` | `void` | Enable/disable tracking |
168
+ | `memorio.clearHistory()` | none | `void` | Clear all history stacks |
169
+ | `memorio.clearRedo()` | none | `void` | Clear only redo stack |
170
+ | `memorio.setMaxHistory(max)` | `number` | `void` | Set max stack depth |
171
+ | `memorio.getMaxHistory()` | none | `number` | Get current max depth |
172
+
173
+ ---
174
+
175
+ ## How It Works
176
+
177
+ 1. When `historyEnabled` is true, the state proxy's callback (`buildProxy` callback) fires on every `set`/`delete` trap, recording a `MutationRecord` with path, action, oldValue, newValue, and timestamp.
178
+ 2. Records are pushed to both a trace log (`mutations`) and an undo stack.
179
+ 3. Any new mutation clears the redo stack.
180
+ 4. `undo()` pops from the undo stack, pushes to the redo stack, and applies the inverse operation (restoring the previous value, or deleting if it was new).
181
+ 5. `redo()` pops from the redo stack, pushes back to the undo stack, and re-applies the original mutation.
182
+ 6. During undo/redo, history tracking is temporarily disabled to prevent recursive recording.
183
+ 7. `diff()` does a recursive key-by-key comparison between the snapshot and current `deepRaw(state)`.
184
+
185
+ ---
186
+
187
+ ## Best Practices
188
+
189
+ 1. **Always snapshot before AI experimentation** — `const snap = memorio.snapshot()` gives you a safe rollback point.
190
+ 2. **Call `diff()` before `rollback()`** — inspect what changed first; sometimes you only need to revert one key.
191
+ 3. **Keep `maxHistory` reasonable** — the default (100) is fine for most apps. Lower it for memory-constrained environments.
192
+ 4. **Don't rely on trace for sensitive data** — the trace log records *all* values written, including tokens/PII. Clear it or disable tracing in production paths.
@@ -0,0 +1,116 @@
1
+ # Introspection & Inspection - Memorio
2
+
3
+ > ✅ **Universal**: Works in Browser, Node.js, Deno, and Edge Workers
4
+
5
+ Introspection utilities let you programmatically discover, verify, and read the shape of the global `state` proxy. Essential for AI agents that need to check whether a path exists before writing, or enumerate available keys before reading.
6
+
7
+ ---
8
+
9
+ ## stateKeys()
10
+
11
+ Returns all top-level keys currently on the `state` proxy. Excludes internal properties.
12
+
13
+ ```javascript
14
+ import 'memorio'
15
+
16
+ state.user = { name: 'Sara' }
17
+ state.counter = 42
18
+
19
+ memorio.stateKeys() // ['user', 'counter']
20
+ ```
21
+
22
+ ---
23
+
24
+ ## pathExists(path)
25
+
26
+ Checks whether a dotted path exists in state. Returns `true` if the path resolves to a non-undefined value.
27
+
28
+ ```javascript
29
+ state.user = { name: 'Sara', profile: { age: 30 } }
30
+
31
+ memorio.pathExists('user') // true
32
+ memorio.pathExists('user.name') // true
33
+ memorio.pathExists('user.profile') // true
34
+ memorio.pathExists('user.profile.age') // true
35
+ memorio.pathExists('user.age') // false
36
+ memorio.pathExists('nonexistent') // false
37
+ ```
38
+
39
+ This is critical for AI agents: always check `pathExists` before writing to a nested path to avoid creating unintended intermediate objects.
40
+
41
+ ---
42
+
43
+ ## stateType(path)
44
+
45
+ Returns the runtime type of the value at a given state path.
46
+
47
+ ```javascript
48
+ state.count = 42
49
+ state.name = 'Sara'
50
+ state.items = [1, 2, 3]
51
+
52
+ memorio.stateType('count') // 'number'
53
+ memorio.stateType('name') // 'string'
54
+ memorio.stateType('items') // 'array'
55
+ memorio.stateType('missing') // 'undefined'
56
+ ```
57
+
58
+ ---
59
+
60
+ ## stateGet(path)
61
+
62
+ Returns the value at a dotted path, deep-cloned to prevent accidental mutation of state.
63
+
64
+ ```javascript
65
+ state.user = { name: 'Sara', tags: ['admin'] }
66
+
67
+ const user = memorio.stateGet('user') // { name: 'Sara', tags: ['admin'] }
68
+ user.name = 'Luigi' // mutates the clone, not state
69
+ state.user.name // still 'Sara'
70
+ ```
71
+
72
+ ---
73
+
74
+ ## stateSchema()
75
+
76
+ Generates a full schema report of the current state tree — every path with its type and whether it's defined.
77
+
78
+ ```javascript
79
+ state.user = { name: 'Sara', age: 30 }
80
+ state.theme = 'dark'
81
+
82
+ memorio.stateSchema()
83
+ // [
84
+ // { path: 'user', type: 'object', defined: true },
85
+ // { path: 'user.name', type: 'string', defined: true },
86
+ // { path: 'user.age', type: 'number', defined: true },
87
+ // { path: 'theme', type: 'string', defined: true }
88
+ // ]
89
+ ```
90
+
91
+ This is the **most useful for AI agents** — it gives a complete picture of what's in state and what types the values are, in a single call. Perfect for:
92
+ - Discovering available state before generating code
93
+ - Validating that expected paths exist
94
+ - Understanding the shape of nested objects
95
+
96
+ ---
97
+
98
+ ## Full API
99
+
100
+ | Method | Parameters | Returns | Description |
101
+ |--------|-----------|---------|-------------|
102
+ | `memorio.stateKeys()` | none | `string[]` | Top-level state keys |
103
+ | `memorio.pathExists(path)` | `string` | `boolean` | Whether a path resolves to a value |
104
+ | `memorio.stateType(path)` | `string` | `string` | Runtime type at path |
105
+ | `memorio.stateGet(path)` | `string` | `any` | Deep-cloned value at path |
106
+ | `memorio.stateSchema()` | none | `SchemaEntry[]` | Full state tree report |
107
+
108
+ ---
109
+
110
+ ## How It Works
111
+
112
+ All introspection functions read from the global `state` proxy via `deepRaw()` — the same function used internally by the state proxy to unwrap itself before storing. This ensures consistent, non-proxied values are returned.
113
+
114
+ - `pathExists` and `stateGet` split the path on `.` and traverse the state tree.
115
+ - `stateSchema` recursively walks the state object, collecting every path and its type.
116
+ - All returned values from `stateGet` and `snapshot` are deep clones (via JSON round-trip) to prevent accidental mutation.