@pumped-fn/lite 1.11.4 → 2.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @pumped-fn/lite
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - e87f8c9: feat(lite): add `resource()` execution-scoped dependency primitive
8
+
9
+ BREAKING CHANGE: `wrapResolve` extension hook signature changed from `(next, atom, scope)` to `(next, event: ResolveEvent)` where `ResolveEvent` is a discriminated union (`{ kind: "atom" }` or `{ kind: "resource" }`).
10
+
11
+ New `resource({ deps, factory })` primitive for execution-level dependencies (logger, transaction, trace span). Resources are resolved fresh per execution chain, shared via seek-up within nested execs, and cleaned up with `ctx.onClose()`.
12
+
13
+ Migration: update `wrapResolve(next, atom, scope)` → `wrapResolve(next, event)`, dispatch on `event.kind`.
14
+
3
15
  ## 1.11.4
4
16
 
5
17
  ### Patch Changes
package/MIGRATION.md CHANGED
@@ -236,8 +236,8 @@ const loggingExt = extension({
236
236
  // AFTER (lite) - Simplified 4-hook interface
237
237
  const loggingExt: Lite.Extension = {
238
238
  name: 'logging',
239
- wrapResolve: async (next, atom, scope) => {
240
- console.log('Resolving atom...')
239
+ wrapResolve: async (next, event) => {
240
+ console.log('Resolving:', event.kind, event.target)
241
241
  const result = await next()
242
242
  console.log('Resolved:', result)
243
243
  return result
package/PATTERNS.md CHANGED
@@ -22,17 +22,17 @@ sequenceDiagram
22
22
  App->>Ctx: ctx.exec({ flow, input, tags })
23
23
  Ctx->>Flow: factory(childCtx, deps)
24
24
  Flow-->>Ctx: output
25
- Ctx->>Ctx: childCtx.close()
25
+ Ctx->>Ctx: childCtx.close(result)
26
26
  Ctx-->>App: output
27
27
 
28
- App->>Ctx: ctx.onClose(cleanup)
29
- App->>Ctx: ctx.close()
30
- Ctx->>Ctx: run cleanups (LIFO)
28
+ App->>Ctx: ctx.onClose(result => cleanup)
29
+ App->>Ctx: ctx.close(result?)
30
+ Ctx->>Ctx: run onClose(CloseResult) LIFO
31
31
  ```
32
32
 
33
33
  ### Extensions Pipeline
34
34
 
35
- Observe and wrap timing for atoms/flows (logging, auth, tracing).
35
+ Observe and wrap atoms/flows logging, auth, tracing, transaction boundaries. Extensions register `onClose(CloseResult)` to finalize based on success or failure.
36
36
 
37
37
  ```mermaid
38
38
  sequenceDiagram
@@ -40,6 +40,7 @@ sequenceDiagram
40
40
  participant Scope
41
41
  participant Ext as Extension
42
42
  participant Atom
43
+ participant Ctx as ExecutionContext
43
44
  participant Flow
44
45
 
45
46
  App->>Scope: createScope({ extensions: [ext] })
@@ -47,18 +48,19 @@ sequenceDiagram
47
48
  App->>Scope: await scope.ready
48
49
 
49
50
  App->>Scope: resolve(atom)
50
- Scope->>Ext: wrapResolve(next, atom, scope)
51
+ Scope->>Ext: wrapResolve(next, { kind: "atom", target, scope })
51
52
  Ext->>Ext: before logic
52
53
  Ext->>Atom: next()
53
54
  Atom-->>Ext: value
54
55
  Ext->>Ext: after logic
55
56
  Ext-->>Scope: value
56
57
 
57
- App->>Scope: ctx.exec({ flow })
58
- Scope->>Ext: wrapExec(next, flow, ctx)
58
+ App->>Ctx: ctx.exec({ flow })
59
+ Ctx->>Ext: wrapExec(next, flow, childCtx)
60
+ Ext->>Ext: ctx.onClose(result => result.ok ? commit : rollback)
59
61
  Ext->>Flow: next()
60
62
  Flow-->>Ext: output
61
- Ext-->>Scope: output
63
+ Ext-->>Ctx: output
62
64
 
63
65
  App->>Scope: dispose()
64
66
  Scope->>Ext: ext.dispose(scope)
@@ -117,21 +119,30 @@ sequenceDiagram
117
119
 
118
120
  ### Ambient Context (Tags)
119
121
 
120
- Propagate state without wiring parameters (app shell, user, locale).
122
+ Propagate values without wiring parameters. Tags serve two roles: scope-level config (consumed by atoms via `tags.required()`) and per-context ambient data (requestId, locale). Use `tags.required()` in deps to declare that an atom or flow needs an ambient value (e.g., a transacted connection) — extensions or context setup provide the value, the consumer just depends on it.
121
123
 
122
124
  ```mermaid
123
125
  sequenceDiagram
124
126
  participant App
127
+ participant Scope
128
+ participant Atom
125
129
  participant Ctx as ExecutionContext
126
130
  participant ChildCtx
127
131
  participant Data as ctx.data
128
132
 
129
- App->>Data: ctx.data.setTag(userTag, user)
133
+ App->>Scope: createScope({ tags: [configTag(cfg)] })
134
+ App->>Scope: resolve(dbAtom)
135
+ Note right of Atom: deps: { config: tags.required(configTag) }
136
+ Scope->>Atom: factory(ctx, { config: cfg })
137
+
138
+ App->>Scope: scope.createContext({ tags: [requestIdTag(rid)] })
139
+ Scope-->>App: ctx
140
+
130
141
  App->>Ctx: ctx.exec({ flow, tags: [localeTag('en')] })
131
142
  Ctx->>ChildCtx: create with merged tags
132
143
 
133
- ChildCtx->>Data: ctx.data.seekTag(userTag)
134
- Data-->>ChildCtx: user (from parent)
144
+ ChildCtx->>Data: ctx.data.seekTag(requestIdTag)
145
+ Data-->>ChildCtx: rid (from parent)
135
146
 
136
147
  ChildCtx->>Data: ctx.data.getTag(localeTag)
137
148
  Data-->>ChildCtx: 'en'
@@ -164,22 +175,25 @@ sequenceDiagram
164
175
 
165
176
  ### Service Pattern
166
177
 
167
- Constrain atom methods to ExecutionContext-first signature for tracing/auth.
178
+ Constrain atom methods to ExecutionContext-first signature. Always invoke via `ctx.exec` so a child context is created — extensions can observe the call, and cleanup is scoped.
168
179
 
169
180
  ```mermaid
170
181
  sequenceDiagram
171
182
  participant App
172
183
  participant Scope
173
184
  participant Ctx as ExecutionContext
185
+ participant Child as ChildContext
174
186
  participant Svc as Service Atom
175
187
 
176
188
  App->>Scope: resolve(userService)
177
189
  Scope-->>App: { getUser, updateUser }
178
190
 
179
- App->>Ctx: svc.getUser(ctx, userId)
180
- Ctx->>Svc: traced execution
181
- Svc-->>Ctx: user
182
- Ctx-->>App: user
191
+ App->>Ctx: ctx.exec({ fn: svc.getUser, params: [userId] })
192
+ Ctx->>Child: create child context
193
+ Child->>Svc: getUser(childCtx, userId)
194
+ Svc-->>Child: user
195
+ Child->>Child: close(result)
196
+ Child-->>App: user
183
197
  ```
184
198
 
185
199
  ### Typed Flow Input
@@ -189,14 +203,17 @@ Type flow input without runtime parsing overhead.
189
203
  ```mermaid
190
204
  sequenceDiagram
191
205
  participant App
206
+ participant Ctx as ExecutionContext
207
+ participant Child as ChildContext
192
208
  participant Flow
193
- participant Ctx
194
209
 
195
210
  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
211
+ App->>Ctx: ctx.exec({ flow, input: typedInput })
212
+ Ctx->>Child: create child (input passed through, no parse)
213
+ Child->>Flow: factory(childCtx, deps) with ctx.input: T
214
+ Flow-->>Child: output
215
+ Child->>Child: close(result)
216
+ Child-->>App: output
200
217
  ```
201
218
 
202
219
  ### Controller as Dependency
@@ -228,11 +245,12 @@ sequenceDiagram
228
245
  participant App
229
246
  participant Ctx as ExecutionContext
230
247
 
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
248
+ App->>Ctx: ctx.exec({ name, fn, params, tags })
249
+ Ctx->>Ctx: create childCtx (name + tags)
250
+ Ctx->>Ctx: fn(childCtx, ...params)
251
+ Ctx->>Ctx: childCtx.close(result)
252
+ Ctx-->>App: output
253
+ Note right of Ctx: name makes sub-executions observable by extensions
236
254
  ```
237
255
 
238
256
  ### Atom Retention (GC)
package/README.md CHANGED
@@ -1,8 +1,22 @@
1
1
  # @pumped-fn/lite
2
2
 
3
- Lightweight effect system for TypeScript: scoped lifecycles, tagged context, and opt‑in reactivity.
3
+ ![Coverage: 97%+ statements](https://img.shields.io/badge/coverage-97%25%2B_statements-brightgreen) ![Coverage: 100% functions](https://img.shields.io/badge/coverage-100%25_functions-brightgreen) ![Tests: 263 passed](https://img.shields.io/badge/tests-263_passed-brightgreen)
4
4
 
5
- Docs: `packages/lite/PATTERNS.md` for usage patterns, `packages/lite/dist/index.d.mts` for API details.
5
+ **Scoped Ambient State** for TypeScript a scope-local atom graph with explicit dependencies and opt-in reactivity.
6
+
7
+ State lives in the scope, not in the component tree. Handlers and components observe — they don't own or construct dependencies. The same graph works across React, server handlers, background jobs, and tests.
8
+
9
+ **Frontend** — atoms form a reactive dependency graph (`homeData <- auth`). UI subscribes via controllers; auth changes cascade to dependents automatically. Components are projections of state, not owners.
10
+
11
+ **Backend** — atoms are infrastructure singletons (db pool, cache). Runtime config enters the scope as tags; atoms consume it via `tags.required()`. Contexts bound per request carry tags (tenantId, traceId) without parameter drilling. Extensions wrap every resolve/exec for logging, tracing, auth. Cleanup is guaranteed.
12
+
13
+ **Both** — presets swap any atom/flow for testing or multi-tenant isolation. Tags carry runtime config; presets replace implementations. No mocks, no test-only code paths.
14
+
15
+ ```
16
+ npx @pumped-fn/lite # CLI reference
17
+ npx @pumped-fn/lite primitives # API
18
+ npx @pumped-fn/lite diagrams # mermaid source
19
+ ```
6
20
 
7
21
  ## How It Works
8
22
 
@@ -11,220 +25,157 @@ sequenceDiagram
11
25
  participant App
12
26
  participant Scope
13
27
  participant Ext as Extension
14
- participant Ctx as ExecutionContext
15
28
  participant Atom
29
+ participant Ctx as ExecutionContext
30
+ participant Child as ChildContext
16
31
  participant Flow
17
32
  participant Ctrl as Controller
18
33
 
19
- App->>Scope: createScope({ extensions, presets, tags })
20
- Scope-->>App: scope (ready)
34
+ Note over App,Ctrl: (*) stable ref same identity until released
21
35
 
22
- App->>Scope: resolve(atom)
23
- Scope->>Ext: wrapResolve(next, atom)
24
- Ext->>Atom: factory(ctx, deps)
25
- Atom-->>Scope: value (cached)
26
-
27
- App->>Scope: createContext({ tags })
28
- Scope-->>App: ctx
29
-
30
- App->>Ctx: ctx.exec({ flow, input, tags })
31
- Ctx->>Ctx: merge tags, create childCtx
32
- Ctx->>Ext: wrapExec(next, flow, childCtx)
33
- Ext->>Flow: parse(input) + factory(childCtx, deps)
34
- Flow-->>Ext: output
35
- Ext-->>Ctx: output
36
- Ctx->>Ctx: childCtx.close() (onClose LIFO)
37
- Ctx-->>App: output
38
-
39
- rect rgb(240, 248, 255)
40
- Note over App,Ctrl: Reactivity (opt‑in)
41
- App->>Scope: controller(atom)
42
- Scope-->>Ctrl: ctrl
43
- App->>Ctrl: ctrl.get() / ctrl.resolve()
44
- Ctrl-->>App: value
45
- App->>Ctrl: ctrl.set(v) / ctrl.update(fn)
46
- App->>Ctrl: ctrl.on('resolved', listener)
47
- Ctrl-->>App: unsubscribe
48
- App->>Ctrl: ctrl.invalidate()
49
- Ctrl->>Atom: re‑run factory
50
- App->>Ctrl: ctrl.release()
51
- App->>Scope: release(atom)
52
- Scope->>Scope: run cleanups, remove cache
53
- App->>Scope: select(atom, selector, { eq })
54
- Scope-->>App: { get, subscribe }
55
- App->>Scope: on('resolved', atom, listener)
56
- Scope-->>App: unsubscribe
57
- end
36
+ %% ── Scope Creation & Extension Init ──
37
+ App->>Scope: createScope({ extensions, presets, tags, gc })
38
+ Scope-->>App: scope (sync return)
58
39
 
59
- rect rgb(255, 250, 240)
60
- Note over App,Scope: Introspection
61
- App->>App: isAtom(v), isFlow(v), isTag(v), isTagged(v)
62
- App->>App: isPreset(v), isControllerDep(v), isTagExecutor(v)
63
- App->>App: getAllTags() → Tag[]
40
+ loop each extension (sequential)
41
+ Scope->>Ext: await ext.init(scope)
64
42
  end
43
+ Note right of Scope: all init() done → scope.ready resolves
44
+ Note right of Scope: any init() throws → scope.ready rejects
45
+ Note right of Scope: resolve() auto‑awaits scope.ready
65
46
 
66
- App->>Scope: flush()
67
- Note right of Scope: wait pending ops
68
- App->>Ctx: ctx.close()
69
- Ctx->>Ctx: run onClose (LIFO)
70
- App->>Scope: dispose()
71
- Scope->>Scope: release atoms, run cleanups
72
- ```
73
-
74
- ## Composition
47
+ %% ── Observers (register before or after resolve) ──
48
+ App->>Scope: scope.on('resolving' | 'resolved' | 'failed', atom, listener)
49
+ Scope-->>App: unsubscribe fn
50
+ Note right of Scope: scope.on listens to AtomState transitions
75
51
 
76
- ```mermaid
77
- graph LR
78
- subgraph Primitives
79
- atom["atom({ factory, deps?, tags?, keepAlive? })"]
80
- flow["flow({ factory, parse?, deps?, tags? })"]
81
- service["service({ factory, deps? })"]
82
- tag["tag({ label, default?, parse? })"]
83
- preset["preset(target, value)"]
52
+ %% ── Atom Resolution ──
53
+ Note right of Scope: singletons — created once, reused across contexts. deps can include tags.required()
54
+ App->>Scope: resolve(atom)
55
+ Scope->>Scope: cache hit? return cached
56
+ alt preset hit
57
+ Scope->>Scope: value → store directly, skip factory
58
+ Scope->>Scope: atom resolve that atom instead
59
+ Scope->>Scope: ⚡ emit 'resolved' (no 'resolving')
84
60
  end
85
-
86
- subgraph Wrappers
87
- typed["typed&lt;T&gt;()"]
88
- ctrlDep["controller(atom, { resolve? })"]
89
- tagExec["tags.required/optional/all(tag)"]
61
+ Scope->>Scope: state → resolving
62
+ Scope->>Scope: ⚡ emit 'resolving' → scope.on listeners
63
+ Scope->>Ext: wrapResolve(next, { kind: "atom", target, scope })
64
+ Ext->>Atom: next() factory(ctx, deps)
65
+ Note right of Atom: ctx.cleanup(fn) → stored per atom
66
+ Note right of Atom: cleanups run LIFO on release/invalidate
67
+ Atom-->>Ext: value
68
+ Note right of Ext: ext returns value — may transform or replace
69
+ alt factory succeeds
70
+ Ext-->>Scope: value (*) cached in entry
71
+ Scope->>Scope: state → resolved
72
+ Scope->>Scope: ⚡ emit 'resolved' → scope.on + ctrl.on listeners
73
+ else factory throws
74
+ Atom-->>Scope: error
75
+ Scope->>Scope: state → failed
76
+ Scope->>Scope: ⚡ emit 'failed' → scope.on listeners (ctrl.on '*' only)
90
77
  end
91
78
 
92
- flow --> typed
93
- atom --> ctrlDep
94
- tag --> tagExec
95
- ```
96
-
97
- ## Context Data
79
+ %% ── Context Creation ──
80
+ Note right of Scope: HTTP request, job, transaction — groups exec calls with shared tags + guaranteed cleanup
81
+ App->>Scope: scope.createContext({ tags })
82
+ Scope-->>App: ctx
98
83
 
99
- ```mermaid
100
- graph TD
101
- subgraph "ctx.data"
102
- raw["Raw: get/set/has/delete/clear/seek"]
103
- typed["Typed: getTag/setTag/hasTag/deleteTag/seekTag/getOrSetTag"]
84
+ %% ── Execution ──
85
+ alt ctx.exec({ flow, input, tags })
86
+ Ctx->>Ctx: preset? → flow: re‑exec with replacement / fn: run as factory
87
+ Ctx->>Ctx: flow.parse(input) if defined
88
+ Ctx->>Child: create child (parent = ctx, merged tags)
89
+ Child->>Ext: wrapExec(next, flow, childCtx)
90
+ Ext->>Flow: next() → factory(childCtx, deps)
91
+ Note right of Flow: childCtx.onClose(result: CloseResult) → { ok: true } | { ok: false, error }
92
+ Flow-->>Ext: output
93
+ Note right of Ext: ext returns output — may transform or replace
94
+ Ext-->>Child: output
95
+ else ctx.exec({ name?, fn, params, tags })
96
+ Ctx->>Child: create child (parent = ctx)
97
+ Child->>Ext: wrapExec(next, fn, childCtx)
98
+ Ext->>Child: next() → fn(childCtx, ...params)
99
+ Child-->>Ext: result
104
100
  end
105
- raw --> typed
106
- ```
107
-
108
- ## Atom Lifecycle (AtomState)
109
-
110
- ```mermaid
111
- stateDiagram-v2
112
- [*] --> idle
113
- idle --> resolving: resolve() / controller()
114
- resolving --> resolved: factory completes
115
- resolving --> failed: factory throws
116
- resolved --> resolving: invalidate()
117
- resolved --> idle: release()
118
- failed --> resolving: invalidate()
119
- failed --> idle: release()
120
- ```
121
-
122
- ## Tag Resolution
123
-
124
- ```mermaid
125
- sequenceDiagram
126
- participant App
127
- participant Tag
128
- participant Source as Atom/Flow/Ctx
129
-
130
- App->>Tag: tag({ label, default? })
131
- Tag-->>App: Tag<T>
132
-
133
- App->>Tag: tag(value)
134
- Tag-->>App: Tagged<T>
135
-
136
- App->>Source: attach Tagged[] to atom/flow/ctx
137
-
138
- App->>Tag: tag.get(source)
139
- Tag->>Source: find first match
140
- Source-->>Tag: value or throw
141
-
142
- App->>Tag: tag.find(source)
143
- Tag->>Source: find first match
144
- Source-->>Tag: value or undefined
145
-
146
- App->>Tag: tag.collect(source)
147
- Tag->>Source: gather all matches
148
- Source-->>Tag: T[]
149
-
150
- App->>Tag: tag.atoms()
151
- Tag-->>App: Atom[] with this tag
152
- ```
153
-
154
- ## Type Utilities
101
+ Ctx->>Child: [A] close(result) → run onClose(CloseResult) LIFO
102
+ Child-->>Ctx: output
103
+ Ctx-->>App: output
155
104
 
156
- ```mermaid
157
- graph LR
158
- subgraph "Lite.Utils"
159
- AtomValue["AtomValue&lt;A&gt;"]
160
- FlowOutput["FlowOutput&lt;F&gt;"]
161
- FlowInput["FlowInput&lt;F&gt;"]
162
- TagValue["TagValue&lt;T&gt;"]
163
- DepsOf["DepsOf&lt;A|F&gt;"]
164
- ControllerValue["ControllerValue&lt;C&gt;"]
165
- Simplify["Simplify&lt;T&gt;"]
166
- AtomType["AtomType&lt;T, D&gt;"]
167
- FlowType["FlowType&lt;O, I, D&gt;"]
105
+ %% ── Resource (execution‑scoped) ──
106
+ rect rgb(245, 240, 255)
107
+ Note over App,Ctrl: Resource (per‑execution middleware)
108
+ Note right of Scope: reusable factory resolved fresh per execution chain — logger, transaction, trace span
109
+
110
+ App->>App: resource({ deps, factory })
111
+ App-->>App: Resource definition (inert)
112
+
113
+ Note right of Child: during dep resolution in ctx.exec():
114
+ Note right of Child: seek hierarchy for existing instance
115
+ alt cache hit (seek‑up)
116
+ Child->>Child: reuse instance from parent ✓
117
+ else cache miss
118
+ Child->>Ext: wrapResolve(next, { kind: "resource", target, ctx })
119
+ Ext->>Child: next() → factory(parentCtx, deps)
120
+ Note right of Child: parentCtx.onClose(result) → cleanup registered
121
+ Child-->>Ext: instance stored on parent context
122
+ end
168
123
  end
169
124
 
170
- subgraph "Type Guards"
171
- isAtom
172
- isFlow
173
- isTag
174
- isTagged
175
- isPreset
176
- isControllerDep
177
- isTagExecutor
178
- end
125
+ %% ── Reactivity (opt‑in) ──
126
+ rect rgb(240, 248, 255)
127
+ Note over App,Ctrl: Reactivity (opt‑in — atoms are static by default)
128
+ Note right of Scope: live config, UI state, cache invalidation — when values change after initial resolve
129
+ App->>Scope: controller(atom)
130
+ Scope-->>Ctrl: ctrl (*)
179
131
 
180
- subgraph "Convenience"
181
- AnyAtom
182
- AnyFlow
183
- AnyController
184
- end
185
- ```
132
+ App->>Ctrl: ctrl.on('resolving' | 'resolved' | '*', listener)
133
+ Ctrl-->>App: unsubscribe
134
+ Note right of Ctrl: ctrl.on listens to per‑atom entry events
186
135
 
187
- ## Introspection
136
+ App->>Ctrl: ctrl.set(v) / ctrl.update(fn)
137
+ Ctrl->>Scope: scheduleInvalidation
138
+ Scope->>Scope: run atom cleanups (LIFO)
139
+ Scope->>Scope: ⚡ emit 'resolving' → scope.on + ctrl.on
140
+ Scope->>Atom: apply new value (skip factory)
141
+ Scope->>Scope: state → resolved
142
+ Scope->>Scope: ⚡ emit 'resolved' → scope.on + ctrl.on
188
143
 
189
- ```mermaid
190
- sequenceDiagram
191
- participant App
192
- participant Registry
144
+ App->>Ctrl: ctrl.invalidate()
145
+ Ctrl->>Scope: scheduleInvalidation
146
+ Scope->>Scope: run atom cleanups (LIFO)
147
+ Scope->>Scope: ⚡ emit 'resolving' → scope.on + ctrl.on
148
+ Scope->>Atom: re‑run factory
149
+ Scope->>Scope: state → resolved
150
+ Scope->>Scope: ⚡ emit 'resolved' → scope.on + ctrl.on
193
151
 
194
- App->>Registry: getAllTags()
195
- Registry-->>App: Tag[] (all live tags)
152
+ App->>Scope: select(atom, selector, { eq })
153
+ Scope-->>App: handle { get, subscribe }
154
+ end
196
155
 
197
- App->>App: isAtom(v) / isFlow(v) / isTag(v)
198
- App->>App: isTagged(v) / isPreset(v)
199
- App->>App: isControllerDep(v) / isTagExecutor(v)
200
- ```
156
+ %% ── Cleanup & Teardown ──
157
+ rect rgb(255, 245, 238)
158
+ Note over App,Scope: Teardown
159
+ App->>Ctx: ctx.close(result?) — same as [A]
160
+ Ctx->>Ctx: run onClose(CloseResult) cleanups (LIFO, idempotent)
201
161
 
202
- ## Additional Exports
162
+ App->>Scope: release(atom)
163
+ Scope->>Scope: run atom cleanups (LIFO)
164
+ Scope->>Scope: remove from cache + controllers
203
165
 
204
- ```mermaid
205
- graph LR
206
- subgraph Errors
207
- ParseError["ParseError (tag | flow-input)"]
208
- end
166
+ App->>Scope: flush()
167
+ Note right of Scope: await pending invalidation chain
209
168
 
210
- subgraph Meta
211
- VERSION
169
+ App->>Scope: dispose()
170
+ loop each extension
171
+ Scope->>Ext: ext.dispose(scope)
172
+ end
173
+ Scope->>Scope: release all atoms, run all cleanups
212
174
  end
213
175
 
214
- subgraph "Symbols (advanced)"
215
- atomSymbol
216
- flowSymbol
217
- tagSymbol
218
- taggedSymbol
219
- presetSymbol
220
- controllerSymbol
221
- controllerDepSymbol
222
- tagExecutorSymbol
223
- typedSymbol
224
- end
225
176
  ```
226
177
 
227
- API reference: `packages/lite/dist/index.d.mts`.
178
+ API reference: `dist/index.d.mts` | Patterns: `PATTERNS.md` | CLI: `npx @pumped-fn/lite`
228
179
 
229
180
  ## License
230
181