memorio 5.0.0 → 5.1.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 (60) hide show
  1. package/README.md +80 -449
  2. package/SECURITY.md +152 -42
  3. package/SUMMARY.md +1 -1
  4. package/adr/001-state-proxy-model.md +95 -96
  5. package/adr/002-observer-semantics.md +179 -180
  6. package/adr/003-deep-mutation-semantics.md +7 -8
  7. package/adr/004-array-mutation-semantics.md +127 -128
  8. package/adr/005-scheduler-contract.md +148 -149
  9. package/adr/006-context-isolation.md +91 -92
  10. package/adr/007-mutation-records.md +5 -6
  11. package/adr/008-transactions.md +6 -7
  12. package/adr/009-history-model.md +6 -7
  13. package/adr/README.md +46 -46
  14. package/adr/template.md +48 -49
  15. package/bin/cli.js +68 -0
  16. package/global.cjs +1462 -323
  17. package/global.js +1459 -324
  18. package/index.cjs +1462 -323
  19. package/index.d.ts +1 -0
  20. package/index.js +1459 -324
  21. package/llms.txt +42 -5
  22. package/markdown/AUDIT-REPORT.md +7 -8
  23. package/markdown/CACHE.md +190 -99
  24. package/markdown/DEVTOOLS.md +0 -1
  25. package/markdown/DISPATCH.md +0 -1
  26. package/markdown/HISTORY.md +0 -1
  27. package/markdown/IDB.md +0 -1
  28. package/markdown/IMPORT.md +0 -1
  29. package/markdown/INSPECT.md +0 -1
  30. package/markdown/LOGGER.md +0 -1
  31. package/markdown/MEMORY-ATTACHMENT.md +0 -1
  32. package/markdown/MEMORY.md +0 -1
  33. package/markdown/OBSERVER.md +0 -1
  34. package/markdown/PLATFORM.md +277 -271
  35. package/markdown/REDUX.md +54 -0
  36. package/markdown/SCHEMA.md +0 -1
  37. package/markdown/SESSION.md +0 -1
  38. package/markdown/SQLITE.md +0 -1
  39. package/markdown/STATE.md +0 -1
  40. package/markdown/STORE.md +0 -1
  41. package/markdown/SYNC.md +0 -1
  42. package/markdown/TYPED.md +0 -1
  43. package/markdown/USEOBSERVER.md +0 -1
  44. package/modules/redux.cjs +381 -10
  45. package/modules/redux.cjs.map +1 -1
  46. package/modules/redux.js +381 -10
  47. package/modules/redux.js.map +1 -1
  48. package/package.json +14 -2
  49. package/types/broadcast.d.ts +61 -0
  50. package/types/computed.d.ts +96 -0
  51. package/types/encryption.d.ts +129 -0
  52. package/types/exports.d.ts +9 -0
  53. package/types/memorio.d.ts +19 -12
  54. package/types/security.d.ts +67 -0
  55. package/types/session.d.ts +23 -5
  56. package/types/store.d.ts +19 -3
  57. package/vsix/memorio.vsix +0 -0
  58. package/markdown/CHANGELOG.md +0 -243
  59. package/markdown/PROJECT.md +0 -311
  60. package/markdown/SECURITY.md +0 -330
