@dougongjs/reactive 0.8.2 → 0.9.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 CHANGED
@@ -1,91 +1,27 @@
1
1
  # @dougongjs/reactive
2
2
 
3
- [简体中文](#简体中文) · [English](#english)
3
+ Independent synchronous signals and structurally owned reactive generations. No external runtime dependencies.
4
4
 
5
- ---
6
-
7
- ## 简体中文
8
-
9
- **零依赖**的响应式原语:`signal` / `computed` / `batch` / `observe`。
10
-
11
- ```sh
12
- npm install @dougongjs/reactive
13
- ```
14
-
15
- ```ts
16
- import { signal, computed, batch, observe } from "@dougongjs/reactive"
17
-
18
- const count = signal(0)
19
- const double = computed(() => count.get() * 2)
20
-
21
- batch(() => count.set(21))
22
- double.get()
23
- ```
24
-
25
- `double.get()` 返回 `42`。
26
-
27
- `computed` 是惰性的,依赖动态追踪。`batch` 按订阅身份合并回调内的重复通知。三者都拒绝异步回调——同步追踪和批次边界不能跨越 `await`,与其产生静默的错误结果,不如立刻抛错。
28
-
29
- ### observe:把值的变化编译成资源的重建
30
-
31
- ```ts
32
- observe(owner, source, (value, lifetime) => {
33
- const socket = new WebSocket(value)
34
- lifetime.cleanup(() => socket.close())
35
- })
36
- ```
37
-
38
- 每次 `source` 变化:先释放上一个 `lifetime`,再用新值建一个新的。
39
-
40
- `observe()` 与 `@dougongjs/core` **互不依赖**——它是一个作用在结构化 `ObservationOwner`(提供 `cleanup` / `lifetime` / `spawn`)上的自由函数,所以能驱动 Core 的 Lifetime,而 Core 不需要知道它存在。插件的 `ctx` 恰好满足这个形状。
41
-
42
- `source` 只需满足结构化的 `Readable<T>`(`get()` + `subscribe()`)——Signal、Core 的 `ContributionView`、诊断视图,甚至你自己写的对象都可以。
43
-
44
- ---
45
-
46
- ## English
47
-
48
- **Zero-dependency** reactive primitives: `signal` / `computed` / `batch` / `observe`.
49
-
50
- ```sh
51
- npm install @dougongjs/reactive
52
- ```
5
+ Upgrading from 0.8.3? Read the [migration guide](https://tangerg.github.io/dougong/en/guide/migrating-to-0.9) and [0.9.0 release notes](https://tangerg.github.io/dougong/en/reference/release-0.9.0).
53
6
 
54
7
  ```ts
55
- import { signal, computed, batch, observe } from "@dougongjs/reactive"
56
-
57
- const count = signal(0)
58
- const double = computed(() => count.get() * 2)
59
-
60
- batch(() => count.set(21))
61
- double.get()
8
+ import { batch, computed, signal } from "@dougongjs/reactive";
9
+
10
+ const first = signal("Dougong");
11
+ const last = signal("Plugins");
12
+ const title = computed(() => `${first.get()} ${last.get()}`);
13
+ const subscription = title.subscribe(() => render(title.get()));
14
+ batch(() => {
15
+ first.set("Typed");
16
+ last.set("Composition");
17
+ });
18
+ subscription[Symbol.dispose]();
62
19
  ```
63
20
 
64
- `double.get()` returns `42`.
65
-
66
- `computed` is lazy with dynamic dependency tracking. `batch` coalesces repeated notifications per subscription identity. All three reject asynchronous callbacks — synchronous tracking and batch boundaries cannot survive an `await`, and throwing immediately beats producing a silently wrong result.
67
-
68
- ### observe: compiling value change into resource rebuild
69
-
70
- ```ts
71
- observe(owner, source, (value, lifetime) => {
72
- const socket = new WebSocket(value)
73
- lifetime.cleanup(() => socket.close())
74
- })
75
- ```
76
-
77
- On each change to `source`: release the previous `lifetime`, then build a new one from the new value.
78
-
79
- `observe()` and `@dougongjs/core` are **mutually independent** — it is a free function over a structural `ObservationOwner` (anything providing `cleanup` / `lifetime` / `spawn`), so it can drive Core Lifetimes without Core knowing it exists. A plugin's `ctx` happens to satisfy that shape.
80
-
81
- `source` need only satisfy the structural `Readable<T>` (`get()` + `subscribe()`) — a signal, Core's `ContributionView`, a diagnostics view, or your own object.
82
-
83
- ---
21
+ `Readable<T>` exposes `get()` and `subscribe()`. The subscription implements standard `Symbol.dispose`; callbacks notify changes without replay. `bridge(owner, readable)` adapts another module copy or implementation into the local dependency graph. Its subscription and terminal detachment belong to the supplied owner through `defer`.
84
22
 
85
- - 文档站 / Documentation: https://tangerg.github.io/dougong/
86
- - 仓库 / Repository: https://github.com/Tangerg/dougong
23
+ `observe(owner, readable, observer)` disposes the previous child before building the next one. The structural owner supplies `child`, `defer` and `spawn`; only the returned child needs `Symbol.asyncDispose`; no framework base class is required. Use `generation.spawn` for asynchronous observer work.
87
24
 
88
- > 早期 0.x 开发阶段,当前不承诺向后兼容。需要 Node.js ≥ 22,或 Chrome / Edge 119、Firefox 121、Safari 17.4 及以上浏览器 / WebView。
89
- > Early 0.x development; no backward-compatibility promises yet. Requires Node.js ≥ 22, or Chrome / Edge 119, Firefox 121 or Safari 17.4 and later.
25
+ ESM; Node.js 22+, Chrome/Edge 119+, Firefox 124+, Safari 17.4+.
90
26
 
91
- MIT
27
+ [Guide](https://tangerg.github.io/dougong/en/guide/reactive)
@@ -0,0 +1,4 @@
1
+ export declare function isAborted(signal: AbortSignal): boolean;
2
+ export declare function abortReason(signal: AbortSignal): unknown;
3
+ export declare function onAbort(signal: AbortSignal, callback: (reason: unknown) => void): () => void;
4
+ //# sourceMappingURL=abort.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abort.d.ts","sourceRoot":"","sources":["../src/abort.ts"],"names":[],"mappings":"AAIA,wBAAgB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAEtD;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAExD;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,CA6B5F"}
@@ -0,0 +1,7 @@
1
+ import { AsyncDisposable, Readable } from './protocol.js';
2
+ import { ReadonlySignal } from './signals.js';
3
+ export interface BridgeOwner {
4
+ readonly defer: (dispose: () => unknown) => AsyncDisposable;
5
+ }
6
+ export declare function bridge<T>(owner: BridgeOwner, source: Readable<T>): ReadonlySignal<T>;
7
+ //# sourceMappingURL=bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,eAAe,EAEpB,KAAK,QAAQ,EACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAU,KAAK,cAAc,EAAe,MAAM,cAAc,CAAC;AAExE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,OAAO,KAAK,eAAe,CAAC;CAC7D;AA2ID,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAmBpF"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- export { batch, computed, signal, type ReadonlySignal, type Signal } from './signals';
2
- export type { AsyncDisposable, Disposable, Readable } from './protocol';
3
- export { observe, type ObservationOwner, type ObservationTask, type Observer } from './observe';
1
+ export { batch, computed, signal, type ReadonlySignal, type Signal } from './signals.js';
2
+ export { bridge, type BridgeOwner } from './bridge.js';
3
+ export type { AsyncDisposable, Disposable, Readable } from './protocol.js';
4
+ export { observe, type ObservationOwner, type ObservationTask, type Observer } from './observe.js';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,cAAc,EAAE,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AACtF,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,cAAc,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AACzF,OAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AACvD,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC3E,OAAO,EAAE,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -1,406 +1,598 @@
1
- function D(s) {
2
- if (typeof s != "function")
3
- throw new Error("Unsupported JavaScript runtime: Promise.withResolvers is required");
1
+ import { listenForAbort as e } from "#abort-listener";
2
+ // src/disposal-runtime.ts
3
+ function t(e) {
4
+ if (typeof e != "function") throw Error("Unsupported JavaScript runtime: Promise.withResolvers is required");
4
5
  }
5
- function b(s, e) {
6
- return s ?? Symbol.for(e);
6
+ function n(e, t) {
7
+ return e ?? Symbol.for(t);
7
8
  }
8
- D(Promise.withResolvers);
9
- const w = b(
10
- Symbol.dispose,
11
- "Symbol.dispose"
12
- ), y = b(
13
- Symbol.asyncDispose,
14
- "Symbol.asyncDispose"
15
- );
16
- function a(s, e) {
17
- const t = m(s);
18
- if (t)
19
- throw t.catch(() => {
20
- }), new TypeError(e);
9
+ t(Promise.withResolvers);
10
+ var r = n(Symbol.dispose, "Symbol.dispose"), i = n(Symbol.asyncDispose, "Symbol.asyncDispose");
11
+ // src/synchronous-result.ts
12
+ function a(e, t) {
13
+ let n = o(e);
14
+ if (n) throw n.then(() => void 0, () => void 0), TypeError(t);
21
15
  }
22
- function m(s) {
23
- if (s === null || typeof s != "object" && typeof s != "function") return;
24
- const e = s.then;
25
- if (typeof e == "function")
26
- return Promise.resolve().then(
27
- () => new Promise((t, i) => Reflect.apply(e, s, [t, i]))
28
- );
16
+ function o(e) {
17
+ if (e === null || typeof e != "object" && typeof e != "function") return;
18
+ let t = e.then;
19
+ if (typeof t != "function") return;
20
+ let n = Promise.withResolvers();
21
+ return queueMicrotask(() => {
22
+ try {
23
+ Reflect.apply(t, e, [n.resolve, n.reject]);
24
+ } catch (e) {
25
+ n.reject(e);
26
+ }
27
+ }), n.promise;
29
28
  }
30
- let u = 0, p = !1;
31
- const f = /* @__PURE__ */ new Set(), o = /* @__PURE__ */ new Set();
32
- let c;
33
- const l = /* @__PURE__ */ new WeakMap();
34
- class g {
35
- #e;
36
- constructor(e) {
37
- this.#e = e, Object.freeze(this);
38
- }
39
- dispose() {
40
- const e = this.#e;
41
- this.#e = void 0, e?.();
42
- }
43
- [w]() {
44
- this.dispose();
45
- }
29
+ // src/protocol.ts
30
+ function s(e, t) {
31
+ let n = e?.get, r = e?.subscribe;
32
+ if (typeof n != "function" || typeof r != "function") throw TypeError(`${t} expects a readable source`);
33
+ return Object.freeze({
34
+ get: () => Reflect.apply(n, e, []),
35
+ subscribe: (t) => Reflect.apply(r, e, [t])
36
+ });
46
37
  }
47
- function O() {
48
- if (p) return;
49
- const s = [];
50
- p = !0;
51
- try {
52
- for (; o.size || f.size; ) {
53
- const e = o.size ? o : f, t = e.values().next().value;
54
- e.delete(t);
55
- const i = t.listener;
56
- if (i)
57
- try {
58
- a(i(), "Signal subscribers must be synchronous");
59
- } catch (r) {
60
- s.push(r);
61
- }
62
- }
63
- } finally {
64
- f.clear(), o.clear(), p = !1;
65
- }
66
- R(s);
38
+ function c(e) {
39
+ let t = e?.[r];
40
+ return typeof t != "function" && u(e, "Readable.subscribe() must return a Disposable"), () => a(Reflect.apply(t, e, []), "Readable subscriptions must dispose synchronously");
67
41
  }
68
- function S(s) {
69
- for (const e of s)
70
- (e.kind === "dependency" ? o : f).add(e);
71
- u || O();
42
+ function l(e, t) {
43
+ let n = e?.[i];
44
+ return typeof n != "function" && u(e, t), () => Reflect.apply(n, e, []);
72
45
  }
73
- function d(s, e, t) {
74
- k(e);
75
- const i = { kind: t, listener: e };
76
- return s.add(i), new g(() => {
77
- i.listener = void 0, s.delete(i);
78
- });
46
+ function u(e, t) {
47
+ throw a(e, t), TypeError(t);
79
48
  }
80
- function R(s) {
81
- if (s.length === 1) throw s[0];
82
- if (s.length > 1)
83
- throw new AggregateError(s, "Reactive subscribers failed");
49
+ function d(e) {
50
+ if (typeof e != "function") throw TypeError("Signal subscriber must be a function");
84
51
  }
85
- function E(s) {
86
- c?.(s);
87
- }
88
- function I(s) {
89
- if (typeof s != "function") throw new TypeError("batch() expects a function");
90
- u++;
91
- let e;
92
- try {
93
- const t = s();
94
- a(t, "Reactive batches must be synchronous"), e = { ok: !0, value: t };
95
- } catch (t) {
96
- e = { ok: !1, error: t };
97
- } finally {
98
- u--;
99
- }
100
- if (!u)
101
- try {
102
- O();
103
- } catch (t) {
104
- e = e.ok ? { ok: !1, error: t } : { ok: !1, error: new AggregateError([e.error, t], "Reactive batch failed") };
105
- }
106
- if (!e.ok) throw e.error;
107
- return e.value;
52
+ // src/signals.ts
53
+ var f = 0, p = 0, m = !1, h = !1, g = /* @__PURE__ */ new Set(), _ = /* @__PURE__ */ new Set(), v, y = [], b = class {
54
+ #e;
55
+ constructor(e) {
56
+ this.#e = e, Object.freeze(this);
57
+ }
58
+ [r]() {
59
+ let e = this.#e;
60
+ this.#e = void 0, e?.();
61
+ }
62
+ };
63
+ function x() {
64
+ if (m) return;
65
+ let e = [];
66
+ m = !0;
67
+ try {
68
+ for (; g.size;) {
69
+ let t = g.values().next().value;
70
+ g.delete(t);
71
+ let n = t.listener;
72
+ if (n) try {
73
+ a(n(), "Signal subscribers must be synchronous");
74
+ } catch (t) {
75
+ e.push(t);
76
+ }
77
+ }
78
+ } finally {
79
+ g.clear(), m = !1;
80
+ }
81
+ w(e);
108
82
  }
109
- function L(s) {
110
- return new z(s).view;
83
+ function S(e) {
84
+ for (let t of e) (t.kind === "dependency" ? _ : g).add(t);
85
+ if (!h) {
86
+ h = !0;
87
+ try {
88
+ for (; _.size;) {
89
+ let e = _.values().next().value;
90
+ _.delete(e);
91
+ let t = e.listener;
92
+ t?.();
93
+ }
94
+ } finally {
95
+ h = !1, _.clear();
96
+ }
97
+ p || x();
98
+ }
111
99
  }
112
- function q(s) {
113
- if (typeof s != "function") throw new TypeError("computed() expects a function");
114
- return new P(s).view;
100
+ function C(e, t, n) {
101
+ d(t);
102
+ let r = {
103
+ kind: n,
104
+ listener: t
105
+ };
106
+ return e.add(r), new b(() => {
107
+ r.listener = void 0, e.delete(r);
108
+ });
115
109
  }
116
- class z {
117
- #e = /* @__PURE__ */ new Set();
118
- #t;
119
- #s = 0;
120
- view;
121
- constructor(e) {
122
- this.#t = e, this.view = Object.freeze({
123
- get: () => (E(this.view), this.#t),
124
- set: (t) => this.#r(t),
125
- subscribe: (t) => d(this.#e, t, "observer")
126
- }), l.set(this.view, this);
127
- }
128
- get version() {
129
- return this.#s;
130
- }
131
- refresh() {
132
- }
133
- subscribeInvalidation(e) {
134
- return d(this.#e, e, "dependency");
135
- }
136
- #r(e) {
137
- if (c)
138
- throw new TypeError("Computed signal calculations cannot write signals");
139
- Object.is(this.#t, e) || (this.#t = e, this.#s++, S(this.#e));
140
- }
110
+ function w(e) {
111
+ if (e.length === 1) throw e[0];
112
+ if (e.length > 1) throw AggregateError(e, "Reactive subscribers failed");
141
113
  }
142
- class P {
143
- #e;
144
- #t = /* @__PURE__ */ new Set();
145
- #s = /* @__PURE__ */ new Map();
146
- #r;
147
- #n = !1;
148
- #i = !0;
149
- #c = 0;
150
- view;
151
- constructor(e) {
152
- this.#e = e, this.view = Object.freeze({
153
- get: () => this.#o(),
154
- subscribe: (t) => this.#h(t)
155
- }), l.set(this.view, this);
156
- }
157
- get version() {
158
- return this.#c;
159
- }
160
- refresh() {
161
- if (this.#n) throw new TypeError("Circular computed signal");
162
- this.#n = !0;
163
- try {
164
- this.#v();
165
- } finally {
166
- this.#n = !1;
167
- }
168
- }
169
- #o() {
170
- return this.refresh(), E(this.view), this.#a();
171
- }
172
- #a() {
173
- const e = this.#r;
174
- if (e.phase === "error") throw e.error;
175
- return e.value;
176
- }
177
- subscribeInvalidation(e) {
178
- return this.#h(e, "dependency");
179
- }
180
- #h(e, t = "observer") {
181
- k(e);
182
- const i = { kind: t, listener: e }, r = this.#t.size === 0;
183
- if (this.#t.add(i), r)
184
- try {
185
- this.refresh(), t === "observer" && this.#a(), this.#f();
186
- } catch (n) {
187
- throw this.#t.delete(i), this.#t.size || this.#u(), n;
188
- }
189
- return new g(() => {
190
- i.listener = void 0, this.#t.delete(i), this.#t.size || this.#u();
191
- });
192
- }
193
- #d = () => {
194
- this.#i || (this.#i = !0, S(this.#t));
195
- };
196
- #u() {
197
- for (const e of this.#s.values())
198
- e.subscription?.dispose(), e.subscription = void 0;
199
- }
200
- #f() {
201
- for (const e of this.#s.values())
202
- e.subscription ??= e.node.subscribeInvalidation(this.#d);
203
- }
204
- #v() {
205
- const e = this.#r;
206
- if (e && !this.#i && !this.#p()) return;
207
- this.#i = !0;
208
- const t = /* @__PURE__ */ new Set(), i = this.#b(t);
209
- this.#l(t), (!e || (i.phase === "value" ? e.phase !== "value" || !Object.is(e.value, i.value) : e.phase !== "error" || !Object.is(e.error, i.error))) && this.#c++, this.#r = i, this.#i = !1, this.#t.size && this.#f();
210
- }
211
- #p() {
212
- for (const e of this.#s.values())
213
- if (e.node.refresh(), e.version !== e.node.version) return !0;
214
- return this.#i;
215
- }
216
- #b(e) {
217
- const t = c;
218
- c = (i) => e.add(i);
219
- try {
220
- const i = this.#e();
221
- return a(i, "Computed signal calculations must be synchronous"), { phase: "value", value: i };
222
- } catch (i) {
223
- return { phase: "error", error: i };
224
- } finally {
225
- c = t;
226
- }
227
- }
228
- #l(e) {
229
- for (const [t, i] of this.#s)
230
- e.has(t) || (i.subscription?.dispose(), this.#s.delete(t));
231
- for (const t of e) {
232
- const i = l.get(t);
233
- if (!i) continue;
234
- const r = this.#s.get(t) ?? {
235
- node: i,
236
- version: i.version,
237
- subscription: void 0
238
- };
239
- r.version = i.version, this.#s.set(t, r);
240
- }
241
- }
114
+ function T(e) {
115
+ if (typeof e != "function") throw TypeError("batch() expects a function");
116
+ p++;
117
+ let t;
118
+ try {
119
+ let n = e();
120
+ a(n, "Reactive batches must be synchronous"), t = {
121
+ succeeded: !0,
122
+ value: n
123
+ };
124
+ } catch (e) {
125
+ t = {
126
+ succeeded: !1,
127
+ error: e
128
+ };
129
+ } finally {
130
+ p--;
131
+ }
132
+ if (!p) try {
133
+ x();
134
+ } catch (e) {
135
+ t = t.succeeded ? {
136
+ succeeded: !1,
137
+ error: e
138
+ } : {
139
+ succeeded: !1,
140
+ error: AggregateError([t.error, e], "Reactive batch failed")
141
+ };
142
+ }
143
+ if (!t.succeeded) throw t.error;
144
+ return t.value;
242
145
  }
243
- function k(s) {
244
- if (typeof s != "function")
245
- throw new TypeError("Signal subscriber must be a function");
146
+ function E(e) {
147
+ return new O(e).view;
246
148
  }
247
- class j {
248
- #e;
249
- #t;
250
- #s;
251
- #r = { present: !1 };
252
- #n = !1;
253
- #i;
254
- #c;
255
- #o;
256
- #a;
257
- constructor(e, t, i) {
258
- this.#e = { phase: "active", binding: { owner: e, source: t, observer: i } };
259
- }
260
- start() {
261
- const { owner: e, source: t } = this.#y(), i = t.subscribe(() => this.#d());
262
- if (x(i, "Readable.subscribe()"), this.#e.phase !== "active")
263
- throw v(i), new Error("Observation is not active");
264
- this.#t = i, this.#p();
265
- const r = e.spawn((C) => this.#u(C)), n = A(r);
266
- this.#i = r, n.then(
267
- () => this.#m(r),
268
- () => this.#m(r)
269
- );
270
- }
271
- attachOwnerCleanup(e) {
272
- this.#c = e;
273
- }
274
- dispose() {
275
- if (this.#a) return this.#a;
276
- const e = Promise.withResolvers();
277
- return this.#a = e.promise, this.#c = void 0, this.#g("disposed"), this.#o?.(), this.#h().then(e.resolve, e.reject), e.promise;
278
- }
279
- async #h() {
280
- const e = [];
281
- if (await this.#l(e), await h(this.#O(), e), await h(this.#w(), e), e.length === 1) throw e[0];
282
- if (e.length > 1) throw new AggregateError(e, "Observation cleanup failed");
283
- }
284
- #d() {
285
- this.#e.phase === "active" && (this.#n = !0, this.#o?.());
286
- }
287
- // Stop on drain failure because continuing would observe a value whose resource ownership never established.
288
- async #u(e) {
289
- try {
290
- for (; this.#e.phase === "active" && !e.aborted; )
291
- for (await this.#f(e); this.#e.phase === "active" && !e.aborted && this.#n; )
292
- this.#n = !1, await this.#v(e);
293
- } catch (t) {
294
- if (this.#e.phase !== "active") throw t;
295
- await this.#E(t);
296
- }
297
- }
298
- #f(e) {
299
- return this.#n || this.#e.phase !== "active" || e.aborted ? Promise.resolve() : new Promise((t) => {
300
- const i = () => {
301
- e.removeEventListener("abort", i), this.#o === i && (this.#o = void 0), t();
302
- };
303
- this.#o = i, e.addEventListener("abort", i, { once: !0 }), (this.#n || this.#e.phase !== "active" || e.aborted) && i();
304
- });
305
- }
306
- // Clear the observed marker during replacement so interruption cannot leave a value without its owner.
307
- async #v(e) {
308
- if (this.#e.phase !== "active" || e.aborted) return;
309
- const t = this.#e.binding.source.get();
310
- if (this.#r.present && Object.is(t, this.#r.value)) return;
311
- const i = this.#w();
312
- i && await i.dispose(), this.#r = { present: !1 }, !(this.#e.phase !== "active" || e.aborted) && this.#p();
313
- }
314
- #p() {
315
- const { owner: e, source: t } = this.#y(), i = e.lifetime("observation");
316
- T(i, "ObservationOwner.lifetime()"), this.#s = i;
317
- const r = t.get();
318
- this.#b(r, i), this.#e.phase === "active" && (this.#r = { present: !0, value: r });
319
- }
320
- #b(e, t) {
321
- const i = this.#y().observer(e, t);
322
- a(i, "Observers must be synchronous; use owner.spawn() for async work");
323
- }
324
- async #l(e) {
325
- const t = this.#t;
326
- if (this.#t = void 0, !!t)
327
- try {
328
- v(t);
329
- } catch (i) {
330
- e.push(i);
331
- }
332
- }
333
- #w() {
334
- const e = this.#s;
335
- return this.#s = void 0, e;
336
- }
337
- #O() {
338
- const e = this.#i;
339
- return this.#i = void 0, e;
340
- }
341
- #S() {
342
- const e = this.#c;
343
- return this.#c = void 0, e;
344
- }
345
- #m(e) {
346
- this.#i === e && (this.#i = void 0);
347
- }
348
- async #E(e) {
349
- const t = [e];
350
- throw this.#g("stopped"), this.#o?.(), await this.#l(t), await h(this.#w(), t), this.#i = void 0, await h(this.#S(), t), t.length === 1 ? t[0] : new AggregateError(t, "Observation stopped after a replacement failed");
351
- }
352
- #y() {
353
- const e = this.#e;
354
- if (e.phase !== "active") throw new Error("Observation is not active");
355
- return e.binding;
356
- }
357
- #g(e) {
358
- this.#e = { phase: e }, this.#r = { present: !1 }, this.#n = !1;
359
- }
149
+ function D(e) {
150
+ if (typeof e != "function") throw TypeError("computed() expects a function");
151
+ return new k(e).view;
360
152
  }
361
- function B(s, e, t) {
362
- if (!e || typeof e.get != "function" || typeof e.subscribe != "function")
363
- throw new TypeError("observe() expects a readable source");
364
- if (typeof t != "function") throw new TypeError("Observer must be a function");
365
- if (!s || typeof s.cleanup != "function" || typeof s.lifetime != "function" || typeof s.spawn != "function")
366
- throw new TypeError("observe() expects an observation owner");
367
- const i = new j(s, e, t), r = s.cleanup(() => i.dispose());
368
- T(r, "ObservationOwner.cleanup()"), i.attachOwnerCleanup(r);
369
- try {
370
- return i.start(), r;
371
- } catch (n) {
372
- throw i.dispose().catch(() => {
373
- }), n;
374
- }
153
+ var O = class {
154
+ #e = /* @__PURE__ */ new Set();
155
+ #t;
156
+ #n = 0;
157
+ view;
158
+ constructor(e) {
159
+ this.#t = e, this.view = Object.freeze({
160
+ get: () => (v?.add(this), this.#t),
161
+ set: (e) => this.#r(e),
162
+ subscribe: (e) => C(this.#e, e, "observer")
163
+ });
164
+ }
165
+ get version() {
166
+ return this.#n;
167
+ }
168
+ refresh() {}
169
+ subscribeInvalidation(e) {
170
+ return C(this.#e, e, "dependency");
171
+ }
172
+ #r(e) {
173
+ if (v) throw TypeError("Computed signal calculations cannot write signals");
174
+ Object.is(this.#t, e) || (this.#t = e, f++, this.#n++, S(this.#e));
175
+ }
176
+ }, k = class e {
177
+ #e;
178
+ #t = /* @__PURE__ */ new Set();
179
+ #n = /* @__PURE__ */ new Map();
180
+ #r;
181
+ #i = -1;
182
+ #a;
183
+ #o;
184
+ #s = !0;
185
+ #c = 0;
186
+ view;
187
+ constructor(e) {
188
+ this.#e = e, this.view = Object.freeze({
189
+ get: () => this.#u(),
190
+ subscribe: (e) => this.#f(e)
191
+ });
192
+ }
193
+ get version() {
194
+ return this.#c;
195
+ }
196
+ refresh() {
197
+ if (this.#a !== void 0) throw v && this.#l(this.#a, v), TypeError("Circular computed signal");
198
+ this.#a = y.length, y.push(this);
199
+ try {
200
+ this.#g();
201
+ } finally {
202
+ y.pop(), this.#a = void 0;
203
+ }
204
+ }
205
+ #l(t, n) {
206
+ let r = /* @__PURE__ */ new Set();
207
+ for (let e of y.slice(t)) for (let t of e.#o ?? e.#n.keys()) r.add(t);
208
+ for (let t of r) if (t instanceof e) for (let e of t.#n.keys()) r.add(e);
209
+ else n.add(t);
210
+ }
211
+ #u() {
212
+ return this.refresh(), v?.add(this), this.#d();
213
+ }
214
+ #d() {
215
+ let e = this.#r;
216
+ if (e.phase === "error") throw e.error;
217
+ return e.value;
218
+ }
219
+ subscribeInvalidation(e) {
220
+ return this.#f(e, "dependency");
221
+ }
222
+ #f(e, t = "observer") {
223
+ d(e);
224
+ let n = {
225
+ kind: t,
226
+ listener: e
227
+ }, r = this.#t.size === 0;
228
+ if (this.#t.add(n), r || t === "observer") try {
229
+ this.refresh(), t === "observer" && this.#d(), r && this.#h();
230
+ } catch (e) {
231
+ throw this.#t.delete(n), this.#t.size || this.#m(), e;
232
+ }
233
+ return new b(() => {
234
+ n.listener = void 0, this.#t.delete(n), this.#t.size || this.#m();
235
+ });
236
+ }
237
+ #p = () => {
238
+ this.#s || (this.#s = !0, S(this.#t));
239
+ };
240
+ #m() {
241
+ for (let e of this.#n.values()) e.subscription?.[r](), e.subscription = void 0;
242
+ }
243
+ #h() {
244
+ for (let [e, t] of this.#n) t.subscription ??= e.subscribeInvalidation(this.#p);
245
+ }
246
+ #g() {
247
+ let e = this.#r;
248
+ if (e && !this.#s && (this.#i === f || !this.#_())) {
249
+ this.#i = f;
250
+ return;
251
+ }
252
+ this.#s = !0;
253
+ let t = /* @__PURE__ */ new Set(), n = this.#v(t);
254
+ this.#y(t), (!e || (n.phase === "value" ? e.phase !== "value" || !Object.is(e.value, n.value) : e.phase !== "error" || !Object.is(e.error, n.error))) && this.#c++, this.#r = n, this.#i = f, this.#s = !1, this.#t.size && this.#h();
255
+ }
256
+ #_() {
257
+ for (let [t, n] of this.#n) if (t instanceof e && t.#a !== void 0 || (t.refresh(), n.version !== t.version)) return !0;
258
+ return this.#s;
259
+ }
260
+ #v(e) {
261
+ let t = v;
262
+ this.#o = e, v = e;
263
+ try {
264
+ let e = this.#e, t = e();
265
+ return a(t, "Computed signal calculations must be synchronous"), {
266
+ phase: "value",
267
+ value: t
268
+ };
269
+ } catch (e) {
270
+ return {
271
+ phase: "error",
272
+ error: e
273
+ };
274
+ } finally {
275
+ v = t, this.#o = void 0;
276
+ }
277
+ }
278
+ #y(e) {
279
+ for (let [t, n] of this.#n) e.has(t) || (n.subscription?.[r](), this.#n.delete(t));
280
+ for (let t of e) {
281
+ let e = this.#n.get(t);
282
+ e ? e.version = t.version : this.#n.set(t, {
283
+ version: t.version,
284
+ subscription: void 0
285
+ });
286
+ }
287
+ }
288
+ }, A = class {
289
+ #e;
290
+ #t;
291
+ #n;
292
+ #r;
293
+ view;
294
+ constructor(e) {
295
+ this.#e = E({
296
+ phase: "value",
297
+ source: e,
298
+ value: e.get()
299
+ }), this.view = Object.freeze({
300
+ get: () => {
301
+ let e = this.#e?.get();
302
+ if (!e) throw Error("Readable bridge has been disposed");
303
+ if (e.phase === "error") throw e.error;
304
+ return e.value;
305
+ },
306
+ subscribe: (e) => this.#o(e)
307
+ });
308
+ }
309
+ start(e) {
310
+ let t = !0, n;
311
+ try {
312
+ n = l(e.defer(() => (this[r](), t ? Promise.resolve().then(() => this.#i()) : void 0)), "BridgeOwner.defer() must return an AsyncDisposable");
313
+ let i = this.#e?.get();
314
+ if (!i) throw Error("Readable bridge has been disposed");
315
+ let a = c(i.source.subscribe(() => this.#a()));
316
+ if (this.#t = a, !this.#e?.get()) {
317
+ try {
318
+ a(), this.#t = void 0;
319
+ } catch (e) {
320
+ this.#r = { value: e };
321
+ }
322
+ throw Error("Readable bridge has been disposed");
323
+ }
324
+ this.#a(), this.view.get();
325
+ } catch (e) {
326
+ throw n && Promise.resolve().then(n).catch(() => void 0), e;
327
+ } finally {
328
+ t = !1;
329
+ }
330
+ }
331
+ #i() {
332
+ if (this.#r) throw this.#r.value;
333
+ }
334
+ #a() {
335
+ let e = this.#e?.get();
336
+ if (!e) return;
337
+ let t = Symbol();
338
+ this.#n = t;
339
+ try {
340
+ let { source: n } = e, r;
341
+ try {
342
+ r = {
343
+ phase: "value",
344
+ source: n,
345
+ value: n.get()
346
+ };
347
+ } catch (e) {
348
+ r = {
349
+ phase: "error",
350
+ source: n,
351
+ error: e
352
+ };
353
+ }
354
+ if (this.#n !== t || this.#e?.get() !== e || e.phase === "value" && r.phase === "value" && Object.is(e.value, r.value) || e.phase === "error" && r.phase === "error" && Object.is(e.error, r.error)) return;
355
+ this.#e?.set(r);
356
+ } finally {
357
+ this.#n = void 0;
358
+ }
359
+ }
360
+ #o(e) {
361
+ let t = this.#e;
362
+ if (!t?.get()) throw Error("Readable bridge has been disposed");
363
+ d(e);
364
+ let n = t.subscribe(() => (this.#e?.get() || n[r](), e()));
365
+ return n;
366
+ }
367
+ [r]() {
368
+ let e = this.#e;
369
+ if (!e?.get()) return this.#i();
370
+ let t = [];
371
+ try {
372
+ e.set(void 0);
373
+ } catch (n) {
374
+ if (e.get()) throw n;
375
+ t.push(n);
376
+ }
377
+ this.#e = void 0;
378
+ try {
379
+ this.#t?.(), this.#t = void 0;
380
+ } catch (e) {
381
+ t.push(e);
382
+ }
383
+ if (t.length) {
384
+ let e = t.length === 1 ? t[0] : AggregateError(t, "Readable bridge cleanup failed");
385
+ throw this.#r = { value: e }, e;
386
+ }
387
+ }
388
+ };
389
+ function j(e, t) {
390
+ let n = e?.defer;
391
+ if (typeof n != "function") throw TypeError("bridge() expects a resource owner");
392
+ let i = s(t, "bridge()"), a = { defer: (t) => Reflect.apply(n, e, [t]) }, o = new A(i);
393
+ try {
394
+ return o.start(a), o.view;
395
+ } catch (e) {
396
+ try {
397
+ o[r]();
398
+ } catch (t) {
399
+ throw AggregateError([e, t], "Readable bridge construction failed");
400
+ }
401
+ throw e;
402
+ }
375
403
  }
376
- function x(s, e) {
377
- if (!s || typeof s.dispose != "function" || typeof s[w] != "function")
378
- throw new TypeError(`${e} must return a Disposable`);
404
+ // src/abort.ts
405
+ function M(e) {
406
+ return Reflect.get(AbortSignal.prototype, "aborted", e);
379
407
  }
380
- function T(s, e) {
381
- if (!s || typeof s.dispose != "function" || typeof s[y] != "function")
382
- throw new TypeError(`${e} must return an AsyncDisposable`);
408
+ function N(e) {
409
+ return Reflect.get(AbortSignal.prototype, "reason", e);
383
410
  }
384
- function A(s) {
385
- const e = s && typeof s.dispose == "function" && typeof s[y] == "function" ? m(s.result) : void 0;
386
- if (!e)
387
- throw new TypeError("ObservationOwner.spawn() must return an ObservationTask");
388
- return e;
411
+ function P(t, n) {
412
+ if (typeof n != "function") throw TypeError("Abort listener must be a function");
413
+ if (M(t)) return n(N(t)), () => {};
414
+ let r = {
415
+ signal: t,
416
+ callback: n
417
+ }, i;
418
+ function a() {
419
+ r = void 0, i?.(), i = void 0;
420
+ }
421
+ function o() {
422
+ if (!r || !M(r.signal)) return;
423
+ let { signal: e, callback: t } = r, n = N(e);
424
+ a(), t(n);
425
+ }
426
+ return i = e(t, o), r ? o() : a(), a;
389
427
  }
390
- function v(s) {
391
- a(s.dispose(), "Readable subscriptions must dispose synchronously");
428
+ // src/observe.ts
429
+ var F = class {
430
+ #e;
431
+ #t;
432
+ #n;
433
+ #r;
434
+ #i = !1;
435
+ #a = { present: !1 };
436
+ #o = !1;
437
+ #s;
438
+ #c;
439
+ #l;
440
+ #u;
441
+ constructor(e, t, n) {
442
+ this.#e = {
443
+ phase: "active",
444
+ binding: {
445
+ owner: e,
446
+ source: t,
447
+ observer: n
448
+ }
449
+ };
450
+ }
451
+ start() {
452
+ this.#i = !0;
453
+ try {
454
+ this.#d();
455
+ } finally {
456
+ this.#i = !1;
457
+ }
458
+ }
459
+ #d() {
460
+ let { owner: e, source: t } = this.#S();
461
+ this.#t = c(t.subscribe(() => this.#p())), this.#g();
462
+ let n = e.spawn((e) => this.#m(e)), r = l(n, "ObservationOwner.spawn() must return an ObservationTask");
463
+ this.#s = r;
464
+ let i = o(n.result);
465
+ if (!i) throw TypeError("ObservationOwner.spawn() must return an ObservationTask");
466
+ i.then(() => this.#b(r), () => this.#b(r));
467
+ }
468
+ attachOwnerCleanup(e) {
469
+ this.#c = e;
470
+ }
471
+ dispose() {
472
+ if (this.#u) return this.#u;
473
+ let e = Promise.withResolvers();
474
+ return this.#u = e.promise, this.#c = void 0, this.#C("disposed"), this.#l?.(), (this.#i ? Promise.resolve().then(() => this.#f()) : this.#f()).then(e.resolve, e.reject), e.promise;
475
+ }
476
+ async #f() {
477
+ let e = [];
478
+ try {
479
+ this.#t?.(), this.#t = void 0;
480
+ } catch (t) {
481
+ e.push(t);
482
+ }
483
+ await L(this.#y(), e);
484
+ try {
485
+ await this.#v();
486
+ } catch (t) {
487
+ e.push(t);
488
+ }
489
+ if (e.length === 1) throw e[0];
490
+ if (e.length > 1) throw AggregateError(e, "Observation cleanup failed");
491
+ }
492
+ #p() {
493
+ this.#e.phase === "active" && (this.#o = !0, this.#l?.());
494
+ }
495
+ async #m(e) {
496
+ let t = P(e, () => this.#l?.());
497
+ try {
498
+ for (; this.#e.phase === "active" && !M(e);) {
499
+ if (!this.#o) {
500
+ let e = Promise.withResolvers();
501
+ this.#l = e.resolve, await e.promise, this.#l = void 0;
502
+ }
503
+ for (; this.#e.phase === "active" && !M(e) && this.#o;) this.#o = !1, await this.#h(e);
504
+ }
505
+ } catch (e) {
506
+ if (this.#e.phase !== "active") throw e;
507
+ try {
508
+ await this.#x();
509
+ } catch (t) {
510
+ throw AggregateError([e, t], "Observation stopped after a replacement failed");
511
+ }
512
+ throw e;
513
+ } finally {
514
+ t();
515
+ }
516
+ }
517
+ async #h(e) {
518
+ if (this.#e.phase !== "active" || M(e)) return;
519
+ let t = this.#e.binding.source.get();
520
+ if (!(this.#a.present && Object.is(t, this.#a.value))) {
521
+ try {
522
+ await this.#v();
523
+ } catch (e) {
524
+ throw this.#e.phase === "active" && await this.#x(), e;
525
+ }
526
+ this.#a = { present: !1 }, !(this.#e.phase !== "active" || M(e)) && this.#g(e);
527
+ }
528
+ }
529
+ #g(e) {
530
+ let { owner: t } = this.#S(), n = t.child("observation");
531
+ if (this.#n = l(n, "ObservationOwner.child() must return an AsyncDisposable"), e && M(e)) return;
532
+ let r = this.#S().source.get();
533
+ e && M(e) || (this.#_(r, n), this.#e.phase === "active" && (this.#a = {
534
+ present: !0,
535
+ value: r
536
+ }));
537
+ }
538
+ #_(e, t) {
539
+ let { observer: n } = this.#S();
540
+ a(n(e, t), "Observers must be synchronous; use owner.spawn() for async work");
541
+ }
542
+ #v() {
543
+ let e = this.#n;
544
+ if (e) return this.#r ??= Promise.resolve().then(e).then(() => {
545
+ this.#n = void 0, this.#r = void 0;
546
+ });
547
+ }
548
+ #y() {
549
+ let e = this.#s;
550
+ return this.#s = void 0, e;
551
+ }
552
+ #b(e) {
553
+ this.#s === e && (this.#s = void 0);
554
+ }
555
+ async #x() {
556
+ this.#C("stopped"), this.#s = void 0;
557
+ let e = this.#c;
558
+ e ? await e() : await this.dispose();
559
+ }
560
+ #S() {
561
+ let e = this.#e;
562
+ if (e.phase !== "active") throw Error("Observation is not active");
563
+ return e.binding;
564
+ }
565
+ #C(e) {
566
+ this.#e = { phase: e }, this.#a = { present: !1 }, this.#o = !1;
567
+ }
568
+ };
569
+ function I(e, t, n) {
570
+ let r = s(t, "observe()");
571
+ if (typeof n != "function") throw TypeError("Observer must be a function");
572
+ let a = e?.defer, o = e?.child, c = e?.spawn;
573
+ if (typeof a != "function" || typeof o != "function" || typeof c != "function") throw TypeError("observe() expects an observation owner");
574
+ let u = {
575
+ defer: (t) => Reflect.apply(a, e, [t]),
576
+ child: (t) => Reflect.apply(o, e, [t]),
577
+ spawn: (t) => Reflect.apply(c, e, [t])
578
+ }, d = new F(u, r, n), f = l(u.defer(() => d.dispose()), "ObservationOwner.defer() must return an AsyncDisposable");
579
+ d.attachOwnerCleanup(f);
580
+ try {
581
+ return d.start(), Object.freeze({ [i]: f });
582
+ } catch (e) {
583
+ try {
584
+ Promise.resolve(f()).catch(() => void 0);
585
+ } catch (t) {
586
+ throw AggregateError([e, t], "Observation construction failed");
587
+ }
588
+ throw e;
589
+ }
392
590
  }
393
- async function h(s, e) {
394
- if (s)
395
- try {
396
- await s.dispose();
397
- } catch (t) {
398
- e.push(t);
399
- }
591
+ async function L(e, t) {
592
+ if (e) try {
593
+ await e();
594
+ } catch (e) {
595
+ t.push(e);
596
+ }
400
597
  }
401
- export {
402
- I as batch,
403
- q as computed,
404
- B as observe,
405
- L as signal
406
- };
598
+ export { T as batch, j as bridge, D as computed, I as observe, E as signal };
package/dist/observe.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { AsyncDisposable, Readable } from './protocol';
1
+ import { AsyncDisposable, Readable } from './protocol.js';
2
2
  export interface ObservationTask<T = void> extends AsyncDisposable {
3
3
  readonly result: Promise<T>;
4
4
  }
5
5
  export interface ObservationOwner<Child extends AsyncDisposable = AsyncDisposable> {
6
- readonly cleanup: (dispose: () => unknown) => AsyncDisposable;
7
- readonly lifetime: (label: string) => Child;
6
+ readonly defer: (dispose: () => unknown) => AsyncDisposable;
7
+ readonly child: (label: string) => Child;
8
8
  readonly spawn: <T>(task: (signal: AbortSignal) => T | PromiseLike<T>) => ObservationTask<T>;
9
9
  }
10
10
  export type Observer<T, Child extends AsyncDisposable> = (value: T, lifetime: Child) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"observe.d.ts","sourceRoot":"","sources":["../src/observe.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,eAAe,EAEpB,KAAK,QAAQ,EACd,MAAM,YAAY,CAAC;AAKpB,MAAM,WAAW,eAAe,CAAC,CAAC,GAAG,IAAI,CAAE,SAAQ,eAAe;IAChE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB,CAAC,KAAK,SAAS,eAAe,GAAG,eAAe;IAC/E,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,OAAO,KAAK,eAAe,CAAC;IAC9D,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC;IAC5C,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,eAAe,CAAC,CAAC,CAAC,CAAC;CAC9F;AAED,MAAM,MAAM,QAAQ,CAAC,CAAC,EAAE,KAAK,SAAS,eAAe,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,KAAK,IAAI,CAAC;AAgN7F,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,SAAS,eAAe,EACtD,KAAK,EAAE,gBAAgB,CAAC,KAAK,CAAC,EAC9B,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EACnB,QAAQ,EAAE,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,GAC3B,eAAe,CAyBjB"}
1
+ {"version":3,"file":"observe.d.ts","sourceRoot":"","sources":["../src/observe.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,eAAe,EACpB,KAAK,QAAQ,EACd,MAAM,eAAe,CAAC;AAKvB,MAAM,WAAW,eAAe,CAAC,CAAC,GAAG,IAAI,CAAE,SAAQ,eAAe;IAChE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB,CAAC,KAAK,SAAS,eAAe,GAAG,eAAe;IAC/E,QAAQ,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,OAAO,KAAK,eAAe,CAAC;IAC5D,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC;IACzC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,eAAe,CAAC,CAAC,CAAC,CAAC;CAC9F;AAED,MAAM,MAAM,QAAQ,CAAC,CAAC,EAAE,KAAK,SAAS,eAAe,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,KAAK,IAAI,CAAC;AAoO7F,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,SAAS,eAAe,EACtD,KAAK,EAAE,gBAAgB,CAAC,KAAK,CAAC,EAC9B,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EACnB,QAAQ,EAAE,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,GAC3B,eAAe,CAiCjB"}
@@ -1,14 +1,16 @@
1
- export { assertPromiseRuntime, resolveDisposalSymbol, asyncDisposeSymbol, disposeSymbol, } from './disposal-runtime';
1
+ export { assertPromiseRuntime, resolveDisposalSymbol, asyncDisposeSymbol, disposeSymbol, } from './disposal-runtime.js';
2
2
  export interface Disposable {
3
- dispose(): void;
4
3
  [Symbol.dispose](): void;
5
4
  }
6
5
  export interface AsyncDisposable {
7
- dispose(): Promise<void>;
8
- [Symbol.asyncDispose](): Promise<void>;
6
+ [Symbol.asyncDispose](): PromiseLike<void>;
9
7
  }
10
8
  export interface Readable<T> {
11
9
  get(): T;
12
10
  subscribe(listener: () => void): Disposable;
13
11
  }
12
+ export declare function captureReadable<T>(source: Readable<T>, operation: string): Readable<T>;
13
+ export declare function captureUnsubscribe(subscription: Disposable): () => void;
14
+ export declare function captureRelease(value: AsyncDisposable, message: string): () => PromiseLike<void>;
15
+ export declare function assertListener(listener: unknown): asserts listener is () => void;
14
16
  //# sourceMappingURL=protocol.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,aAAa,GACd,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,UAAU;IACzB,OAAO,IAAI,IAAI,CAAC;IAChB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,GAAG,IAAI,CAAC,CAAC;IACT,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,UAAU,CAAC;CAC7C"}
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,aAAa,GACd,MAAM,uBAAuB,CAAC;AAE/B,MAAM,WAAW,UAAU;IACzB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,GAAG,IAAI,CAAC,CAAC;IACT,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,UAAU,CAAC;CAC7C;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CAStF;AAED,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,UAAU,GAAG,MAAM,IAAI,CASvE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,CAI/F;AAQD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,IAAI,MAAM,IAAI,CAIhF"}
package/dist/signals.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Readable } from './protocol';
1
+ import { Readable } from './protocol.js';
2
2
  declare const dougongSignal: unique symbol;
3
3
  export interface ReadonlySignal<T> extends Readable<T> {
4
4
  readonly [dougongSignal]: true;
@@ -1 +1 @@
1
- {"version":3,"file":"signals.d.ts","sourceRoot":"","sources":["../src/signals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3E,OAAO,CAAC,MAAM,aAAa,EAAE,OAAO,MAAM,CAAC;AAE3C,MAAM,WAAW,cAAc,CAAC,CAAC,CAAE,SAAQ,QAAQ,CAAC,CAAC,CAAC;IACpD,QAAQ,CAAC,CAAC,aAAa,CAAC,EAAE,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,MAAM,CAAC,CAAC,CAAE,SAAQ,cAAc,CAAC,CAAC,CAAC;IAClD,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;CAClC;AA0GD,wBAAgB,KAAK,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,CA4B7C;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAEpD;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAGjE"}
1
+ {"version":3,"file":"signals.d.ts","sourceRoot":"","sources":["../src/signals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkD,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG9F,OAAO,CAAC,MAAM,aAAa,EAAE,OAAO,MAAM,CAAC;AAE3C,MAAM,WAAW,cAAc,CAAC,CAAC,CAAE,SAAQ,QAAQ,CAAC,CAAC,CAAC;IACpD,QAAQ,CAAC,CAAC,aAAa,CAAC,EAAE,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,MAAM,CAAC,CAAC,CAAE,SAAQ,cAAc,CAAC,CAAC,CAAC;IAClD,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;CAClC;AAgHD,wBAAgB,KAAK,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,CAgC7C;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAEpD;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAGjE"}
@@ -1,3 +1,3 @@
1
1
  export declare function assertSynchronous(value: unknown, message: string): void;
2
2
  export declare function captureThenable(value: unknown): Promise<unknown> | undefined;
3
- //# sourceMappingURL=sync-result.d.ts.map
3
+ //# sourceMappingURL=synchronous-result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"synchronous-result.d.ts","sourceRoot":"","sources":["../src/synchronous-result.ts"],"names":[],"mappings":"AACA,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAUvE;AAGD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,SAAS,CAa5E"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dougongjs/reactive",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -8,12 +8,13 @@
8
8
  "browserslist": [
9
9
  "Chrome >= 119",
10
10
  "Edge >= 119",
11
- "Firefox >= 121",
11
+ "Firefox >= 124",
12
12
  "Safari >= 17.4"
13
13
  ],
14
14
  "sideEffects": false,
15
15
  "files": [
16
16
  "dist",
17
+ "runtime",
17
18
  "README.md",
18
19
  "LICENSE"
19
20
  ],
@@ -23,6 +24,13 @@
23
24
  "import": "./dist/index.js"
24
25
  }
25
26
  },
27
+ "imports": {
28
+ "#abort-listener": {
29
+ "types": "./runtime/abort-listener.d.mts",
30
+ "node": "./runtime/abort-listener.node.mjs",
31
+ "default": "./runtime/abort-listener.web.mjs"
32
+ }
33
+ },
26
34
  "description": "Zero-dependency signal, computed, batch and observe primitives.",
27
35
  "license": "MIT",
28
36
  "author": "Dougong contributors",
@@ -47,6 +55,6 @@
47
55
  "access": "public"
48
56
  },
49
57
  "scripts": {
50
- "build": "node ../../scripts/generate-internal.mjs && vite build"
58
+ "build": "node ../../tooling/generate-internal.mjs && vite build"
51
59
  }
52
60
  }
@@ -0,0 +1,2 @@
1
+ // Generated from tooling/internal/abort-listener.d.mts. Run pnpm generate:internal.
2
+ export function listenForAbort(signal: AbortSignal, notify: () => void): () => void;
@@ -0,0 +1,26 @@
1
+ // Generated from tooling/internal/abort-listener.node.mjs. Run pnpm generate:internal.
2
+ import { addAbortListener } from "node:events";
3
+
4
+ export function listenForAbort(signal, notify) {
5
+ // Node supplies stop-resistant listener options to this private registration signal.
6
+ // The caller's genuine EventTarget owns the actual listener and its native GC bookkeeping.
7
+ const registration = new AbortController().signal;
8
+ let detach;
9
+ registration.addEventListener = (type, listener, options) => {
10
+ detach = () => EventTarget.prototype.removeEventListener.call(signal, type, listener);
11
+ try {
12
+ // Only native cancellation may consume the listener. Synthetic abort events can arrive first.
13
+ EventTarget.prototype.addEventListener.call(signal, type, listener, {
14
+ ...options,
15
+ once: false,
16
+ });
17
+ } catch (error) {
18
+ // A native listener hook may throw before or after insertion.
19
+ detach();
20
+ throw error;
21
+ }
22
+ };
23
+ addAbortListener(registration, notify);
24
+ if (!detach) throw new Error("Node did not attach the protected abort listener");
25
+ return detach;
26
+ }
@@ -0,0 +1,7 @@
1
+ // Generated from tooling/internal/abort-listener.web.mjs. Run pnpm generate:internal.
2
+ // Only the controller can abort this private dependent; other listeners can stop propagation on the source.
3
+ export function listenForAbort(signal, notify) {
4
+ const dependent = AbortSignal.any([signal]);
5
+ dependent.addEventListener("abort", notify);
6
+ return () => dependent.removeEventListener("abort", notify);
7
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"sync-result.d.ts","sourceRoot":"","sources":["../src/sync-result.ts"],"names":[],"mappings":"AACA,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAOvE;AAGD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,SAAS,CAO5E"}