@pumped-fn/lite 1.11.2 → 1.11.4
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/CHANGELOG.md +20 -0
- package/PATTERNS.md +182 -299
- package/README.md +166 -378
- package/dist/index.cjs +19 -18
- package/dist/index.d.cts +36 -9
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +36 -9
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +19 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @pumped-fn/lite
|
|
2
2
|
|
|
3
|
+
## 1.11.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a3ae2b7: Replace text glossary with mermaid sequence diagrams in documentation
|
|
8
|
+
|
|
9
|
+
- README.md now uses visual diagrams for composition, atom lifecycle, tag resolution, type utilities, and API surface
|
|
10
|
+
- PATTERNS.md converted all usage patterns to sequence diagrams for clarity
|
|
11
|
+
|
|
12
|
+
## 1.11.3
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- eda1154: Extend preset() to support Flow in addition to Atom
|
|
17
|
+
|
|
18
|
+
- `preset(flow, fn)` - replacement function bypasses deps resolution (mock scenario)
|
|
19
|
+
- `preset(flow, otherFlow)` - delegates parse/deps/factory entirely to replacement
|
|
20
|
+
- Self-preset throws at creation time
|
|
21
|
+
- Extensions wrap both preset variants
|
|
22
|
+
|
|
3
23
|
## 1.11.2
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/PATTERNS.md
CHANGED
|
@@ -1,380 +1,263 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Patterns
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Usage patterns as sequences. For API details, see `packages/lite/dist/index.d.mts`.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## A. Fundamental Usage
|
|
6
6
|
|
|
7
7
|
### Request Lifecycle
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
| GoF Pattern | Primitive |
|
|
12
|
-
|-------------|-----------|
|
|
13
|
-
| IoC Container | `Scope` (long-lived, caches atoms) |
|
|
14
|
-
| Command | `Flow` (operations within request) |
|
|
15
|
-
| Composite | `ExecutionContext` (parent-child with isolated data) |
|
|
16
|
-
|
|
17
|
-
**Key Insight:**
|
|
18
|
-
- `Scope` = application container (atoms cached here)
|
|
19
|
-
- `ExecutionContext` = request boundary (data lives here, closed at request end)
|
|
20
|
-
- `Flow` / `ctx.exec` = operations within the request (share context via `seekTag`)
|
|
9
|
+
Model a request boundary with cleanup and shared context.
|
|
21
10
|
|
|
22
11
|
```mermaid
|
|
23
12
|
sequenceDiagram
|
|
24
13
|
participant App
|
|
25
14
|
participant Scope
|
|
26
|
-
participant
|
|
27
|
-
participant
|
|
28
|
-
participant Flow1 as Flow (validate)
|
|
29
|
-
participant Flow2 as Flow (process)
|
|
30
|
-
|
|
31
|
-
Note over Scope: Long-lived (app lifetime)
|
|
32
|
-
App->>Scope: resolve(serviceAtom)
|
|
33
|
-
Scope-->>App: service (cached)
|
|
15
|
+
participant Ctx as ExecutionContext
|
|
16
|
+
participant Flow
|
|
34
17
|
|
|
35
|
-
|
|
36
|
-
App->>Scope: createContext({ tags
|
|
18
|
+
App->>Scope: createScope()
|
|
19
|
+
App->>Scope: scope.createContext({ tags })
|
|
37
20
|
Scope-->>App: ctx
|
|
38
21
|
|
|
39
|
-
App->>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
Scope-->>Context: deps (cached in scope)
|
|
45
|
-
Context->>Flow1: factory(childCtx, deps)
|
|
46
|
-
Note over Flow1: childCtx.parent = ctx
|
|
47
|
-
Flow1->>Flow1: tags.required(userIdTag) from merged tags
|
|
48
|
-
Flow1-->>Context: validated
|
|
49
|
-
|
|
50
|
-
App->>Context: ctx.exec({ flow: processFlow, input: validated })
|
|
51
|
-
Context->>Scope: resolve flow deps (atoms)
|
|
52
|
-
Scope-->>Context: deps (from cache)
|
|
53
|
-
Context->>Flow2: factory(childCtx, deps)
|
|
54
|
-
Flow2->>Flow2: childCtx.exec({ fn: service.save, params: [data] })
|
|
55
|
-
Note over Flow2: Creates grandchildCtx
|
|
56
|
-
Flow2->>ServiceAtom: service.save(grandchildCtx, data)
|
|
57
|
-
ServiceAtom->>ServiceAtom: grandchildCtx.data.seekTag(TX_TAG)
|
|
58
|
-
Note over ServiceAtom: seekTag traverses parent chain
|
|
59
|
-
ServiceAtom-->>Flow2: saved
|
|
60
|
-
Flow2-->>Context: result
|
|
61
|
-
|
|
62
|
-
App->>Context: ctx.data.getTag(TX_TAG).commit()
|
|
63
|
-
App->>Context: ctx.close()
|
|
64
|
-
Context->>Context: onClose cleanups run (rollback skipped)
|
|
65
|
-
```
|
|
22
|
+
App->>Ctx: ctx.exec({ flow, input, tags })
|
|
23
|
+
Ctx->>Flow: factory(childCtx, deps)
|
|
24
|
+
Flow-->>Ctx: output
|
|
25
|
+
Ctx->>Ctx: childCtx.close()
|
|
26
|
+
Ctx-->>App: output
|
|
66
27
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
- `seekTag()` traverses parent chain for shared data (e.g., transaction)
|
|
72
|
-
- `ctx.close()` runs all `onClose` cleanups (LIFO order)
|
|
73
|
-
- On error: child context auto-closes, cleanups still run
|
|
28
|
+
App->>Ctx: ctx.onClose(cleanup)
|
|
29
|
+
App->>Ctx: ctx.close()
|
|
30
|
+
Ctx->>Ctx: run cleanups (LIFO)
|
|
31
|
+
```
|
|
74
32
|
|
|
75
|
-
|
|
33
|
+
### Extensions Pipeline
|
|
76
34
|
|
|
77
|
-
|
|
35
|
+
Observe and wrap timing for atoms/flows (logging, auth, tracing).
|
|
78
36
|
|
|
79
|
-
|
|
37
|
+
```mermaid
|
|
38
|
+
sequenceDiagram
|
|
39
|
+
participant App
|
|
40
|
+
participant Scope
|
|
41
|
+
participant Ext as Extension
|
|
42
|
+
participant Atom
|
|
43
|
+
participant Flow
|
|
80
44
|
|
|
81
|
-
|
|
45
|
+
App->>Scope: createScope({ extensions: [ext] })
|
|
46
|
+
Scope->>Ext: ext.init(scope)
|
|
47
|
+
App->>Scope: await scope.ready
|
|
48
|
+
|
|
49
|
+
App->>Scope: resolve(atom)
|
|
50
|
+
Scope->>Ext: wrapResolve(next, atom, scope)
|
|
51
|
+
Ext->>Ext: before logic
|
|
52
|
+
Ext->>Atom: next()
|
|
53
|
+
Atom-->>Ext: value
|
|
54
|
+
Ext->>Ext: after logic
|
|
55
|
+
Ext-->>Scope: value
|
|
56
|
+
|
|
57
|
+
App->>Scope: ctx.exec({ flow })
|
|
58
|
+
Scope->>Ext: wrapExec(next, flow, ctx)
|
|
59
|
+
Ext->>Flow: next()
|
|
60
|
+
Flow-->>Ext: output
|
|
61
|
+
Ext-->>Scope: output
|
|
62
|
+
|
|
63
|
+
App->>Scope: dispose()
|
|
64
|
+
Scope->>Ext: ext.dispose(scope)
|
|
65
|
+
```
|
|
82
66
|
|
|
83
|
-
|
|
84
|
-
|---------|-----------|
|
|
85
|
-
| Dependency injection | `deps` (atoms from Scope, tags from context hierarchy) |
|
|
86
|
-
| Service invocation | `ctx.exec({ fn, params })` (observable by extensions) |
|
|
87
|
-
| Resource cleanup | `ctx.onClose()` (LIFO, runs on success or failure) |
|
|
67
|
+
### Scoped Isolation + Testing
|
|
88
68
|
|
|
89
|
-
|
|
69
|
+
Swap implementations and isolate tenants/tests.
|
|
90
70
|
|
|
91
71
|
```mermaid
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
72
|
+
sequenceDiagram
|
|
73
|
+
participant Test
|
|
74
|
+
participant Scope
|
|
75
|
+
participant Atom
|
|
96
76
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
77
|
+
Test->>Scope: createScope({ presets: [preset(dbAtom, mockDb)], tags: [tenantTag(id)] })
|
|
78
|
+
Test->>Scope: resolve(dbAtom)
|
|
79
|
+
Scope-->>Test: mockDb (not real db)
|
|
100
80
|
|
|
101
|
-
|
|
102
|
-
|
|
81
|
+
Test->>Scope: createContext()
|
|
82
|
+
Scope-->>Test: ctx with tenantTag
|
|
83
|
+
```
|
|
103
84
|
|
|
104
|
-
|
|
105
|
-
CtxHierarchy --> |"traverses parent chain"| ResolvedTag["userId value"]
|
|
106
|
-
end
|
|
85
|
+
## B. Advanced Client/State Usage
|
|
107
86
|
|
|
108
|
-
|
|
109
|
-
ResolvedAtom --> DepsObj["deps object"]
|
|
110
|
-
ResolvedTag --> DepsObj
|
|
111
|
-
end
|
|
112
|
-
```
|
|
87
|
+
### Controller Reactivity
|
|
113
88
|
|
|
114
|
-
|
|
89
|
+
Client-side state with lifecycle hooks and invalidation.
|
|
115
90
|
|
|
116
91
|
```mermaid
|
|
117
92
|
sequenceDiagram
|
|
118
|
-
participant
|
|
119
|
-
participant
|
|
120
|
-
participant
|
|
121
|
-
participant
|
|
122
|
-
|
|
123
|
-
Note over Flow: ❌ Direct call
|
|
124
|
-
Flow->>Fn: service.method(ctx, data)
|
|
125
|
-
Note over Fn: Extensions cannot observe
|
|
126
|
-
|
|
127
|
-
Note over Flow: ✅ Via ctx.exec
|
|
128
|
-
Flow->>Ctx: ctx.exec({ fn: service.method, params: [data] })
|
|
129
|
-
Ctx->>Ctx: create childCtx (parent = ctx)
|
|
130
|
-
Ctx->>Ext: wrapExec(next, fn, childCtx)
|
|
131
|
-
Ext->>Fn: next()
|
|
132
|
-
Fn-->>Ext: result
|
|
133
|
-
Ext-->>Ctx: result
|
|
134
|
-
Ctx->>Ctx: childCtx.close()
|
|
135
|
-
Ctx-->>Flow: result
|
|
136
|
-
```
|
|
93
|
+
participant App
|
|
94
|
+
participant Scope
|
|
95
|
+
participant Ctrl as Controller
|
|
96
|
+
participant Atom
|
|
137
97
|
|
|
138
|
-
|
|
98
|
+
App->>Scope: controller(atom)
|
|
99
|
+
Scope-->>App: ctrl
|
|
139
100
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
participant Flow
|
|
143
|
-
participant Ctx as ExecutionContext
|
|
144
|
-
participant Tx as Transaction
|
|
145
|
-
|
|
146
|
-
Flow->>Tx: beginTransaction()
|
|
147
|
-
Tx-->>Flow: tx
|
|
148
|
-
Flow->>Ctx: ctx.onClose(() => tx.rollback())
|
|
149
|
-
|
|
150
|
-
alt Success
|
|
151
|
-
Flow->>Flow: do work
|
|
152
|
-
Flow->>Tx: tx.commit()
|
|
153
|
-
Flow->>Ctx: return result
|
|
154
|
-
Ctx->>Ctx: close() → rollback() is no-op
|
|
155
|
-
else Error
|
|
156
|
-
Flow->>Flow: do work (throws)
|
|
157
|
-
Ctx->>Ctx: close() → rollback() executes
|
|
158
|
-
Ctx->>Tx: tx.rollback()
|
|
159
|
-
end
|
|
160
|
-
```
|
|
101
|
+
App->>Ctrl: ctrl.on('resolving' | 'resolved' | '*', listener)
|
|
102
|
+
Ctrl-->>App: unsubscribe
|
|
161
103
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
- Tag deps resolve via `ctx.data.seekTag()` (traverses parent → grandparent → scope tags)
|
|
165
|
-
- `ctx.exec({ fn, params })` creates child context with isolated `data` Map
|
|
166
|
-
- Extensions intercept via `wrapExec(next, target, ctx)`
|
|
167
|
-
- Register pessimistic cleanup via `ctx.onClose(fn)`, neutralize on success
|
|
104
|
+
App->>Ctrl: ctrl.get()
|
|
105
|
+
Ctrl-->>App: current value
|
|
168
106
|
|
|
169
|
-
|
|
107
|
+
App->>Ctrl: ctrl.set(newValue)
|
|
108
|
+
Ctrl->>Ctrl: notify listeners
|
|
170
109
|
|
|
171
|
-
|
|
110
|
+
App->>Ctrl: ctrl.update(v => v + 1)
|
|
111
|
+
Ctrl->>Ctrl: notify listeners
|
|
172
112
|
|
|
173
|
-
|
|
113
|
+
App->>Ctrl: ctrl.invalidate()
|
|
114
|
+
Ctrl->>Atom: re-run factory
|
|
115
|
+
Ctrl->>Ctrl: notify listeners
|
|
116
|
+
```
|
|
174
117
|
|
|
175
|
-
|
|
118
|
+
### Ambient Context (Tags)
|
|
176
119
|
|
|
177
|
-
|
|
178
|
-
|-------------|-----------|
|
|
179
|
-
| Command | `Flow` (encapsulates request with input/output) |
|
|
180
|
-
| Interceptor | `Extension.wrapExec()` (wraps execution) |
|
|
181
|
-
| Context Object | `Tag` (propagates metadata without explicit passing) |
|
|
120
|
+
Propagate state without wiring parameters (app shell, user, locale).
|
|
182
121
|
|
|
183
122
|
```mermaid
|
|
184
123
|
sequenceDiagram
|
|
185
|
-
participant
|
|
186
|
-
participant
|
|
187
|
-
participant
|
|
188
|
-
participant
|
|
189
|
-
participant Flow
|
|
190
|
-
participant Context as ExecutionContext
|
|
191
|
-
|
|
192
|
-
Client->>Scope: createContext({ tags: [requestId] })
|
|
193
|
-
Scope-->>Client: ctx
|
|
194
|
-
Client->>Context: exec({ flow, input, tags: [userId] })
|
|
195
|
-
|
|
196
|
-
Context->>Context: merge tags (flow → scope → context → exec)
|
|
197
|
-
Context->>Context: create child context
|
|
198
|
-
|
|
199
|
-
Context->>Extension1: wrapExec(next, flow, childCtx)
|
|
200
|
-
Extension1->>Extension1: extract userId tag, validate
|
|
201
|
-
Extension1->>Extension2: next()
|
|
202
|
-
Extension2->>Extension2: read parent span from ctx.parent?.data
|
|
203
|
-
Extension2->>Extension2: create child span, store in ctx.data
|
|
204
|
-
Extension2->>Flow: next()
|
|
205
|
-
Flow->>Flow: factory(ctx, deps) with tags.required(userId)
|
|
206
|
-
Flow-->>Extension2: result
|
|
207
|
-
Extension2->>Extension2: end span
|
|
208
|
-
Extension2-->>Extension1: result
|
|
209
|
-
Extension1-->>Context: result
|
|
210
|
-
Context->>Context: auto-close child (run onClose cleanups)
|
|
211
|
-
Context-->>Client: result
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
**Characteristics:**
|
|
215
|
-
- Extensions wrap in registration order (outer → inner)
|
|
216
|
-
- Each `exec()` creates isolated child context with own `data` Map
|
|
217
|
-
- Tags merge with later sources winning (exec tags override flow tags)
|
|
218
|
-
- Parent chain enables span correlation without AsyncLocalStorage
|
|
124
|
+
participant App
|
|
125
|
+
participant Ctx as ExecutionContext
|
|
126
|
+
participant ChildCtx
|
|
127
|
+
participant Data as ctx.data
|
|
219
128
|
|
|
220
|
-
|
|
129
|
+
App->>Data: ctx.data.setTag(userTag, user)
|
|
130
|
+
App->>Ctx: ctx.exec({ flow, tags: [localeTag('en')] })
|
|
131
|
+
Ctx->>ChildCtx: create with merged tags
|
|
221
132
|
|
|
222
|
-
|
|
133
|
+
ChildCtx->>Data: ctx.data.seekTag(userTag)
|
|
134
|
+
Data-->>ChildCtx: user (from parent)
|
|
223
135
|
|
|
224
|
-
|
|
136
|
+
ChildCtx->>Data: ctx.data.getTag(localeTag)
|
|
137
|
+
Data-->>ChildCtx: 'en'
|
|
138
|
+
```
|
|
225
139
|
|
|
226
|
-
|
|
140
|
+
### Derived State (Select)
|
|
227
141
|
|
|
228
|
-
|
|
229
|
-
|-------------|-----------|
|
|
230
|
-
| IoC Container | `Scope` (manages atom lifecycles and resolution) |
|
|
231
|
-
| Strategy | `Preset` (swap implementations at scope creation) |
|
|
232
|
-
| Composite | `ExecutionContext` (parent-child isolation) |
|
|
142
|
+
Subscribe to a slice of atom state with custom equality.
|
|
233
143
|
|
|
234
144
|
```mermaid
|
|
235
145
|
sequenceDiagram
|
|
236
146
|
participant App
|
|
237
|
-
participant
|
|
238
|
-
participant
|
|
239
|
-
participant
|
|
240
|
-
participant MockDb as mockDbAtom
|
|
241
|
-
|
|
242
|
-
Note over App: Production - Tenant A
|
|
243
|
-
App->>TenantScope: createScope({ tags: [tenantId('A')] })
|
|
244
|
-
App->>TenantScope: resolve(dbAtom)
|
|
245
|
-
TenantScope->>DbAtom: factory(ctx, deps)
|
|
246
|
-
DbAtom->>DbAtom: tags.required(tenantId) → 'A'
|
|
247
|
-
DbAtom-->>TenantScope: TenantA DB connection
|
|
248
|
-
|
|
249
|
-
Note over App: Test - Mocked DB
|
|
250
|
-
App->>TestScope: createScope({ presets: [preset(dbAtom, mockDbAtom)] })
|
|
251
|
-
App->>TestScope: resolve(dbAtom)
|
|
252
|
-
TestScope->>TestScope: check presets → found
|
|
253
|
-
TestScope->>MockDb: resolve mockDbAtom instead
|
|
254
|
-
MockDb-->>TestScope: Mock DB instance
|
|
255
|
-
|
|
256
|
-
Note over App: Parallel tenant contexts
|
|
257
|
-
par Tenant A request
|
|
258
|
-
TenantScope->>TenantScope: createContext({ tags: [requestId('r1')] })
|
|
259
|
-
and Tenant B request
|
|
260
|
-
TenantScope->>TenantScope: createContext({ tags: [requestId('r2')] })
|
|
261
|
-
end
|
|
262
|
-
Note over TenantScope: Each context isolated, same scope
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
**Characteristics:**
|
|
266
|
-
- Each Scope is an isolated DI container with own cache
|
|
267
|
-
- Presets swap atom implementations without changing definitions
|
|
268
|
-
- Tags at scope level apply to all resolutions
|
|
269
|
-
- Multiple ExecutionContexts share scope but isolate request data
|
|
270
|
-
- Child contexts inherit parent tags, can override
|
|
147
|
+
participant Scope
|
|
148
|
+
participant Handle as SelectHandle
|
|
149
|
+
participant Atom
|
|
271
150
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
- Testing: preset mocks without touching production atom definitions
|
|
275
|
-
- Feature flags: preset alternative implementations per environment
|
|
151
|
+
App->>Scope: select(atom, v => v.count, { eq: shallowEqual })
|
|
152
|
+
Scope-->>App: handle
|
|
276
153
|
|
|
277
|
-
|
|
154
|
+
App->>Handle: handle.get()
|
|
155
|
+
Handle-->>App: selected value
|
|
278
156
|
|
|
279
|
-
|
|
157
|
+
App->>Handle: handle.subscribe(listener)
|
|
158
|
+
Handle-->>App: unsubscribe
|
|
280
159
|
|
|
281
|
-
|
|
160
|
+
Note over Atom,Handle: atom changes
|
|
161
|
+
Handle->>Handle: eq(prev, next)?
|
|
162
|
+
Handle->>App: notify if changed
|
|
163
|
+
```
|
|
282
164
|
|
|
283
|
-
###
|
|
165
|
+
### Service Pattern
|
|
284
166
|
|
|
285
|
-
|
|
167
|
+
Constrain atom methods to ExecutionContext-first signature for tracing/auth.
|
|
286
168
|
|
|
287
169
|
```mermaid
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
Cache["Resolution Cache"]
|
|
294
|
-
|
|
295
|
-
Scope -->|resolve| AtomA
|
|
296
|
-
Scope -->|resolve| AtomB
|
|
297
|
-
AtomB -->|deps| AtomA
|
|
298
|
-
AtomC -->|deps| AtomA
|
|
299
|
-
AtomC -->|deps| AtomB
|
|
300
|
-
Scope --- Cache
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
**Primitives:** `createScope()`, `atom()`, `deps`, `scope.resolve()`
|
|
170
|
+
sequenceDiagram
|
|
171
|
+
participant App
|
|
172
|
+
participant Scope
|
|
173
|
+
participant Ctx as ExecutionContext
|
|
174
|
+
participant Svc as Service Atom
|
|
304
175
|
|
|
305
|
-
|
|
176
|
+
App->>Scope: resolve(userService)
|
|
177
|
+
Scope-->>App: { getUser, updateUser }
|
|
306
178
|
|
|
307
|
-
|
|
179
|
+
App->>Ctx: svc.getUser(ctx, userId)
|
|
180
|
+
Ctx->>Svc: traced execution
|
|
181
|
+
Svc-->>Ctx: user
|
|
182
|
+
Ctx-->>App: user
|
|
183
|
+
```
|
|
308
184
|
|
|
309
|
-
###
|
|
185
|
+
### Typed Flow Input
|
|
310
186
|
|
|
311
|
-
|
|
187
|
+
Type flow input without runtime parsing overhead.
|
|
312
188
|
|
|
313
189
|
```mermaid
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
190
|
+
sequenceDiagram
|
|
191
|
+
participant App
|
|
192
|
+
participant Flow
|
|
193
|
+
participant Ctx
|
|
194
|
+
|
|
195
|
+
Note over Flow: flow({ parse: typed<T>(), factory })
|
|
196
|
+
App->>Flow: ctx.exec({ flow, input: typedInput })
|
|
197
|
+
Flow->>Flow: skip parse (type-only)
|
|
198
|
+
Flow->>Ctx: factory(ctx) with ctx.input: T
|
|
199
|
+
Ctx-->>App: output
|
|
324
200
|
```
|
|
325
201
|
|
|
326
|
-
|
|
202
|
+
### Controller as Dependency
|
|
327
203
|
|
|
328
|
-
|
|
204
|
+
Receive reactive handle instead of resolved value in atom/flow deps.
|
|
329
205
|
|
|
330
|
-
|
|
206
|
+
```mermaid
|
|
207
|
+
sequenceDiagram
|
|
208
|
+
participant Scope
|
|
209
|
+
participant AtomA as serverAtom
|
|
210
|
+
participant Ctrl as Controller
|
|
211
|
+
participant AtomB as configAtom
|
|
212
|
+
|
|
213
|
+
Scope->>AtomA: resolve(serverAtom)
|
|
214
|
+
Note over AtomA: deps: { cfg: controller(configAtom, { resolve: true }) }
|
|
215
|
+
AtomA->>Scope: resolve configAtom first
|
|
216
|
+
Scope-->>Ctrl: ctrl (already resolved)
|
|
217
|
+
AtomA->>AtomA: factory(ctx, { cfg: ctrl })
|
|
218
|
+
AtomA->>Ctrl: ctrl.on('resolved', () => ctx.invalidate())
|
|
219
|
+
Note over AtomA: react to config changes
|
|
220
|
+
```
|
|
331
221
|
|
|
332
|
-
###
|
|
222
|
+
### Inline Function Execution
|
|
333
223
|
|
|
334
|
-
|
|
224
|
+
Execute ad-hoc logic within context without defining a flow.
|
|
335
225
|
|
|
336
226
|
```mermaid
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
ScopeTags[Scope tags]
|
|
341
|
-
CtxTags[Context tags]
|
|
342
|
-
ExecTags[Exec tags]
|
|
343
|
-
end
|
|
344
|
-
|
|
345
|
-
subgraph Merge[Tag Merge - later wins]
|
|
346
|
-
FlowTags --> Merged
|
|
347
|
-
ScopeTags --> Merged
|
|
348
|
-
CtxTags --> Merged
|
|
349
|
-
ExecTags --> Merged
|
|
350
|
-
end
|
|
227
|
+
sequenceDiagram
|
|
228
|
+
participant App
|
|
229
|
+
participant Ctx as ExecutionContext
|
|
351
230
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
231
|
+
App->>Ctx: ctx.exec({ fn: (ctx, a, b) => a + b, params: [1, 2], tags })
|
|
232
|
+
Ctx->>Ctx: create childCtx with tags
|
|
233
|
+
Ctx->>Ctx: fn(childCtx, 1, 2)
|
|
234
|
+
Ctx->>Ctx: childCtx.close()
|
|
235
|
+
Ctx-->>App: 3
|
|
357
236
|
```
|
|
358
237
|
|
|
359
|
-
|
|
238
|
+
### Atom Retention (GC)
|
|
360
239
|
|
|
361
|
-
|
|
240
|
+
Control when atoms are garbage collected or kept alive indefinitely.
|
|
362
241
|
|
|
363
|
-
|
|
242
|
+
```mermaid
|
|
243
|
+
sequenceDiagram
|
|
244
|
+
participant App
|
|
245
|
+
participant Scope
|
|
246
|
+
participant Atom
|
|
364
247
|
|
|
365
|
-
|
|
248
|
+
App->>Scope: createScope({ gc: { enabled: true, graceMs: 3000 } })
|
|
366
249
|
|
|
367
|
-
|
|
250
|
+
App->>Scope: resolve(atom)
|
|
251
|
+
Scope-->>App: value
|
|
252
|
+
Note over Scope: no refs → start grace timer
|
|
368
253
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
Presets -->|none| Factory[Run factory]
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
**Primitives:** `preset()`, `createScope({ presets })`, `isPreset()`
|
|
254
|
+
alt keepAlive: true
|
|
255
|
+
Note over Atom: never GC'd
|
|
256
|
+
else graceMs expires
|
|
257
|
+
Scope->>Atom: release()
|
|
258
|
+
Atom->>Atom: run cleanups
|
|
259
|
+
end
|
|
379
260
|
|
|
380
|
-
|
|
261
|
+
App->>Scope: flush()
|
|
262
|
+
Note over Scope: wait all pending
|
|
263
|
+
```
|