@@ -1,180 +1,179 @@
1
- # ADR-002: Observer Semantics
2
-
3
- > **Status:** Proposed
4
- > **Date:** 2026-09-12
5
- > **Deciders:** Memorio 5.x Core Team
6
-
7
- ## Context
8
-
9
- Memorio must freeze observer behavior so that developers can reason
10
- about *when* and *why* their callbacks fire. The observer system is the
11
- bridge between state mutations and UI updates, between the Mutation
12
- Engine and the reactive system. Before adding advanced features (impact
13
- analysis, simulation, causal graph), the observer contract must be
14
- explicit and deterministic.
15
-
16
- The following questions must have explicit answers:
17
-
18
- 1. Does a deep mutation notify the leaf observer?
19
- 2. Does a deep mutation notify parent observers?
20
- 3. What happens for array mutations?
21
- 4. What is the reference identity contract for observer callbacks?
22
- 5. Are notifications synchronous or asynchronous?
23
- 6. Are multiple mutations batched into a single notification?
24
- 7. What is the ordering guarantee when multiple observers listen to
25
- overlapping paths?
26
- 8. Can an observer mutate state synchronously?
27
- 9. What happens when an observer throws?
28
- 10. What happens when an observer is removed during dispatch?
29
- 11. How are nested transactions handled with respect to observer
30
- notification?
31
-
32
- ## Assumptions
33
-
34
- - The state Proxy fires exactly one callback per `set` or `delete`
35
- trap (no double-firing for the same logical mutation).
36
- - The dispatch layer (`core/dispatch.ts`) is the sole event bus.
37
- - `useObserver` is a thin wrapper over `observer` + `dispatch.listen`.
38
- - History tracking is opt-in (`enableHistory(true)`).
39
-
40
- ## Decision
41
-
42
- ### 1. Leaf notification
43
-
44
- A mutation at path `state.user.profile.name` dispatches an event on
45
- exactly the path `state.user.profile.name`. Only observers registered
46
- on that exact path receive the notification. Observers on ancestor
47
- paths (`state.user.profile`, `state.user`, `state`) do **not** fire for
48
- a leaf mutation unless the mutation replaces the ancestor itself.
49
-
50
- ```js
51
- observer('state.user.profile.name', cb) // fires
52
- observer('state.user.profile', cb) // does NOT fire
53
- observer('state.user', cb) // does NOT fire
54
- ```
55
-
56
- This is the **exact-path observer** model. It is intentional:
57
- coarse-grained ancestor notification is handled by `useObserver`'s
58
- auto-discovery mode (which explicitly registers on each accessed path),
59
- not by event bubbling.
60
-
61
- ### 2. Deep mutation notification
62
-
63
- When `state.user.profile.name = "Alice"` executes, the set trap fires
64
- on the `name` property of the `profile` proxy. The callback receives
65
- `{ path: 'user.profile.name', ... }`. The event dispatched is
66
- `state.user.profile.name`. Only the leaf observer fires.
67
-
68
- ### 3. Array mutations
69
-
70
- Array mutations at index `i` dispatch on the **array path**
71
- (`state.items`), not the index path. This is because array identity
72
- matters for UI reconciliation (React list rendering).
73
-
74
- ```js
75
- state.items.push(3) // dispatches 'state.items'
76
- state.items[0] = 99 // dispatches 'state.items'
77
- state.items.sort() // dispatches 'state.items'
78
- ```
79
-
80
- ### 4. Reference identity
81
-
82
- Observer callbacks receive the event object. The callback function
83
- itself is stored by reference in the dispatch layer. Re-registering the
84
- same function on the same path replaces the old listener (single-slot
85
- behavior in `observer`, multi-subscriber in `dispatch.listen`).
86
-
87
- `useObserver` deduplicates identical path registrations.
88
-
89
- ### 5. Synchronous vs asynchronous
90
-
91
- - **Event dispatch** (`dispatch.set`) is **synchronous** —
92
- `globalThis.dispatchEvent` runs listeners inline.
93
- - **Callback notification** via `dispatch.listen` is **asynchronous**
94
- — wrapped in `queueMicrotask(cb)` (or `Promise.resolve().then(cb)`
95
- as fallback). This ensures that by the time the callback fires, the
96
- state Proxy has already committed the mutation.
97
-
98
- This means:
99
-
100
- ```js
101
- state.counter = 1
102
- // state.counter === 1 is TRUE here (mutation already committed)
103
- // observer callback hasn't run yet (next microtask)
104
- ```
105
-
106
- ### 6. Batching
107
-
108
- There is **no automatic batching** of observer callbacks. Each mutation
109
- dispatches its own microtask. Multiple synchronous mutations in a
110
- transaction produce multiple notifications — one per mutation.
111
-
112
- A future scheduler (ADR-005) may introduce microtask/raf batching for
113
- UI frameworks, but the core engine does not coalesce notifications.
114
-
115
- ### 7. Ordering guarantee
116
-
117
- When multiple observers listen to the same path, they fire in
118
- **registration order** (FIFO). The dispatch layer maintains a list of
119
- handlers per event name.
120
-
121
- When multiple mutations occur synchronously (without history), they
122
- dispatch in mutation order. When history is enabled, the undo/redo
123
- stack preserves mutation order via array index.
124
-
125
- ### 8. Observer mutating state
126
-
127
- An observer callback **may** mutate state. Because notifications are
128
- asynchronous (microtask), the mutation triggers a new dispatch cycle
129
- with its own microtask. There is no immediate re-entrancy. However,
130
- deeply recursive state mutations from observers are considered a
131
- application-level bug and are **not** guarded at the engine level.
132
-
133
- ### 9. Observer throwing
134
-
135
- If an observer callback throws, the error propagates as an unhandled
136
- rejection (since the callback runs inside a microtask). The dispatch
137
- layer does **not** catch or swallow errors. A throwing observer does
138
- not prevent other observers on the same path from firing (each
139
- callback is wrapped in its own microtask boundary via
140
- `Promise.resolve().then`).
141
-
142
- ### 10. Observer removed during dispatch
143
-
144
- Since dispatch is asynchronous (microtask), calling
145
- `dispatch.remove(path)` or `observer.remove(path)` during a callback
146
- removes the listener from subsequent dispatches. The current dispatch
147
- cycle is unaffected — all handlers registered at dispatch time fire.
148
-
149
- ### 11. Transactions and observer notification
150
-
151
- During a transaction, each mutation inside the transaction dispatches
152
- normally. The transaction grouping does not suppress notifications.
153
- However, `undo()` and `redo()` temporarily disable history recording
154
- (via `internal.historyEnabled = false`), and the inverse/forward
155
- operations fire the proxy callback — which will dispatch to observers.
156
-
157
- A future enhancement may batch observer notifications for all mutations
158
- within a transaction into a single synthetic notification. This ADR
159
- does **not** define that behavior yet.
160
-
161
- ## Consequences
162
-
163
- - **Positive:** Deterministic, FIFO-ordered, asynchronous notification
164
- gives observers a consistent view of state on every call.
165
- - **Positive:** Microtask scheduling avoids "state not yet committed"
166
- bugs that plague synchronous observer models.
167
- - **Positive:** Exact-path matching means observers fire only when the
168
- specific watched path changes — no spurious re-renders.
169
- - **Positive:** No batching in the core engine keeps it simple and
170
- framework-agnostic. Batching is a scheduler-layer concern.
171
- - **Negative:** Multiple synchronous mutations produce multiple
172
- microtask notifications — consumers that need coalescing must
173
- implement it (or use the future scheduler).
174
- - **Negative:** Throwing observers produce unhandled rejections rather
175
- than being caught — this is intentional (fail fast) but may surprise
176
- consumers expecting error isolation.
177
-
178
- ## Compliance tests
179
-
180
- - `tests/vitest/tests/contracts/adr-002-observer.test.ts`
1
+ # ADR-002: Observer Semantics
2
+
3
+ > **Status:** Proposed
4
+ > **Date:** 2026-09-12
5
+
6
+ ## Context
7
+
8
+ Memorio must freeze observer behavior so that developers can reason
9
+ about *when* and *why* their callbacks fire. The observer system is the
10
+ bridge between state mutations and UI updates, between the Mutation
11
+ Engine and the reactive system. Before adding advanced features (impact
12
+ analysis, simulation, causal graph), the observer contract must be
13
+ explicit and deterministic.
14
+
15
+ The following questions must have explicit answers:
16
+
17
+ 1. Does a deep mutation notify the leaf observer?
18
+ 2. Does a deep mutation notify parent observers?
19
+ 3. What happens for array mutations?
20
+ 4. What is the reference identity contract for observer callbacks?
21
+ 5. Are notifications synchronous or asynchronous?
22
+ 6. Are multiple mutations batched into a single notification?
23
+ 7. What is the ordering guarantee when multiple observers listen to
24
+ overlapping paths?
25
+ 8. Can an observer mutate state synchronously?
26
+ 9. What happens when an observer throws?
27
+ 10. What happens when an observer is removed during dispatch?
28
+ 11. How are nested transactions handled with respect to observer
29
+ notification?
30
+
31
+ ## Assumptions
32
+
33
+ - The state Proxy fires exactly one callback per `set` or `delete`
34
+ trap (no double-firing for the same logical mutation).
35
+ - The dispatch layer (`core/dispatch.ts`) is the sole event bus.
36
+ - `useObserver` is a thin wrapper over `observer` + `dispatch.listen`.
37
+ - History tracking is opt-in (`enableHistory(true)`).
38
+
39
+ ## Decision
40
+
41
+ ### 1. Leaf notification
42
+
43
+ A mutation at path `state.user.profile.name` dispatches an event on
44
+ exactly the path `state.user.profile.name`. Only observers registered
45
+ on that exact path receive the notification. Observers on ancestor
46
+ paths (`state.user.profile`, `state.user`, `state`) do **not** fire for
47
+ a leaf mutation unless the mutation replaces the ancestor itself.
48
+
49
+ ```js
50
+ observer('state.user.profile.name', cb) // fires
51
+ observer('state.user.profile', cb) // does NOT fire
52
+ observer('state.user', cb) // does NOT fire
53
+ ```
54
+
55
+ This is the **exact-path observer** model. It is intentional:
56
+ coarse-grained ancestor notification is handled by `useObserver`'s
57
+ auto-discovery mode (which explicitly registers on each accessed path),
58
+ not by event bubbling.
59
+
60
+ ### 2. Deep mutation notification
61
+
62
+ When `state.user.profile.name = "Alice"` executes, the set trap fires
63
+ on the `name` property of the `profile` proxy. The callback receives
64
+ `{ path: 'user.profile.name', ... }`. The event dispatched is
65
+ `state.user.profile.name`. Only the leaf observer fires.
66
+
67
+ ### 3. Array mutations
68
+
69
+ Array mutations at index `i` dispatch on the **array path**
70
+ (`state.items`), not the index path. This is because array identity
71
+ matters for UI reconciliation (React list rendering).
72
+
73
+ ```js
74
+ state.items.push(3) // dispatches 'state.items'
75
+ state.items[0] = 99 // dispatches 'state.items'
76
+ state.items.sort() // dispatches 'state.items'
77
+ ```
78
+
79
+ ### 4. Reference identity
80
+
81
+ Observer callbacks receive the event object. The callback function
82
+ itself is stored by reference in the dispatch layer. Re-registering the
83
+ same function on the same path replaces the old listener (single-slot
84
+ behavior in `observer`, multi-subscriber in `dispatch.listen`).
85
+
86
+ `useObserver` deduplicates identical path registrations.
87
+
88
+ ### 5. Synchronous vs asynchronous
89
+
90
+ - **Event dispatch** (`dispatch.set`) is **synchronous** -
91
+ `globalThis.dispatchEvent` runs listeners inline.
92
+ - **Callback notification** via `dispatch.listen` is **asynchronous**
93
+ - wrapped in `queueMicrotask(cb)` (or `Promise.resolve().then(cb)`
94
+ as fallback). This ensures that by the time the callback fires, the
95
+ state Proxy has already committed the mutation.
96
+
97
+ This means:
98
+
99
+ ```js
100
+ state.counter = 1
101
+ // state.counter === 1 is TRUE here (mutation already committed)
102
+ // observer callback hasn't run yet (next microtask)
103
+ ```
104
+
105
+ ### 6. Batching
106
+
107
+ There is **no automatic batching** of observer callbacks. Each mutation
108
+ dispatches its own microtask. Multiple synchronous mutations in a
109
+ transaction produce multiple notifications - one per mutation.
110
+
111
+ A future scheduler (ADR-005) may introduce microtask/raf batching for
112
+ UI frameworks, but the core engine does not coalesce notifications.
113
+
114
+ ### 7. Ordering guarantee
115
+
116
+ When multiple observers listen to the same path, they fire in
117
+ **registration order** (FIFO). The dispatch layer maintains a list of
118
+ handlers per event name.
119
+
120
+ When multiple mutations occur synchronously (without history), they
121
+ dispatch in mutation order. When history is enabled, the undo/redo
122
+ stack preserves mutation order via array index.
123
+
124
+ ### 8. Observer mutating state
125
+
126
+ An observer callback **may** mutate state. Because notifications are
127
+ asynchronous (microtask), the mutation triggers a new dispatch cycle
128
+ with its own microtask. There is no immediate re-entrancy. However,
129
+ deeply recursive state mutations from observers are considered a
130
+ application-level bug and are **not** guarded at the engine level.
131
+
132
+ ### 9. Observer throwing
133
+
134
+ If an observer callback throws, the error propagates as an unhandled
135
+ rejection (since the callback runs inside a microtask). The dispatch
136
+ layer does **not** catch or swallow errors. A throwing observer does
137
+ not prevent other observers on the same path from firing (each
138
+ callback is wrapped in its own microtask boundary via
139
+ `Promise.resolve().then`).
140
+
141
+ ### 10. Observer removed during dispatch
142
+
143
+ Since dispatch is asynchronous (microtask), calling
144
+ `dispatch.remove(path)` or `observer.remove(path)` during a callback
145
+ removes the listener from subsequent dispatches. The current dispatch
146
+ cycle is unaffected - all handlers registered at dispatch time fire.
147
+
148
+ ### 11. Transactions and observer notification
149
+
150
+ During a transaction, each mutation inside the transaction dispatches
151
+ normally. The transaction grouping does not suppress notifications.
152
+ However, `undo()` and `redo()` temporarily disable history recording
153
+ (via `internal.historyEnabled = false`), and the inverse/forward
154
+ operations fire the proxy callback - which will dispatch to observers.
155
+
156
+ A future enhancement may batch observer notifications for all mutations
157
+ within a transaction into a single synthetic notification. This ADR
158
+ does **not** define that behavior yet.
159
+
160
+ ## Consequences
161
+
162
+ - **Positive:** Deterministic, FIFO-ordered, asynchronous notification
163
+ gives observers a consistent view of state on every call.
164
+ - **Positive:** Microtask scheduling avoids "state not yet committed"
165
+ bugs that plague synchronous observer models.
166
+ - **Positive:** Exact-path matching means observers fire only when the
167
+ specific watched path changes - no spurious re-renders.
168
+ - **Positive:** No batching in the core engine keeps it simple and
169
+ framework-agnostic. Batching is a scheduler-layer concern.
170
+ - **Negative:** Multiple synchronous mutations produce multiple
171
+ microtask notifications - consumers that need coalescing must
172
+ implement it (or use the future scheduler).
173
+ - **Negative:** Throwing observers produce unhandled rejections rather
174
+ than being caught - this is intentional (fail fast) but may surprise
175
+ consumers expecting error isolation.
176
+
177
+ ## Compliance tests
178
+
179
+ - `tests/vitest/tests/contracts/adr-002-observer.test.ts`
@@ -2,7 +2,6 @@
2
2
 
