memorio 4.7.1 → 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 +241 -34
- package/SECURITY.md +10 -10
- package/SUMMARY.md +14 -0
- package/index.cjs +160 -29
- package/index.d.ts +3 -0
- package/index.js +115 -7
- package/llms.txt +119 -28
- package/markdown/CHANGELOG.md +8 -8
- package/markdown/HISTORY.md +192 -0
- package/markdown/INSPECT.md +116 -0
- package/markdown/MEMORY.md +155 -0
- package/markdown/SCHEMA.md +169 -0
- package/markdown/SECURITY.md +1 -1
- package/markdown/TYPED.md +158 -0
- package/package.json +7 -7
- package/types/exports.d.ts +25 -0
- package/types/history.d.ts +27 -0
- package/types/inspect.d.ts +14 -0
- package/types/memorio.d.ts +48 -2
- package/types/memory.d.ts +70 -0
- package/types/schema.d.ts +53 -0
package/markdown/CHANGELOG.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
101
|
+
- `lint` and `tsc` pass clean - 0 vulnerabilities from `npm audit`
|
|
102
102
|
|
|
103
|
-
### 🆕 API
|
|
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
|
|
139
|
+
## v2.9.0 - 2026-05-13
|
|
140
140
|
|
|
141
141
|
### Added
|
|
142
|
-
- DevTools
|
|
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
|
|
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.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Memory System
|
|
2
|
+
|
|
3
|
+
`memorio.memory` provides a semantic memory layer — a key/value store with type safety, TTL, confidence scoring, tagging, and scope-based persistence.
|
|
4
|
+
|
|
5
|
+
## Core API
|
|
6
|
+
|
|
7
|
+
### `memorio.memory.remember(key, value, opts?)`
|
|
8
|
+
|
|
9
|
+
Stores a memory entry.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
await memorio.memory.remember('user.name', 'Dario', {
|
|
13
|
+
type: 'preference',
|
|
14
|
+
confidence: 0.92,
|
|
15
|
+
scope: 'local',
|
|
16
|
+
ttl: null,
|
|
17
|
+
tags: ['user', 'profile'],
|
|
18
|
+
source: 'conversation'
|
|
19
|
+
})
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
| Option | Type | Default | Description |
|
|
23
|
+
|--------|------|---------|-------------|
|
|
24
|
+
| `id` | `string` | `key` | Custom entry ID |
|
|
25
|
+
| `type` | `'fact' \| 'preference' \| 'decision' \| 'task' \| 'context'` | `'fact'` | Semantic classification |
|
|
26
|
+
| `confidence` | `number` | `1.0` | Trust score 0.0–1.0 |
|
|
27
|
+
| `scope` | `'hot' \| 'session' \| 'local' \| 'durable'` | auto | Storage backend (see below) |
|
|
28
|
+
| `ttl` | `number \| null` | `null` | Milliseconds before expiry (`null` = never) |
|
|
29
|
+
| `tags` | `string \| string[]` | `[]` | Metadata for filtering |
|
|
30
|
+
| `source` | `string` | `undefined` | Origin of the memory |
|
|
31
|
+
|
|
32
|
+
### `memorio.memory.recall(query, opts?)`
|
|
33
|
+
|
|
34
|
+
Retrieves a memory by key.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
const name = await memorio.memory.recall('user.name')
|
|
38
|
+
const highConfidence = await memorio.memory.recall('project', {
|
|
39
|
+
minConfidence: 0.8
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
| Option | Type | Default | Description |
|
|
44
|
+
|--------|------|---------|-------------|
|
|
45
|
+
| `type` | `MemoryType \| MemoryType[]` | `*` | Filter by type |
|
|
46
|
+
| `tags` | `string \| string[]` | `*` | Filter by tags |
|
|
47
|
+
| `minConfidence` | `number` | `0` | Minimum confidence threshold |
|
|
48
|
+
| `includeObsolete` | `boolean` | `false` | Include expired/superseded entries |
|
|
49
|
+
|
|
50
|
+
### `memorio.memory.update(key, value, opts?)`
|
|
51
|
+
|
|
52
|
+
Updates an existing memory. Creates a **superseded copy** of the old entry (preserving history).
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
await memorio.memory.update('user.name', 'Alice', { confidence: 0.95 })
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### `memorio.memory.forget(key)`
|
|
59
|
+
|
|
60
|
+
Permanently removes a memory.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
await memorio.memory.forget('user.temp')
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `memorio.memory.context(opts?)`
|
|
67
|
+
|
|
68
|
+
Returns ranked, relevant memories. Uses recency × confidence × type/scope boost algorithm.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
const ctx = await memorio.memory.context({
|
|
72
|
+
tags: 'project',
|
|
73
|
+
types: ['decision', 'preference'],
|
|
74
|
+
minConfidence: 0.7,
|
|
75
|
+
maxEntries: 10,
|
|
76
|
+
scopes: ['local', 'durable']
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
```ts
|
|
82
|
+
Array<{
|
|
83
|
+
key: string
|
|
84
|
+
value: any
|
|
85
|
+
type: MemoryType
|
|
86
|
+
confidence: number
|
|
87
|
+
scope: MemoryScope
|
|
88
|
+
tags: string[]
|
|
89
|
+
age: number // milliseconds since createdAt
|
|
90
|
+
accessCount: number
|
|
91
|
+
lastAccessedAt: number
|
|
92
|
+
}>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### `memorio.memory.stats()`
|
|
96
|
+
|
|
97
|
+
Returns usage statistics:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
{
|
|
101
|
+
total: number
|
|
102
|
+
byScope: Record<MemoryScope, number>
|
|
103
|
+
byType: Partial<Record<MemoryType, number>>
|
|
104
|
+
expired: number
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `memorio.memory.forgetExpired()`
|
|
109
|
+
|
|
110
|
+
Cleans all expired entries. Returns count removed.
|
|
111
|
+
|
|
112
|
+
### `memorio.memory.clear()`
|
|
113
|
+
|
|
114
|
+
Wipes all memories and the index.
|
|
115
|
+
|
|
116
|
+
## Memory Entry Model
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
interface MemoryEntry<T = any> {
|
|
120
|
+
id: string
|
|
121
|
+
key: string
|
|
122
|
+
value: T
|
|
123
|
+
type: 'fact' | 'preference' | 'decision' | 'task' | 'context'
|
|
124
|
+
confidence: number
|
|
125
|
+
scope: 'hot' | 'session' | 'local' | 'durable'
|
|
126
|
+
ttl?: number | null
|
|
127
|
+
tags: string[]
|
|
128
|
+
source?: string
|
|
129
|
+
status: 'active' | 'obsolete' | 'superseded'
|
|
130
|
+
createdAt: number
|
|
131
|
+
lastConfirmedAt: number
|
|
132
|
+
supersededId?: string | null
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Scopes
|
|
137
|
+
|
|
138
|
+
| Scope | Storage | TTL | Cross-session | Size limit |
|
|
139
|
+
|-------|---------|-----|---------------|------------|
|
|
140
|
+
| `hot` | `state` proxy | ✅ | ❌ | ~5MB (RAM) |
|
|
141
|
+
| `session` | `sessionStorage` | ✅ | Tab only | ~5MB |
|
|
142
|
+
| `local` | `localStorage` | ✅ | ✅ | ~10MB |
|
|
143
|
+
| `durable` | `IndexedDB` | ✅ | ✅ | ~1GB+ |
|
|
144
|
+
|
|
145
|
+
Default scope is `local` for values ≤100KB, `durable` for larger values.
|
|
146
|
+
|
|
147
|
+
## Memory Lifecycle
|
|
148
|
+
|
|
149
|
+
| Event | Behavior |
|
|
150
|
+
|-------|----------|
|
|
151
|
+
| `remember()` on existing key | Old entry → `superseded`, new entry → `active` |
|
|
152
|
+
| Entry with TTL expires | Status → `obsolete` (cleaned by `forgetExpired()`) |
|
|
153
|
+
| `recall()` expired entry | Returns `null` unless `includeObsolete: true` |
|
|
154
|
+
| `update()` | Creates superseded copy + updated active entry |
|
|
155
|
+
| `clear()` | Wipes all memories + index |
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Schema Validation - Memorio
|
|
2
|
+
|
|
3
|
+
> ✅ **Universal**: Works in Browser, Node.js, Deno, and Edge Workers
|
|
4
|
+
|
|
5
|
+
Schema validation guards your `state` against invalid writes. It runs inside the state proxy's `set` trap, so any `state.somePath = value` that violates a registered schema is rejected at runtime — before the value is ever stored.
|
|
6
|
+
|
|
7
|
+
Schema validation is **opt-in** and **zero-dependency**.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import 'memorio'
|
|
15
|
+
|
|
16
|
+
// Register a validator for a top-level state key
|
|
17
|
+
memorio.registerSchema('user', {
|
|
18
|
+
type: 'object',
|
|
19
|
+
required: ['name', 'email'],
|
|
20
|
+
properties: {
|
|
21
|
+
name: { type: 'string', min: 1 },
|
|
22
|
+
email: { type: 'string', pattern: /^[^@]+@[^@]+$/ },
|
|
23
|
+
age: { type: 'number', min: 0, max: 150 }
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// Valid write — accepted
|
|
28
|
+
state.user = { name: 'Sara', email: 'sara@test.com', age: 30 }
|
|
29
|
+
|
|
30
|
+
// Invalid write — rejected, returns false
|
|
31
|
+
state.user = { name: 'Sara' } // missing 'email'
|
|
32
|
+
state.user = { name: 42, email: 'x' } // wrong type for 'name'
|
|
33
|
+
state.user = { age: -5 } // out of range
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Schema Definition
|
|
39
|
+
|
|
40
|
+
A `Schema` object supports the following fields:
|
|
41
|
+
|
|
42
|
+
| Field | Type | Description |
|
|
43
|
+
|-------|------|-------------|
|
|
44
|
+
| `type` | `'string' \| 'number' \| 'boolean' \| 'object' \| 'array' \| 'any'` | Runtime type check |
|
|
45
|
+
| `required` | `string[]` | Property names that must exist (objects only) |
|
|
46
|
+
| `properties` | `Record<string, Schema>` | Nested property schemas (validated recursively) |
|
|
47
|
+
| `min` | `number` | Number: minimum value. String: minimum length |
|
|
48
|
+
| `max` | `number` | Number: maximum value. String: maximum length |
|
|
49
|
+
| `pattern` | `RegExp` | Regex the string value must match |
|
|
50
|
+
| `enum` | `any[]` | Whitelist of allowed values |
|
|
51
|
+
| `validator` | `(value) => boolean \| string` | Custom validator function |
|
|
52
|
+
|
|
53
|
+
### Custom validator functions
|
|
54
|
+
|
|
55
|
+
For logic that's hard to express declaratively, pass a function instead of a schema object:
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
memorio.registerSchema('counter', (value) => {
|
|
59
|
+
if (typeof value !== 'number') return 'counter must be a number'
|
|
60
|
+
if (value < 0) return 'counter must be >= 0'
|
|
61
|
+
return true
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
A custom validator receives the raw value. Return `true` to accept, or a **string** describing the error to reject.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Path-based registration
|
|
70
|
+
|
|
71
|
+
Schemas are keyed by their **state path**, relative to `state`:
|
|
72
|
+
|
|
73
|
+
| API call | Catches |
|
|
74
|
+
|----------|---------|
|
|
75
|
+
| `registerSchema('user', schema)` | `state.user = value` |
|
|
76
|
+
| `registerSchema('user.age', schema)` | `state.user.age = value` |
|
|
77
|
+
| `registerSchema('items', schema)` | `state.items = value` |
|
|
78
|
+
|
|
79
|
+
The full dotted path is constructed from the proxy's tree depth. Nested sets propagate the full path automatically.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Manual validation
|
|
84
|
+
|
|
85
|
+
You can validate a value without writing it to state:
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
memorio.validate('user', { name: 'Sara', email: 'sara@test.com' })
|
|
89
|
+
// { valid: true }
|
|
90
|
+
|
|
91
|
+
memorio.validate('user', { name: 'Sara' })
|
|
92
|
+
// { valid: false, errors: ["user: missing required property 'email'"] }
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
When no schema is registered for a path, `validate` returns `{ valid: true }`.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Schema management
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
memorio.listSchemas() // ['user', 'theme', 'items', 'counter']
|
|
103
|
+
memorio.unregisterSchema('counter') // removes the schema
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Full API
|
|
109
|
+
|
|
110
|
+
| Method | Parameters | Returns | Description |
|
|
111
|
+
|--------|-----------|---------|-------------|
|
|
112
|
+
| `memorio.registerSchema(path, schema)` | `string`, `Schema \| fn` | `void` | Register a validator |
|
|
113
|
+
| `memorio.validate(path, value)` | `string`, `any` | `{ valid, errors? }` | Manually validate a value |
|
|
114
|
+
| `memorio.unregisterSchema(path)` | `string` | `boolean` | Remove a registered schema |
|
|
115
|
+
| `memorio.listSchemas()` | none | `string[]` | List all registered paths |
|
|
116
|
+
| `memorio.registerSchema()` is also importable | `registerSchema` | named export | same function |
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Combine with Typed Stores
|
|
121
|
+
|
|
122
|
+
Schema validation gives you **runtime** safety; typed stores give you **compile-time** safety. Use both for full coverage:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import 'memorio'
|
|
126
|
+
|
|
127
|
+
interface AppState {
|
|
128
|
+
user: { name: string; email: string; age: number }
|
|
129
|
+
theme: 'light' | 'dark'
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const app = memorio.typed<AppState>()
|
|
133
|
+
|
|
134
|
+
memorio.registerSchema('user', {
|
|
135
|
+
type: 'object',
|
|
136
|
+
required: ['name', 'email'],
|
|
137
|
+
properties: {
|
|
138
|
+
name: { type: 'string', min: 1 },
|
|
139
|
+
email: { type: 'string', pattern: /^[^@]+@[^@]+$/ },
|
|
140
|
+
age: { type: 'number', min: 0, max: 150 }
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
app.user = { name: '', email: 'bad' } // ❌ TypeScript: age missing
|
|
145
|
+
// ❌ Runtime: missing required fields
|
|
146
|
+
app.user = { name: 'Sara', email: 'ok', age: 30 } // ✅ both checks pass
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
See [Typed Stores](TYPED.md) for compile-time type safety.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## How it works
|
|
154
|
+
|
|
155
|
+
1. When you call `registerSchema(path, schema)`, the schema is stored in an internal `Map`.
|
|
156
|
+
2. On every `state.set` operation, the proxy's `set` trap computes the full path (e.g. `'user.name'`).
|
|
157
|
+
3. If a schema is registered for that path, the value is validated.
|
|
158
|
+
4. If validation fails, the write is rejected (`return false`), and an error is logged via `console.error` (when `memorio.debug = true`) or `console.debug` (via the internal `message` helper).
|
|
159
|
+
5. If no schema is registered, the write proceeds normally.
|
|
160
|
+
|
|
161
|
+
The validation adds negligible overhead when no schemas are registered (a single `Map` lookup that returns `undefined`).
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Limitations
|
|
166
|
+
|
|
167
|
+
- Schema validation hooks into the `state` proxy only. `store`, `session`, and `cache` are not validated (they use separate storage). Use `validate()` before writing to other modules.
|
|
168
|
+
- Path matching is **exact**: `registerSchema('user')` guards `state.user = ...`, but does **not** recursively validate `state.user.name = 'new'`. Register schemas at each path you need to guard.
|
|
169
|
+
- The schema system is not a replacement for server-side validation. It protects against accidental misuse and provides defense-in-depth in the browser.
|
package/markdown/SECURITY.md
CHANGED