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.
- package/README.md +80 -449
- package/SECURITY.md +152 -42
- package/SUMMARY.md +1 -1
- package/adr/001-state-proxy-model.md +95 -96
- package/adr/002-observer-semantics.md +179 -180
- package/adr/003-deep-mutation-semantics.md +7 -8
- package/adr/004-array-mutation-semantics.md +127 -128
- package/adr/005-scheduler-contract.md +148 -149
- package/adr/006-context-isolation.md +91 -92
- package/adr/007-mutation-records.md +5 -6
- package/adr/008-transactions.md +6 -7
- package/adr/009-history-model.md +6 -7
- package/adr/README.md +46 -46
- package/adr/template.md +48 -49
- package/bin/cli.js +68 -0
- package/global.cjs +1462 -323
- package/global.js +1459 -324
- package/index.cjs +1462 -323
- package/index.d.ts +1 -0
- package/index.js +1459 -324
- package/llms.txt +42 -5
- package/markdown/AUDIT-REPORT.md +7 -8
- package/markdown/CACHE.md +190 -99
- package/markdown/DEVTOOLS.md +0 -1
- package/markdown/DISPATCH.md +0 -1
- package/markdown/HISTORY.md +0 -1
- package/markdown/IDB.md +0 -1
- package/markdown/IMPORT.md +0 -1
- package/markdown/INSPECT.md +0 -1
- package/markdown/LOGGER.md +0 -1
- package/markdown/MEMORY-ATTACHMENT.md +0 -1
- package/markdown/MEMORY.md +0 -1
- package/markdown/OBSERVER.md +0 -1
- package/markdown/PLATFORM.md +277 -271
- package/markdown/REDUX.md +54 -0
- package/markdown/SCHEMA.md +0 -1
- package/markdown/SESSION.md +0 -1
- package/markdown/SQLITE.md +0 -1
- package/markdown/STATE.md +0 -1
- package/markdown/STORE.md +0 -1
- package/markdown/SYNC.md +0 -1
- package/markdown/TYPED.md +0 -1
- package/markdown/USEOBSERVER.md +0 -1
- package/modules/redux.cjs +381 -10
- package/modules/redux.cjs.map +1 -1
- package/modules/redux.js +381 -10
- package/modules/redux.js.map +1 -1
- package/package.json +14 -2
- package/types/broadcast.d.ts +61 -0
- package/types/computed.d.ts +96 -0
- package/types/encryption.d.ts +129 -0
- package/types/exports.d.ts +9 -0
- package/types/memorio.d.ts +19 -12
- package/types/security.d.ts +67 -0
- package/types/session.d.ts +23 -5
- package/types/store.d.ts +19 -3
- package/vsix/memorio.vsix +0 -0
- package/markdown/CHANGELOG.md +0 -243
- package/markdown/PROJECT.md +0 -311
- 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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
observer('state.user.profile
|
|
52
|
-
observer('state.user
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
`
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
state.items
|
|
76
|
-
state.items
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
state.counter
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
-
|
|
170
|
-
|
|
171
|
-
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
|
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)`
|
|
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`
|
|
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
|
|
115
|
+
- **Positive:** Deep mutations are deterministic - exactly one event
|
|
117
116
|
per mutation, on the exact leaf path.
|
|
118
|
-
- **Positive:** Reference identity is preserved
|
|
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
|
|
124
|
-
- **Negative:** No-op set traps still dispatch events
|
|
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
|