@pumped-fn/lite 2.0.0 → 2.1.2

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,54 @@
1
1
  # @pumped-fn/lite
2
2
 
3
+ ## 2.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 8ed17e7: - Fix watch and invalidation edge cases in `@pumped-fn/lite` by aligning `select()` with `Object.is`, snapshotting select listeners during notification, making watch option typing match the runtime contract, and surfacing invalidation-chain failures from `flush()` instead of leaking them as background rejections.
8
+ - Fix `@pumped-fn/lite-react` hook refresh behavior by keeping stale values visible during re-resolution, recomputing `useSelect` snapshots when selector or equality semantics change, tracking pending promises per controller, and suppressing non-Suspense `unhandledRejection` leaks on failed refreshes.
9
+
10
+ ## 2.1.1
11
+
12
+ ### Patch Changes
13
+
14
+ - 2ce41fc: Fix 16 bugs found via adversarial triage + 5 rounds of Codex review:
15
+
16
+ **Correctness**
17
+
18
+ - `preset(atom, undefined)` now works — uses `has()` check instead of `!== undefined`
19
+ - `seekHas()` traverses parent chain via interface dispatch, not `instanceof`
20
+ - Error-path `pendingSet` only reschedules value-type sets — `fn(undefined)` no longer produces garbage
21
+ - `doInvalidateSequential` swallows resolve errors when pending operations exist
22
+ - Resource cycle detection moved to per-execution-chain WeakMap — fixes false errors with `ctx.exec()`
23
+ - Resource inflight check runs before circular check — sibling `ctx.exec()` no longer false-positives
24
+
25
+ **Reactive system**
26
+
27
+ - `set()`/`update()` pendingSet path skips cleanups — watch deps preserved since factory doesn't re-run
28
+ - Unconditional `invalidationChain.delete()` in pendingSet fast-path — prevents self-loops
29
+ - Copy-on-iterate on all 4 listener iteration sites — unsub during notification no longer drops siblings
30
+
31
+ **Lifecycle**
32
+
33
+ - `dispose()` awaits `chainPromise` before setting `disposed` — drains pending invalidation chain
34
+ - `resolve()`, `controller()`, `createContext()` throw after dispose
35
+ - `release()` cleans up dependents + schedules GC on freed deps
36
+
37
+ **SelectHandle**
38
+
39
+ - Eager subscription in constructor — tracks changes without active subscribers
40
+ - `dispose()` method for explicit teardown
41
+ - Re-subscribe refreshes cached value after auto-cleanup
42
+ - Added `seekHas()` to `ContextData` interface, `dispose()` to `SelectHandle` interface
43
+
44
+ ## 2.1.0
45
+
46
+ ### Minor Changes
47
+
48
+ - a87362f: Add `controller({ resolve: true, watch: true, eq? })` for automatic reactive invalidation.
49
+
50
+ When `watch: true` is set, the parent atom re-runs automatically whenever the dep resolves to a new value (equality-gated via `Object.is` or a custom `eq` function). Replaces manual `ctx.cleanup(ctx.scope.on('resolved', dep, () => ctx.invalidate()))` wiring. Watch listeners are auto-cleaned on re-resolve, release, and dispose.
51
+
3
52
  ## 2.0.0
4
53
 
5
54
  ### Major Changes
package/PATTERNS.md CHANGED
@@ -218,22 +218,23 @@ sequenceDiagram
218
218
 
219
219
  ### Controller as Dependency
220
220
 
221
- Receive reactive handle instead of resolved value in atom/flow deps.
221
+ Receive a reactive handle instead of the resolved value in atom deps. Use `resolve: true` to pre-resolve before the factory runs. Add `watch: true` (atom deps only) to auto-invalidate the parent when the dep value changes — replaces manual `ctx.cleanup(ctx.scope.on('resolved', dep, () => ctx.invalidate()))`.
222
222
 
223
223
  ```mermaid
224
224
  sequenceDiagram
225
225
  participant Scope
226
- participant AtomA as serverAtom
226
+ participant Parent as derivedAtom
227
+ participant Dep as configAtom
227
228
  participant Ctrl as Controller
228
- participant AtomB as configAtom
229
-
230
- Scope->>AtomA: resolve(serverAtom)
231
- Note over AtomA: deps: { cfg: controller(configAtom, { resolve: true }) }
232
- AtomA->>Scope: resolve configAtom first
233
- Scope-->>Ctrl: ctrl (already resolved)
234
- AtomA->>AtomA: factory(ctx, { cfg: ctrl })
235
- AtomA->>Ctrl: ctrl.on('resolved', () => ctx.invalidate())
236
- Note over AtomA: react to config changes
229
+
230
+ Scope->>Parent: resolve(derivedAtom)
231
+ Note over Parent: deps: { cfg: controller(configAtom, { resolve: true, watch: true, eq? }) }
232
+ Parent->>Scope: resolve configAtom first
233
+ Scope-->>Ctrl: ctrl (resolved)
234
+ Parent->>Parent: factory(_, { cfg: ctrl })
235
+ Note over Scope: on dep 'resolved': compare prev/next via eq ?? Object.is
236
+ Scope->>Parent: scheduleInvalidation if changed
237
+ Note over Parent: watch listener auto-cleaned on re-resolve / release / dispose
237
238
  ```
238
239
 
239
240
  ### Inline Function Execution
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @pumped-fn/lite
2
2
 
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)
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: 300 passed](https://img.shields.io/badge/tests-300_passed-brightgreen)
4
4
 
5
5
  **Scoped Ambient State** for TypeScript — a scope-local atom graph with explicit dependencies and opt-in reactivity.
6
6
 
@@ -151,6 +151,13 @@ sequenceDiagram
151
151
 
152
152
  App->>Scope: select(atom, selector, { eq })
153
153
  Scope-->>App: handle { get, subscribe }
154
+
155
+ Note over App,Ctrl: Dependency reactivity — atom deps only
156
+ Note right of Scope: watch:true replaces manual ctx.cleanup(ctx.scope.on('resolved', dep, () => ctx.invalidate()))
157
+ App->>Scope: resolve(derivedAtom)
158
+ Note right of Scope: deps: { src: controller(srcAtom, { resolve: true, watch: true, eq? }) }
159
+ Scope->>Scope: on dep 'resolved': eq(prev, next) → scheduleInvalidation if changed
160
+ Note right of Scope: watch listener auto-cleaned on re-resolve / release / dispose
154
161
  end
155
162
 
156
163
  %% ── Cleanup & Teardown ──