memorio 5.1.3 → 5.2.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.
Files changed (48) hide show
  1. package/AGENTS.md +22 -13
  2. package/CHANGELOG.md +24 -9
  3. package/README.md +61 -19
  4. package/SECURITY-HARDENING.md +258 -0
  5. package/SECURITY.md +67 -3
  6. package/SUMMARY.md +55 -59
  7. package/adr/002-observer-semantics.md +1 -1
  8. package/adr/003-deep-mutation-semantics.md +1 -1
  9. package/adr/004-array-mutation-semantics.md +1 -1
  10. package/adr/010-logic-phase-0.md +42 -0
  11. package/adr/README.md +16 -11
  12. package/bin/cli.js +82 -60
  13. package/examples/acquired-knowledge.ts +174 -0
  14. package/examples/agent-memory-demo.ts +140 -0
  15. package/examples/sqlite-batched-writes.ts +60 -60
  16. package/examples/sync.ts +90 -90
  17. package/examples/useObserver.tsx +2 -2
  18. package/global.cjs +2026 -115
  19. package/global.js +2021 -116
  20. package/index.cjs +2026 -115
  21. package/index.d.ts +1 -0
  22. package/index.js +2021 -116
  23. package/llms.txt +135 -4
  24. package/markdown/EXTENSION_VSCODE_DESIGN.md +410 -0
  25. package/markdown/LOGIC.md +100 -0
  26. package/markdown/MEMORY-ATTACHMENT.md +17 -10
  27. package/markdown/MEMORY.md +404 -14
  28. package/markdown/MEM_FORMAT.md +313 -0
  29. package/markdown/SQLITE.md +2 -2
  30. package/markdown/STATE.md +27 -3
  31. package/markdown/SYNC.md +19 -15
  32. package/markdown/TEMPORAL.md +297 -0
  33. package/markdown/USEOBSERVER.md +7 -4
  34. package/modules/redux.cjs +1188 -38
  35. package/modules/redux.cjs.map +1 -1
  36. package/modules/redux.js +1187 -38
  37. package/modules/redux.js.map +1 -1
  38. package/package.json +14 -5
  39. package/types/exports.d.ts +47 -3
  40. package/types/logic.d.ts +79 -0
  41. package/types/memorio.d.ts +60 -16
  42. package/types/memory.d.ts +118 -0
  43. package/types/session.d.ts +1 -4
  44. package/types/state.d.ts +19 -5
  45. package/types/store.d.ts +1 -4
  46. package/types/temporal.d.ts +95 -0
  47. package/types/useObserver.d.ts +6 -10
  48. package/vsix/memorio.vsix +0 -0