3
3
  > **Status:** Proposed
4
4
  > **Date:** 2026-09-12
5
- > **Deciders:** Memorio 5.x Core Team
6
5
 
7
6
  ## Context
8
7
 
@@ -11,7 +10,7 @@ common source of subtle bugs in proxy-based state libraries. The
11
10
  4.7.3 baseline had forensic defects around deep mutation notifications:
12
11
  observers not firing, paths not resolved correctly, payloads missing
13
12
  arguments. While these defects were reported as not reproducible in
14
- repo v4.9.10, the semantics remain **undocumented** — they rely on
13
+ repo v5.1.0, the semantics remain **undocumented** - they rely on
15
14
  accidental Proxy behavior rather than an explicit contract.
16
15
 
17
16
  This ADR makes the deep mutation semantics explicit.
@@ -20,7 +19,7 @@ This ADR makes the deep mutation semantics explicit.
20
19
 
21
20
  - `buildProxy` recursively wraps nested plain objects and arrays.
22
21
  - Each Proxy tracks its dotted path via the `tree` array.
23
- - The set trap computes `path = objPath(key, tree)` — a dotted path
22
+ - The set trap computes `path = objPath(key, tree)` - a dotted path
24
23
  relative to `state` (e.g. `user.profile.name`).
25
24
  - The callback receives `{ action, path, newValue, previousValue }`.