@@ -0,0 +1,297 @@
1
+ > **Status:** Published
2
+ > **Date:** 2026-09-17
3
+ > **Scope**: API Reference
4
+ > **Standard**: Memorio API Specification v5
5
+ >
6
+ ---
7
+ # Temporal Memory - Time-Travel for State
8
+
9
+ > ✅ **Universal**: Works in Browser, Node.js, Deno, and Edge Workers
10
+
11
+ Memorio's temporal engine provides immutable, point-in-time query, diffing, and branching over state mutations. Unlike the undo/redo system (which is linear and single-step), the temporal engine maintains a full log of every mutation with causal ordering via Hybrid Logical Clocks (HLC), enabling sophisticated time-travel queries.
12
+
13
+ Temporal tracking is **enabled by default** and independent from `historyEnabled` (undo/redo). The temporal log is stored separately and indexed by path for efficient lookups.
14
+
15
+ ---
16
+
17
+ ## Quick Start
18
+
19
+ ```javascript
20
+ import { state, memorio } from 'memorio'
21
+
22
+ // Temporal tracking is on by default
23
+ state.counter = 1
24
+ state.counter = 2
25
+ state.counter = 3
26
+
27
+ // Query the value at a point in time
28
+ const history = memorio.temporal.history('counter')
29
+ const ts = history[0].hlc
30
+
31
+ console.log(memorio.temporal.at('counter', ts)) // 1
32
+ console.log(memorio.temporal.at('counter', Date.now())) // 3
33
+
34
+ // Diff between two points in time
35
+ const hlc2 = history[1].hlc
36
+ const changes = memorio.temporal.diff('counter', hlc2, Date.now())
37
+ console.debug(changes)
38
+ // [{ path: 'counter', before: 2, after: 3, operation: 'set', hlc: 'hlc:...', timestamp: ... }]
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Core Concepts
44
+
45
+ ### Hybrid Logical Clock (HLC) Timestamps
46
+
47
+ Every mutation is stamped with an HLC timestamp, which combines:
48
+ - **Physical time** (milliseconds since epoch) - coarse ordering across devices
49
+ - **Logical counter** - disambiguates mutations within the same millisecond on the same node
50
+ - **Node ID** - stable per origin, ensures total causal ordering
51
+
52
+ This means multiple mutations that happen within the same millisecond are still uniquely and correctly ordered. When passing timestamps to temporal queries:
53
+ - **HLC strings** (`"hlc:1725...:0:abc123"`) are parsed and compared using physical + logical for precise per-mutation resolution
54
+ - **Numeric timestamps** (epoch ms) are treated as "include all mutations up to and including this physical millisecond"
55
+
56
+ ```javascript
57
+ // HLC string: precise to the individual mutation
58
+ memorio.temporal.at('counter', 'hlc:1725123456789:0:abc123')
59
+
60
+ // Numeric: includes all mutations at this physical time
61
+ memorio.temporal.at('counter', 1725123456789)
62
+ ```
63
+
64
+ ### Path Indexing
65
+
66
+ Mutations are indexed by dotted path (e.g. `user.name`). The `history()`, `at()`, `diff()`, and `explain()` methods accept path queries:
67
+ - **Exact path** (`'user.name'`): returns mutations to that exact path
68
+ - **Parent path** (`'user'`): returns mutations to `user` and any sub-paths (`user.name`, `user.role`, etc.)
69
+ - **No path** (`history()`): returns all mutations
70
+
71
+ ### Immutable Snapshots
72
+
73
+ All temporal queries return **deep clones** of the historical state. Modifying the returned value does **not** affect live state.
74
+
75
+ ---
76
+
77
+ ## API
78
+
79
+ ### `memorio.temporal.history(path?)`
80
+
81
+ Returns all mutations matching the path, sorted chronologically by HLC.
82
+
83
+ ```javascript
84
+ state.user = { name: 'Alice' }
85
+ state.user.name = 'Bob'
86
+ state.counter = 1
87
+
88
+ memorio.temporal.history() // all mutations
89
+ memorio.temporal.history('user.name') // mutations to user.name (2)
90
+ memorio.temporal.history('user') // mutations to user and sub-paths (2)
91
+ memorio.temporal.history('counter') // mutations to counter (1)
92
+ ```
93
+
94
+ Each entry is a `Mutation` record with `id`, `path`, `operation`, `before`, `after`, `timestamp`, `hlc`, and optional `source`/`context`/`transactionId`.
95
+
96
+ ### `memorio.temporal.at(path, timestamp)`
97
+
98
+ Returns the value at a path at a specific point in time. Never mutates live state.
99
+
100
+ ```javascript
101
+ state.counter = 1
102
+ state.counter = 2
103
+
104
+ const mutations = memorio.temporal.history('counter')
105
+ const hlc1 = mutations[0].hlc
106
+
107
+ console.log(memorio.temporal.at('counter', hlc1)) // 1
108
+ console.log(memorio.temporal.at('counter', Date.now())) // 2
109
+
110
+ // Works with HLC strings for precise queries
111
+ console.log(memorio.temporal.at('counter', mutations[1].hlc)) // 2
112
+
113
+ // Returns undefined if no mutation existed at that time
114
+ console.log(memorio.temporal.at('counter', mutations[0].timestamp - 1)) // undefined
115
+ ```
116
+
117
+ ### `memorio.temporal.diff(path, from, to)`
118
+
119
+ Returns the individual mutation changes that occurred within a time range.
120
+
121
+ ```javascript
122
+ state.counter = 1
123
+ const hlc1 = memorio.temporal.history('counter')[0].hlc
124
+
125
+ state.counter = 2
126
+ state.counter = 3
127
+
128
+ const diffs = memorio.temporal.diff('counter', hlc1, Date.now())
129
+ // [
130
+ // { path: 'counter', before: 1, after: 2, operation: 'set', ... },
131
+ // { path: 'counter', before: 2, after: 3, operation: 'set', ... }
132
+ // ]
133
+ ```
134
+
135
+ The `from` timestamp is exclusive; the `to` timestamp is inclusive. Both accept HLC strings or epoch ms numbers.
136
+
137
+ ### `memorio.temporal.replay(options)`
138
+
139
+ Reconstructs the full state tree from the temporal log, optionally limited to a time range.
140
+
141
+ ```javascript
142
+ state.user = { name: 'Alice' }
143
+ state.user.name = 'Bob'
144
+ state.counter = 42
145
+
146
+ state.user.name = 'Charlie'
147
+
148
+ const replayed = memorio.temporal.replay()
149
+ // { user: { name: 'Charlie' }, counter: 42 }
150
+
151
+ // Time-range replay
152
+ const mutations = memorio.temporal.history('counter')
153
+ const hlc1 = mutations[0].hlc
154
+ const hlc2 = mutations[1].hlc
155
+ memorio.temporal.replay({ from: hlc1, to: hlc2 }) // state at the second counter mutation
156
+ ```
157
+
158
+ ### `memorio.temporal.explain(path)`
159
+
160
+ Returns provenance metadata for how a value at a path came to exist.
161
+
162
+ ```javascript
163
+ state.user = { role: 'user' }
164
+ state.user.role = 'admin'
165
+
166
+ const explanation = memorio.temporal.explain('user.role')
167
+ // {
168
+ // path: 'user.role',
169
+ // currentValue: 'admin',
170
+ // previousValue: 'user', // value before the last mutation
171
+ // history: [ /* mutations affecting this path */ ],
172
+ // createdAt: 'hlc:...', // when the path was first set
173
+ // lastChangedAt: 'hlc:...', // when the path was last modified
174
+ // source: undefined, // optional source attribution
175
+ // ...
176
+ // }
177
+ ```
178
+
179
+ ### `memorio.temporal.branch()`
180
+
181
+ Creates an isolated temporal branch. Mutations to the branch's state do **not** affect live state or the main temporal log.
182
+
183
+ ```javascript
184
+ state.user = { role: 'user' }
185
+ state.counter = 1
186
+
187
+ const branch = memorio.temporal.branch()
188
+
189
+ // Branch state starts from a snapshot of live state
190
+ console.log(branch.state.user.role) // 'user'
191
+ console.log(branch.state.counter) // 1
192
+
193
+ // Mutations to branch state are isolated
194
+ branch.state.user.role = 'admin'
195
+ branch.state.counter = 99
196
+
197
+ console.log(state.user.role) // 'user' (unchanged)
198
+ console.log(state.counter) // 1 (unchanged)
199
+
200
+ // Branch has its own temporal history
201
+ const branchHistory = branch.temporal.history('counter')
202
+ console.log(branchHistory.length) // 4 (3 from main + 1 from branch)
203
+
204
+ // Clean up
205
+ branch.discard() // or branch.commit() to merge
206
+ ```
207
+
208
+ ### `memorio.temporal.enable(enabled?)` / `memorio.temporal.isEnabled()`
209
+
210
+ Enable or disable temporal tracking.
211
+
212
+ ```javascript
213
+ memorio.temporal.enable(false) // stop recording
214
+ state.x = 1
215
+ memorio.temporal.history('x') // [] - no new mutations recorded
216
+
217
+ memorio.temporal.enable(true) // resume
218
+ state.x = 2
219
+ memorio.temporal.history('x') // [mutation for x=2]
220
+ ```
221
+
222
+ ### `memorio.temporal.configure(config)`
223
+
224
+ Configure temporal engine settings.
225
+
226
+ ```javascript
227
+ memorio.temporal.configure({
228
+ maxEntries: 50000, // max mutations retained in the log (default: 10000)
229
+ snapshots: false, // reserved for future snapshot checkpointing
230
+ snapshotInterval: 100 // reserved for future snapshot checkpointing
231
+ })
232
+ ```
233
+
234
+ ### `memorio.temporal.stats()`
235
+
236
+ Returns statistics about the temporal log.
237
+
238
+ ```javascript
239
+ console.log(memorio.temporal.stats())
240
+ // {
241
+ // total: 42, // total mutations in the log
242
+ // byPath: { // mutation count per path
243
+ // 'user.name': 3,
244
+ // 'counter': 5,
245
+ // ...
246
+ // }
247
+ // }
248
+ ```
249
+
250
+ ### `memorio.temporal.size()`
251
+
252
+ Returns the total number of mutations in the temporal log.
253
+
254
+ ### `memorio.temporal.clear()`
255
+
256
+ Clears the entire temporal log. Does **not** affect live state.
257
+
258
+ ```javascript
259
+ memorio.temporal.clear()
260
+ memorio.temporal.size() // 0
261
+ ```
262
+
263
+ ---
264
+
265
+ ## Temporal vs. History (Undo/Redo)
266
+
267
+ | Feature | Temporal (`temporal`) | History (`enableHistory`/`undo`/`trace`) |
268
+ |---|---|---|
269
+ | **Enabled by default** | Yes | No |
270
+ | **Log** | Full, immutable, indexed by path | Undo/redo stacks + trace |
271
+ | **Timestamps** | HLC (physical + logical + node) | Epoch ms |
272
+ | **Time-travel** | Point-in-time queries (`at`, `diff`, `replay`) | Linear undo/redo only |
273
+ | **Branching** | Yes (`branch()`) | No |
274
+ | **Provenance** | `explain()` | No |
275
+ | **Memory limit** | `maxEntries` (default: 10000) | `maxHistory` (default: 100) |
276
+ | **Mutations** | Deep-cloned snapshots | References to live values |
277
+
278
+ Both systems record mutations independently. Enabling one does not enable the other.
279
+
280
+ ---
281
+
282
+ ## How It Works
283
+
284
+ 1. When `state` is mutated, the proxy callback in `buildProxy` fires, creating a `Mutation` via `createMutation()`.
285
+ 2. `createMutation()` assigns an HLC timestamp (`physical`, `logical`, `node`) and deep-clones the `after`/`before` values to ensure immutability.
286
+ 3. The mutation is recorded into `internal.temporalLog` (shared via `globalThis` for ESM/CJS deduplication).
287
+ 4. Queries like `at()`, `diff()`, `replay()`, and `explain()` reconstruct historical state by replaying mutations up to the requested timestamp using the combined HLC sort key (physical * 1e9 + logical).
288
+ 5. Branch engines use their own local log arrays and isolated state proxies.
289
+
290
+ ---
291
+
292
+ ## Best Practices
293
+
294
+ 1. **Use HLC strings for precise queries**: When you need to query the exact state after a specific mutation, use the mutation's `hlc` string. Numeric timestamps include all mutations at the same physical millisecond.
295
+ 2. **Don't trust temporal log for security**: The temporal log is inspectable and can be cleared. It's a debugging and time-travel tool, not an audit trail.
296
+ 3. **Branches are isolated**: Mutations in a branch do not affect the main log. Use `commit()` to persist changes (when implemented) or `discard()` to clean up.
297
+ 4. **Clear when done**: For long-running apps, `memorio.temporal.clear()` frees memory. The log is capped at `maxEntries` by default, but clearing is faster.
@@ -110,8 +110,8 @@ function StoreWatcher() {
110
110
  Direct primitive values are now fully supported:
111
111
 
112
112
  ```javascript