26
25
  - The proxy stores **raw** values (via `deepRaw`) on the raw target.
@@ -59,7 +58,7 @@ and a consumer writes `state.user.profile = { name: "Alice" }`:
59
58
  2. The full object `{ name: "Alice" }` is stored as the raw value.
60
59
  3. A new proxy wrapper is created for `{ name: "Alice" }` on the next
61
60
  read.
62
- 4. The event dispatched is `state.user.profile` — **not**
61
+ 4. The event dispatched is `state.user.profile` - **not**
63
62
  `state.user.profile.name`.
64
63
 
65
64
  There is **no automatic intermediate object creation** during deep
@@ -113,15 +112,15 @@ When a nested object inside an array is mutated (`state.items[0].name = "X"`):
113
112
 
114
113
  ## Consequences
115
114
 
116
- - **Positive:** Deep mutations are deterministic — exactly one event
115
+ - **Positive:** Deep mutations are deterministic - exactly one event
117
116
  per mutation, on the exact leaf path.
118
- - **Positive:** Reference identity is preserved — the same proxy wrapper
117
+ - **Positive:** Reference identity is preserved - the same proxy wrapper
119
118
  is reused, preventing proxy-depth accumulation (regression tested).
120
119
  - **Positive:** `memorio.mutate()` provides a safe path-based API for
121
120
  setting deeply nested values that don't exist yet.
122
121
  - **Negative:** Array element object mutations don't notify array-path
123
- observers — consumers must observe the specific element path.
124
- - **Negative:** No-op set traps still dispatch events — this is
122
+ observers - consumers must observe the specific element path.
123
+ - **Negative:** No-op set traps still dispatch events - this is
125
124
  intentional for consistency but may cause unnecessary re-renders.
126
125
 
127
126
  ## Compliance tests