113
- // Direct values work with primitives ✅
114
- useObserver(() => { console.debug('changed') }, [state.counter])
113
+ // Primitive paths are discovered from state reads in the callback ✅
114
+ useObserver(() => { console.debug('changed', state.counter) }, [state.counter])
115
115
 
116
116
  // Arrays of primitives work ✅
117
117
  useObserver(() => { console.log(state.a, state.b) }, [state.a, state.b])
@@ -156,7 +156,10 @@ useObserver(() => {
156
156
  },[]);
157
157
  ```
158
158
 
159
- Returns a cleanup function:
159
+ Subscriptions are created after React commits and cleaned up automatically
160
+ when dependencies change or the component unmounts. `useObserver` returns
161
+ `void`; use the JavaScript `observer` API when manual subscription control is
162
+ required.
160
163
 
161
164
  ## useObserver vs observer
162
165
 
@@ -251,6 +254,6 @@ useObserver(() => {
251
254
 
252
255
  1. Always use in React components
253
256
  2. Use auto-discovery for simpler code: `useObserver(() => { ... })`
254
- 3. No manual cleanup needed - returns cleanup function automatically
257
+ 3. No manual cleanup is needed; React lifecycle cleanup is automatic
255
258
  4. Use with state for reactive UI
256
259
  5. Check console for auto-discovery logs