@dougongjs/reactive 0.9.0 → 0.11.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
@@ -2,10 +2,10 @@
2
2
 
3
3
  Independent synchronous signals and structurally owned reactive generations. No external runtime dependencies.
4
4
 
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).
5
+ Upgrading from 0.9? Read the [migration guide](https://tangerg.github.io/dougong/en/guide/migrating-to-0.10) and [0.10.0 release notes](https://tangerg.github.io/dougong/en/reference/release-0.10.0). From 0.8.3, complete [Migrating to 0.9.0](https://tangerg.github.io/dougong/en/guide/migrating-to-0.9) first.
6
6
 
7
7
  ```ts
8
- import { batch, computed, signal } from "@dougongjs/reactive";
8
+ import { batch, computed, disposeSymbol, signal } from "@dougongjs/reactive";
9
9
 
10
10
  const first = signal("Dougong");
11
11
  const last = signal("Plugins");
@@ -15,13 +15,19 @@ batch(() => {
15
15
  first.set("Typed");
16
16
  last.set("Composition");
17
17
  });
18
- subscription[Symbol.dispose]();
18
+ subscription[disposeSymbol]();
19
19
  ```
20
20
 
21
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`.
22
22
 
23
+ `batch`, `computed`, `subscribe` and `observe` require synchronous callbacks in both their types and runtime checks. Direct Promise results, PromiseLike results and mixed asynchronous branches are rejected, including when a caller explicitly supplies `void`. Ordinary synchronous return values remain inferred; `Observer<T, Child, Result>` can describe an explicit non-void result. Values whose type was already erased still receive the runtime check.
24
+
23
25
  `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.
24
26
 
25
- ESM; Node.js 22+, Chrome/Edge 119+, Firefox 124+, Safari 17.4+.
27
+ `ObservationTask` describes completion only: it is not generic, and its `result` is `PromiseLike<unknown>`. The required `spawn` accepts a task returning `void | PromiseLike<void>`. Owners may provide a richer generic task API without coupling observation to their result payloads.
28
+
29
+ Results inheriting this module's realm's `Promise.prototype` select the intrinsic native protocol, including decorated local Promises and subclasses. Other results use one captured structural `then`; display tags do not select a protocol. Foreign Promises must expose a conforming public `then`. Adapt decorated foreign results in their origin realm, and box payloads there when another assimilation would change them. See the guide for the precise boundary and disposal guarantees.
30
+
31
+ ESM; <!-- runtime-baseline:start -->Node.js `^24.22.0 || >=26.10.0`, Chrome ≥ 119, Edge ≥ 119, Firefox ≥ 124, Safari ≥ 17.4<!-- runtime-baseline:end -->.
26
32
 
27
33
  [Guide](https://tangerg.github.io/dougong/en/guide/reactive)
package/dist/abort.d.ts CHANGED
@@ -1,4 +1,5 @@
1
+ import { SynchronousResult } from './async-result.js';
1
2
  export declare function isAborted(signal: AbortSignal): boolean;
2
3
  export declare function abortReason(signal: AbortSignal): unknown;
3
- export declare function onAbort(signal: AbortSignal, callback: (reason: unknown) => void): () => void;
4
+ export declare function onAbort<Result>(signal: AbortSignal, callback: (reason: unknown) => SynchronousResult<Result>): () => void;
4
5
  //# sourceMappingURL=abort.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"abort.d.ts","sourceRoot":"","sources":["../src/abort.ts"],"names":[],"mappings":"AAEA,OAAO,EAAqB,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG9E,wBAAgB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAEtD;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAExD;AAED,wBAAgB,OAAO,CAAC,MAAM,EAC5B,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,iBAAiB,CAAC,MAAM,CAAC,GACvD,MAAM,IAAI,CA6BZ"}
@@ -0,0 +1,10 @@
1
+ type WithoutCallableThen<T> = T extends object ? "then" extends keyof T ? Extract<T["then"], (...arguments_: never[]) => unknown> extends never ? T : never : T : T;
2
+ export type SynchronousResult<T> = (T & NoInfer<WithoutCallableThen<T>>) | (T & undefined);
3
+ export interface ResolvedValue<T> {
4
+ readonly value: T;
5
+ }
6
+ export declare function resolveValue<T>(value: T | PromiseLike<T>): Promise<ResolvedValue<Awaited<T>>>;
7
+ export declare function assertSynchronous(value: unknown, message: string): void;
8
+ export declare function captureThenable(value: unknown): Promise<ResolvedValue<unknown>> | undefined;
9
+ export {};
10
+ //# sourceMappingURL=async-result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-result.d.ts","sourceRoot":"","sources":["../src/async-result.ts"],"names":[],"mappings":"AAIA,KAAK,mBAAmB,CAAC,CAAC,IAAI,CAAC,SAAS,MAAM,GAC1C,MAAM,SAAS,MAAM,CAAC,GACpB,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,SAAS,KAAK,GACnE,CAAC,GACD,KAAK,GACP,CAAC,GACH,CAAC,CAAC;AAIN,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;AAE3F,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB;AAED,wBAAsB,YAAY,CAAC,CAAC,EAClC,KAAK,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,GACxB,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAGpC;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAUvE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS,CA0B3F"}
@@ -1 +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"}
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,EAAiB,KAAK,cAAc,EAAe,MAAM,cAAc,CAAC;AAG/E,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,OAAO,KAAK,eAAe,CAAC;CAC7D;AAyJD,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,5 +1,5 @@
1
1
  export { batch, computed, signal, type ReadonlySignal, type Signal } from './signals.js';
2
2
  export { bridge, type BridgeOwner } from './bridge.js';
3
- export type { AsyncDisposable, Disposable, Readable } from './protocol.js';
3
+ export { asyncDisposeSymbol, disposeSymbol, type AsyncDisposable, type Disposable, type Readable, } from './protocol.js';
4
4
  export { observe, type ObservationOwner, type ObservationTask, type Observer } from './observe.js';
5
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,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"}
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,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,QAAQ,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -7,14 +7,20 @@ function n(e, t) {
7
7
  return e ?? Symbol.for(t);
8
8
  }
9
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);
10
+ var r = n(Symbol.dispose, "Symbol.dispose"), i = n(Symbol.asyncDispose, "Symbol.asyncDispose"), a = Promise.prototype, o = a.then;
11
+ async function s(e) {
12
+ return l(e) ?? Object.freeze({ value: e });
13
+ }
14
+ function c(e, t) {
15
+ let n = l(e);
14
16
  if (n) throw n.then(() => void 0, () => void 0), TypeError(t);
15
17
  }
16
- function o(e) {
18
+ function l(e) {
17
19
  if (e === null || typeof e != "object" && typeof e != "function") return;
20
+ if (u(e)) {
21
+ let t = Promise.withResolvers();
22
+ return Reflect.apply(o, e, [(e) => t.resolve(Object.freeze({ value: e })), t.reject]), t.promise;
23
+ }
18
24
  let t = e.then;
19
25
  if (typeof t != "function") return;
20
26
  let n = Promise.withResolvers();
@@ -24,10 +30,19 @@ function o(e) {
24
30
  } catch (e) {
25
31
  n.reject(e);
26
32
  }
27
- }), n.promise;
33
+ }), n.promise.then((e) => Object.freeze({ value: e }));
34
+ }
35
+ function u(e) {
36
+ let t = /* @__PURE__ */ new Set();
37
+ for (let n = Object.getPrototypeOf(e); n !== null; n = Object.getPrototypeOf(n)) {
38
+ if (n === a) return !0;
39
+ if (t.has(n)) throw TypeError("Result has a cyclic prototype chain");
40
+ t.add(n);
41
+ }
42
+ return !1;
28
43
  }
29
44
  // src/protocol.ts
30
- function s(e, t) {
45
+ function d(e, t) {
31
46
  let n = e?.get, r = e?.subscribe;
32
47
  if (typeof n != "function" || typeof r != "function") throw TypeError(`${t} expects a readable source`);
33
48
  return Object.freeze({
@@ -35,22 +50,22 @@ function s(e, t) {
35
50
  subscribe: (t) => Reflect.apply(r, e, [t])
36
51
  });
37
52
  }
38
- function c(e) {
53
+ function f(e) {
39
54
  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");
55
+ return typeof t != "function" && m(e, "Readable.subscribe() must return a Disposable"), () => c(Reflect.apply(t, e, []), "Readable subscriptions must dispose synchronously");
41
56
  }
42
- function l(e, t) {
57
+ function p(e, t) {
43
58
  let n = e?.[i];
44
- return typeof n != "function" && u(e, t), () => Reflect.apply(n, e, []);
59
+ return typeof n != "function" && m(e, t), () => Reflect.apply(n, e, []);
45
60
  }
46
- function u(e, t) {
47
- throw a(e, t), TypeError(t);
61
+ function m(e, t) {
62
+ throw c(e, t), TypeError(t);
48
63
  }
49
- function d(e) {
64
+ function h(e) {
50
65
  if (typeof e != "function") throw TypeError("Signal subscriber must be a function");
51
66
  }
52
67
  // src/signals.ts
53
- var f = 0, p = 0, m = !1, h = !1, g = /* @__PURE__ */ new Set(), _ = /* @__PURE__ */ new Set(), v, y = [], b = class {
68
+ var g = 0, _ = 0, v = !1, y = !1, b = /* @__PURE__ */ new Set(), x = /* @__PURE__ */ new Set(), S, C = [], w = class {
54
69
  #e;
55
70
  constructor(e) {
56
71
  this.#e = e, Object.freeze(this);
@@ -60,64 +75,64 @@ var f = 0, p = 0, m = !1, h = !1, g = /* @__PURE__ */ new Set(), _ = /* @__PURE_
60
75
  this.#e = void 0, e?.();
61
76
  }
62
77
  };
63
- function x() {
64
- if (m) return;
78
+ function T() {
79
+ if (v) return;
65
80
  let e = [];
66
- m = !0;
81
+ v = !0;
67
82
  try {
68
- for (; g.size;) {
69
- let t = g.values().next().value;
70
- g.delete(t);
83
+ for (; b.size;) {
84
+ let t = b.values().next().value;
85
+ b.delete(t);
71
86
  let n = t.listener;
72
87
  if (n) try {
73
- a(n(), "Signal subscribers must be synchronous");
88
+ c(n(), "Signal subscribers must be synchronous");
74
89
  } catch (t) {
75
90
  e.push(t);
76
91
  }
77
92
  }
78
93
  } finally {
79
- g.clear(), m = !1;
94
+ b.clear(), v = !1;
80
95
  }
81
- w(e);
96
+ O(e);
82
97
  }
83
- function S(e) {
84
- for (let t of e) (t.kind === "dependency" ? _ : g).add(t);
85
- if (!h) {
86
- h = !0;
98
+ function E(e) {
99
+ for (let t of e) (t.kind === "dependency" ? x : b).add(t);
100
+ if (!y) {
101
+ y = !0;
87
102
  try {
88
- for (; _.size;) {
89
- let e = _.values().next().value;
90
- _.delete(e);
103
+ for (; x.size;) {
104
+ let e = x.values().next().value;
105
+ x.delete(e);
91
106
  let t = e.listener;
92
107
  t?.();
93
108
  }
94
109
  } finally {
95
- h = !1, _.clear();
110
+ y = !1, x.clear();
96
111
  }
97
- p || x();
112
+ _ || T();
98
113
  }
99
114
  }
100
- function C(e, t, n) {
101
- d(t);
115
+ function D(e, t, n) {
116
+ h(t);
102
117
  let r = {
103
118
  kind: n,
104
119
  listener: t
105
120
  };
106
- return e.add(r), new b(() => {
121
+ return e.add(r), new w(() => {
107
122
  r.listener = void 0, e.delete(r);
108
123
  });
109
124
  }
110
- function w(e) {
125
+ function O(e) {
111
126
  if (e.length === 1) throw e[0];
112
127
  if (e.length > 1) throw AggregateError(e, "Reactive subscribers failed");
113
128
  }
114
- function T(e) {
129
+ function k(e) {
115
130
  if (typeof e != "function") throw TypeError("batch() expects a function");
116
- p++;
131
+ _++;
117
132
  let t;
118
133
  try {
119
134
  let n = e();
120
- a(n, "Reactive batches must be synchronous"), t = {
135
+ c(n, "Reactive batches must be synchronous"), t = {
121
136
  succeeded: !0,
122
137
  value: n
123
138
  };
@@ -127,10 +142,10 @@ function T(e) {
127
142
  error: e
128
143
  };
129
144
  } finally {
130
- p--;
145
+ _--;
131
146
  }
132
- if (!p) try {
133
- x();
147
+ if (!_) try {
148
+ T();
134
149
  } catch (e) {
135
150
  t = t.succeeded ? {
136
151
  succeeded: !1,
@@ -143,23 +158,23 @@ function T(e) {
143
158
  if (!t.succeeded) throw t.error;
144
159
  return t.value;
145
160
  }
146
- function E(e) {
147
- return new O(e).view;
161
+ function A(e) {
162
+ return new M(e).view;
148
163
  }
149
- function D(e) {
164
+ function j(e) {
150
165
  if (typeof e != "function") throw TypeError("computed() expects a function");
151
- return new k(e).view;
166
+ return new N(e).view;
152
167
  }
153
- var O = class {
168
+ var M = class {
154
169
  #e = /* @__PURE__ */ new Set();
155
170
  #t;
156
171
  #n = 0;
157
172
  view;
158
173
  constructor(e) {
159
174
  this.#t = e, this.view = Object.freeze({
160
- get: () => (v?.add(this), this.#t),
175
+ get: () => (S?.add(this), this.#t),
161
176
  set: (e) => this.#r(e),
162
- subscribe: (e) => C(this.#e, e, "observer")
177
+ subscribe: (e) => D(this.#e, e, "observer")
163
178
  });
164
179
  }
165
180
  get version() {
@@ -167,13 +182,13 @@ var O = class {
167
182
  }
168
183
  refresh() {}
169
184
  subscribeInvalidation(e) {
170
- return C(this.#e, e, "dependency");
185
+ return D(this.#e, e, "dependency");
171
186
  }
172
187
  #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));
188
+ if (S) throw TypeError("Computed signal calculations cannot write signals");
189
+ Object.is(this.#t, e) || (this.#t = e, g++, this.#n++, E(this.#e));
175
190
  }
176
- }, k = class e {
191
+ }, N = class e {
177
192
  #e;
178
193
  #t = /* @__PURE__ */ new Set();
179
194
  #n = /* @__PURE__ */ new Map();
@@ -181,7 +196,7 @@ var O = class {
181
196
  #i = -1;
182
197
  #a;
183
198
  #o;
184
- #s = !0;
199
+ #s = -1;
185
200
  #c = 0;
186
201
  view;
187
202
  constructor(e) {
@@ -194,33 +209,33 @@ var O = class {
194
209
  return this.#c;
195
210
  }
196
211
  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);
212
+ if (this.#a !== void 0) throw S && this.#l(this.#a, S), TypeError("Circular computed signal");
213
+ this.#a = C.length, C.push(this);
199
214
  try {
200
215
  this.#g();
201
216
  } finally {
202
- y.pop(), this.#a = void 0;
217
+ C.pop(), this.#a = void 0;
203
218
  }
204
219
  }
205
220
  #l(t, n) {
206
221
  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);
222
+ for (let e of C.slice(t)) for (let t of e.#o ?? e.#n.keys()) r.add(t);
208
223
  for (let t of r) if (t instanceof e) for (let e of t.#n.keys()) r.add(e);
209
224
  else n.add(t);
210
225
  }
211
226
  #u() {
212
- return this.refresh(), v?.add(this), this.#d();
227
+ return this.refresh(), S?.add(this), this.#d();
213
228
  }
214
229
  #d() {
215
230
  let e = this.#r;
216
- if (e.phase === "error") throw e.error;
231
+ if (e.outcome === "error") throw e.error;
217
232
  return e.value;
218
233
  }
219
234
  subscribeInvalidation(e) {
220
235
  return this.#f(e, "dependency");
221
236
  }
222
237
  #f(e, t = "observer") {
223
- d(e);
238
+ h(e);
224
239
  let n = {
225
240
  kind: t,
226
241
  listener: e
@@ -230,12 +245,12 @@ var O = class {
230
245
  } catch (e) {
231
246
  throw this.#t.delete(n), this.#t.size || this.#m(), e;
232
247
  }
233
- return new b(() => {
248
+ return new w(() => {
234
249
  n.listener = void 0, this.#t.delete(n), this.#t.size || this.#m();
235
250
  });
236
251
  }
237
252
  #p = () => {
238
- this.#s || (this.#s = !0, S(this.#t));
253
+ this.#s !== g && (this.#s = g, E(this.#t));
239
254
  };
240
255
  #m() {
241
256
  for (let e of this.#n.values()) e.subscription?.[r](), e.subscription = void 0;
@@ -245,34 +260,33 @@ var O = class {
245
260
  }
246
261
  #g() {
247
262
  let e = this.#r;
248
- if (e && !this.#s && (this.#i === f || !this.#_())) {
249
- this.#i = f;
263
+ if (e && this.#s <= this.#i && (this.#i === g || !this.#_())) {
264
+ this.#i = g;
250
265
  return;
251
266
  }
252
- this.#s = !0;
253
267
  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();
268
+ this.#y(t), (!e || (n.outcome === "value" ? e.outcome !== "value" || !Object.is(e.value, n.value) : e.outcome !== "error" || !Object.is(e.error, n.error))) && this.#c++, this.#r = n, this.#i = g, this.#t.size && this.#h();
255
269
  }
256
270
  #_() {
257
271
  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;
272
+ return this.#s > this.#i;
259
273
  }
260
274
  #v(e) {
261
- let t = v;
262
- this.#o = e, v = e;
275
+ let t = S;
276
+ this.#o = e, S = e;
263
277
  try {
264
278
  let e = this.#e, t = e();
265
- return a(t, "Computed signal calculations must be synchronous"), {
266
- phase: "value",
279
+ return c(t, "Computed signal calculations must be synchronous"), {
280
+ outcome: "value",
267
281
  value: t
268
282
  };
269
283
  } catch (e) {
270
284
  return {
271
- phase: "error",
285
+ outcome: "error",
272
286
  error: e
273
287
  };
274
288
  } finally {
275
- v = t, this.#o = void 0;
289
+ S = t, this.#o = void 0;
276
290
  }
277
291
  }
278
292
  #y(e) {
@@ -285,22 +299,22 @@ var O = class {
285
299
  });
286
300
  }
287
301
  }
288
- }, A = class {
302
+ }, P = "Readable bridge has been disposed", F = class {
289
303
  #e;
290
304
  #t;
291
305
  #n;
292
306
  #r;
293
307
  view;
294
308
  constructor(e) {
295
- this.#e = E({
296
- phase: "value",
309
+ this.#e = A({
310
+ outcome: "value",
297
311
  source: e,
298
312
  value: e.get()
299
313
  }), this.view = Object.freeze({
300
314
  get: () => {
301
315
  let e = this.#e?.get();
302
- if (!e) throw Error("Readable bridge has been disposed");
303
- if (e.phase === "error") throw e.error;
316
+ if (!e) throw Error(P);
317
+ if (e.outcome === "error") throw e.error;
304
318
  return e.value;
305
319
  },
306
320
  subscribe: (e) => this.#o(e)
@@ -309,21 +323,22 @@ var O = class {
309
323
  start(e) {
310
324
  let t = !0, n;
311
325
  try {
312
- n = l(e.defer(() => (this[r](), t ? Promise.resolve().then(() => this.#i()) : void 0)), "BridgeOwner.defer() must return an AsyncDisposable");
326
+ n = p(e.defer(() => (this[r](), t ? Promise.resolve().then(() => this.#i()) : void 0)), "BridgeOwner.defer() must return an AsyncDisposable");
313
327
  let i = this.#e?.get();
314
- if (!i) throw Error("Readable bridge has been disposed");
315
- let a = c(i.source.subscribe(() => this.#a()));
328
+ if (!i) throw Error(P);
329
+ let a = f(i.source.subscribe(() => this.#a()));
316
330
  if (this.#t = a, !this.#e?.get()) {
317
331
  try {
318
332
  a(), this.#t = void 0;
319
333
  } catch (e) {
320
334
  this.#r = { value: e };
321
335
  }
322
- throw Error("Readable bridge has been disposed");
336
+ throw Error(P);
323
337
  }
324
338
  this.#a(), this.view.get();
325
339
  } catch (e) {
326
- throw n && Promise.resolve().then(n).catch(() => void 0), e;
340
+ let t = n;
341
+ throw t && Promise.resolve().then(() => s(t())).catch(() => void 0), e;
327
342
  } finally {
328
343
  t = !1;
329
344
  }
@@ -332,35 +347,37 @@ var O = class {
332
347
  if (this.#r) throw this.#r.value;
333
348
  }
334
349
  #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;
350
+ k(() => {
351
+ let e = this.#e?.get();
352
+ if (!e) return;
353
+ let t = Symbol();
354
+ this.#n = t;
341
355
  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
- };
356
+ let { source: n } = e, r;
357
+ try {
358
+ r = {
359
+ outcome: "value",
360
+ source: n,
361
+ value: n.get()
362
+ };
363
+ } catch (e) {
364
+ r = {
365
+ outcome: "error",
366
+ source: n,
367
+ error: e
368
+ };
369
+ }
370
+ if (this.#n !== t || this.#e?.get() !== e || e.outcome === "value" && r.outcome === "value" && Object.is(e.value, r.value) || e.outcome === "error" && r.outcome === "error" && Object.is(e.error, r.error)) return;
371
+ this.#e?.set(r);
372
+ } finally {
373
+ this.#n = void 0;
353
374
  }
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
- }
375
+ });
359
376
  }
360
377
  #o(e) {
361
378
  let t = this.#e;
362
- if (!t?.get()) throw Error("Readable bridge has been disposed");
363
- d(e);
379
+ if (!t?.get()) throw Error(P);
380
+ h(e);
364
381
  let n = t.subscribe(() => (this.#e?.get() || n[r](), e()));
365
382
  return n;
366
383
  }
@@ -386,10 +403,10 @@ var O = class {
386
403
  }
387
404
  }
388
405
  };
389
- function j(e, t) {
406
+ function I(e, t) {
390
407
  let n = e?.defer;
391
408
  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);
409
+ let i = d(t, "bridge()"), a = { defer: (t) => Reflect.apply(n, e, [t]) }, o = new F(i);
393
410
  try {
394
411
  return o.start(a), o.view;
395
412
  } catch (e) {
@@ -402,15 +419,15 @@ function j(e, t) {
402
419
  }
403
420
  }
404
421
  // src/abort.ts
405
- function M(e) {
422
+ function L(e) {
406
423
  return Reflect.get(AbortSignal.prototype, "aborted", e);
407
424
  }
408
- function N(e) {
425
+ function R(e) {
409
426
  return Reflect.get(AbortSignal.prototype, "reason", e);
410
427
  }
411
- function P(t, n) {
428
+ function z(t, n) {
412
429
  if (typeof n != "function") throw TypeError("Abort listener must be a function");
413
- if (M(t)) return n(N(t)), () => {};
430
+ if (L(t)) return B(n, R(t)), () => {};
414
431
  let r = {
415
432
  signal: t,
416
433
  callback: n
@@ -419,14 +436,17 @@ function P(t, n) {
419
436
  r = void 0, i?.(), i = void 0;
420
437
  }
421
438
  function o() {
422
- if (!r || !M(r.signal)) return;
423
- let { signal: e, callback: t } = r, n = N(e);
424
- a(), t(n);
439
+ if (!r || !L(r.signal)) return;
440
+ let { signal: e, callback: t } = r, n = R(e);
441
+ a(), B(t, n);
425
442
  }
426
443
  return i = e(t, o), r ? o() : a(), a;
427
444
  }
445
+ function B(e, t) {
446
+ c(e(t), "Abort listeners must be synchronous");
447
+ }
428
448
  // src/observe.ts
429
- var F = class {
449
+ var V = "observation", H = class {
430
450
  #e;
431
451
  #t;
432
452
  #n;
@@ -438,6 +458,7 @@ var F = class {
438
458
  #c;
439
459
  #l;
440
460
  #u;
461
+ #d;
441
462
  constructor(e, t, n) {
442
463
  this.#e = {
443
464
  phase: "active",
@@ -451,61 +472,75 @@ var F = class {
451
472
  start() {
452
473
  this.#i = !0;
453
474
  try {
454
- this.#d();
475
+ this.#f();
455
476
  } finally {
456
477
  this.#i = !1;
457
478
  }
458
479
  }
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");
480
+ #f() {
481
+ let { source: e } = this.#w();
482
+ this.#t = f(e.subscribe(() => this.#h())), this.#v();
483
+ let { owner: t } = this.#w(), n = t.spawn((e) => this.#g(e)), r = p(n, "ObservationOwner.spawn() must return an ObservationTask");
463
484
  this.#s = r;
464
- let i = o(n.result);
485
+ let i = l(n.result);
465
486
  if (!i) throw TypeError("ObservationOwner.spawn() must return an ObservationTask");
466
- i.then(() => this.#b(r), () => this.#b(r));
487
+ i.then(() => this.#S(r), () => this.#S(r));
467
488
  }
468
489
  attachOwnerCleanup(e) {
469
- this.#c = e;
490
+ this.#d || (this.#c = e);
491
+ }
492
+ handle = Object.freeze({ [i]: () => this.#p() });
493
+ #p() {
494
+ if (this.#l) return this.#l;
495
+ let e = this.#c;
496
+ if (!e) return this.dispose();
497
+ let t = Promise.withResolvers();
498
+ this.#l = t.promise;
499
+ try {
500
+ s(e()).then(() => t.resolve(), t.reject);
501
+ } catch (e) {
502
+ t.reject(e);
503
+ }
504
+ return t.promise;
470
505
  }
471
506
  dispose() {
472
- if (this.#u) return this.#u;
507
+ if (this.#d) return this.#d;
473
508
  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;
509
+ return this.#d = e.promise, this.#c = void 0, this.#T("disposed"), this.#u?.(), (this.#i ? Promise.resolve().then(() => this.#m()) : this.#m()).then(e.resolve, e.reject), e.promise;
475
510
  }
476
- async #f() {
511
+ async #m() {
477
512
  let e = [];
478
513
  try {
479
514
  this.#t?.(), this.#t = void 0;
480
515
  } catch (t) {
481
516
  e.push(t);
482
517
  }
483
- await L(this.#y(), e);
518
+ await W(this.#x(), e);
484
519
  try {
485
- await this.#v();
520
+ await this.#b();
486
521
  } catch (t) {
487
522
  e.push(t);
488
523
  }
489
524
  if (e.length === 1) throw e[0];
490
525
  if (e.length > 1) throw AggregateError(e, "Observation cleanup failed");
491
526
  }
492
- #p() {
493
- this.#e.phase === "active" && (this.#o = !0, this.#l?.());
527
+ #h() {
528
+ this.#e.phase === "active" && (this.#o = !0, this.#u?.());
494
529
  }
495
- async #m(e) {
496
- let t = P(e, () => this.#l?.());
530
+ async #g(e) {
531
+ let t = z(e, () => this.#u?.());
497
532
  try {
498
- for (; this.#e.phase === "active" && !M(e);) {
533
+ for (; this.#e.phase === "active" && !L(e);) {
499
534
  if (!this.#o) {
500
535
  let e = Promise.withResolvers();
501
- this.#l = e.resolve, await e.promise, this.#l = void 0;
536
+ this.#u = e.resolve, await e.promise, this.#u = void 0;
502
537
  }
503
- for (; this.#e.phase === "active" && !M(e) && this.#o;) this.#o = !1, await this.#h(e);
538
+ for (; this.#e.phase === "active" && !L(e) && this.#o;) this.#o = !1, await this.#_(e);
504
539
  }
505
540
  } catch (e) {
506
541
  if (this.#e.phase !== "active") throw e;
507
542
  try {
508
- await this.#x();
543
+ await this.#C();
509
544
  } catch (t) {
510
545
  throw AggregateError([e, t], "Observation stopped after a replacement failed");
511
546
  }
@@ -514,85 +549,82 @@ var F = class {
514
549
  t();
515
550
  }
516
551
  }
517
- async #h(e) {
518
- if (this.#e.phase !== "active" || M(e)) return;
552
+ async #_(e) {
553
+ if (this.#e.phase !== "active" || L(e)) return;
519
554
  let t = this.#e.binding.source.get();
520
555
  if (!(this.#a.present && Object.is(t, this.#a.value))) {
521
556
  try {
522
- await this.#v();
557
+ await this.#b();
523
558
  } catch (e) {
524
- throw this.#e.phase === "active" && await this.#x(), e;
559
+ throw this.#e.phase === "active" && await this.#C(), e;
525
560
  }
526
- this.#a = { present: !1 }, !(this.#e.phase !== "active" || M(e)) && this.#g(e);
561
+ this.#a = { present: !1 }, !(this.#e.phase !== "active" || L(e)) && this.#v(e);
527
562
  }
528
563
  }
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 = {
564
+ #v(e) {
565
+ let { owner: t } = this.#w(), n = t.child(V);
566
+ if (this.#n = p(n, "ObservationOwner.child() must return an AsyncDisposable"), e && L(e)) return;
567
+ let r = this.#w().source.get();
568
+ e && L(e) || (this.#y(r, n), this.#e.phase === "active" && (this.#a = {
534
569
  present: !0,
535
570
  value: r
536
571
  }));
537
572
  }
538
- #_(e, t) {
539
- let { observer: n } = this.#S();
540
- a(n(e, t), "Observers must be synchronous; use owner.spawn() for async work");
573
+ #y(e, t) {
574
+ let { observer: n } = this.#w();
575
+ c(n(e, t), "Observers must be synchronous; use owner.spawn() for async work");
541
576
  }
542
- #v() {
577
+ #b() {
543
578
  let e = this.#n;
544
- if (e) return this.#r ??= Promise.resolve().then(e).then(() => {
579
+ if (e) return this.#r ??= Promise.resolve().then(() => s(e())).then(() => {
545
580
  this.#n = void 0, this.#r = void 0;
546
581
  });
547
582
  }
548
- #y() {
583
+ #x() {
549
584
  let e = this.#s;
550
585
  return this.#s = void 0, e;
551
586
  }
552
- #b(e) {
587
+ #S(e) {
553
588
  this.#s === e && (this.#s = void 0);
554
589
  }
555
- async #x() {
556
- this.#C("stopped"), this.#s = void 0;
557
- let e = this.#c;
558
- e ? await e() : await this.dispose();
590
+ async #C() {
591
+ this.#T("stopped"), this.#s = void 0, await this.#p();
559
592
  }
560
- #S() {
593
+ #w() {
561
594
  let e = this.#e;
562
595
  if (e.phase !== "active") throw Error("Observation is not active");
563
596
  return e.binding;
564
597
  }
565
- #C(e) {
598
+ #T(e) {
566
599
  this.#e = { phase: e }, this.#a = { present: !1 }, this.#o = !1;
567
600
  }
568
601
  };
569
- function I(e, t, n) {
570
- let r = s(t, "observe()");
602
+ function U(e, t, n) {
603
+ let r = d(t, "observe()");
571
604
  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);
605
+ let i = e?.defer, a = e?.child, o = e?.spawn;
606
+ if (typeof i != "function" || typeof a != "function" || typeof o != "function") throw TypeError("observe() expects an observation owner");
607
+ let c = {
608
+ defer: (t) => Reflect.apply(i, e, [t]),
609
+ child: (t) => Reflect.apply(a, e, [t]),
610
+ spawn: (t) => Reflect.apply(o, e, [t])
611
+ }, l = new H(c, r, n), u = l.dispose.bind(l);
580
612
  try {
581
- return d.start(), Object.freeze({ [i]: f });
613
+ return u = p(c.defer(u), "ObservationOwner.defer() must return an AsyncDisposable"), l.attachOwnerCleanup(u), l.start(), l.handle;
582
614
  } catch (e) {
583
615
  try {
584
- Promise.resolve(f()).catch(() => void 0);
616
+ s(u()).catch(() => void 0);
585
617
  } catch (t) {
586
618
  throw AggregateError([e, t], "Observation construction failed");
587
619
  }
588
620
  throw e;
589
621
  }
590
622
  }
591
- async function L(e, t) {
623
+ async function W(e, t) {
592
624
  if (e) try {
593
- await e();
625
+ await s(e());
594
626
  } catch (e) {
595
627
  t.push(e);
596
628
  }
597
629
  }
598
- export { T as batch, j as bridge, D as computed, I as observe, E as signal };
630
+ export { i as asyncDisposeSymbol, k as batch, I as bridge, j as computed, r as disposeSymbol, U as observe, A as signal };
package/dist/observe.d.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import { AsyncDisposable, Readable } from './protocol.js';
2
- export interface ObservationTask<T = void> extends AsyncDisposable {
3
- readonly result: Promise<T>;
2
+ import { SynchronousResult } from './async-result.js';
3
+ export interface ObservationTask extends AsyncDisposable {
4
+ readonly result: PromiseLike<unknown>;
4
5
  }
5
6
  export interface ObservationOwner<Child extends AsyncDisposable = AsyncDisposable> {
6
7
  readonly defer: (dispose: () => unknown) => AsyncDisposable;
7
8
  readonly child: (label: string) => Child;
8
- readonly spawn: <T>(task: (signal: AbortSignal) => T | PromiseLike<T>) => ObservationTask<T>;
9
+ readonly spawn: (task: (signal: AbortSignal) => void | PromiseLike<void>) => ObservationTask;
9
10
  }
10
- export type Observer<T, Child extends AsyncDisposable> = (value: T, lifetime: Child) => void;
11
- export declare function observe<T, Child extends AsyncDisposable>(owner: ObservationOwner<Child>, source: Readable<T>, observer: Observer<T, Child>): AsyncDisposable;
11
+ export type Observer<T, Child extends AsyncDisposable, Result = void> = (value: T, lifetime: Child) => SynchronousResult<Result>;
12
+ export declare function observe<T, Child extends AsyncDisposable, Result = void>(owner: ObservationOwner<Child>, source: Readable<T>, observer: Observer<T, Child, Result>): AsyncDisposable;
12
13
  //# sourceMappingURL=observe.d.ts.map
@@ -1 +1 @@
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
+ {"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;AACvB,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,mBAAmB,CAAC;AAI3B,MAAM,WAAW,eAAgB,SAAQ,eAAe;IACtD,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;CACvC;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,IAAI,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,eAAe,CAAC;CAC9F;AAED,MAAM,MAAM,QAAQ,CAAC,CAAC,EAAE,KAAK,SAAS,eAAe,EAAE,MAAM,GAAG,IAAI,IAAI,CACtE,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,KAAK,KACZ,iBAAiB,CAAC,MAAM,CAAC,CAAC;AA2P/B,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,SAAS,eAAe,EAAE,MAAM,GAAG,IAAI,EACrE,KAAK,EAAE,gBAAgB,CAAC,KAAK,CAAC,EAC9B,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EACnB,QAAQ,EAAE,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,GACnC,eAAe,CAkCjB"}
@@ -1,3 +1,4 @@
1
+ import { SynchronousResult } from './async-result.js';
1
2
  export { assertPromiseRuntime, resolveDisposalSymbol, asyncDisposeSymbol, disposeSymbol, } from './disposal-runtime.js';
2
3
  export interface Disposable {
3
4
  [Symbol.dispose](): void;
@@ -7,7 +8,7 @@ export interface AsyncDisposable {
7
8
  }
8
9
  export interface Readable<T> {
9
10
  get(): T;
10
- subscribe(listener: () => void): Disposable;
11
+ subscribe<Result>(listener: () => SynchronousResult<Result>): Disposable;
11
12
  }
12
13
  export declare function captureReadable<T>(source: Readable<T>, operation: string): Readable<T>;
13
14
  export declare function captureUnsubscribe(subscription: Disposable): () => void;
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE9E,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,MAAM,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC,MAAM,CAAC,GAAG,UAAU,CAAC;CAC1E;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,5 @@
1
1
  import { Readable } from './protocol.js';
2
+ import { SynchronousResult } from './async-result.js';
2
3
  declare const dougongSignal: unique symbol;
3
4
  export interface ReadonlySignal<T> extends Readable<T> {
4
5
  readonly [dougongSignal]: true;
@@ -6,8 +7,8 @@ export interface ReadonlySignal<T> extends Readable<T> {
6
7
  export interface Signal<T> extends ReadonlySignal<T> {
7
8
  readonly set: (value: T) => void;
8
9
  }
9
- export declare function batch<T>(callback: () => T): T;
10
+ export declare function batch<T>(callback: () => SynchronousResult<T>): T;
10
11
  export declare function signal<T>(initialValue: T): Signal<T>;
11
- export declare function computed<T>(calculate: () => T): ReadonlySignal<T>;
12
+ export declare function computed<T>(calculate: () => SynchronousResult<T>): ReadonlySignal<T>;
12
13
  export {};
13
14
  //# sourceMappingURL=signals.d.ts.map
@@ -1 +1 @@
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
+ {"version":3,"file":"signals.d.ts","sourceRoot":"","sources":["../src/signals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkD,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9F,OAAO,EAAqB,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE9E,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,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,CAgChE;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAEpD;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAGpF"}
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@dougongjs/reactive",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "type": "module",
5
5
  "engines": {
6
- "node": ">=22"
6
+ "node": "^24.22.0 || >=26.10.0"
7
7
  },
8
8
  "browserslist": [
9
9
  "Chrome >= 119",
@@ -27,8 +27,7 @@
27
27
  "imports": {
28
28
  "#abort-listener": {
29
29
  "types": "./runtime/abort-listener.d.mts",
30
- "node": "./runtime/abort-listener.node.mjs",
31
- "default": "./runtime/abort-listener.web.mjs"
30
+ "default": "./runtime/abort-listener.mjs"
32
31
  }
33
32
  },
34
33
  "description": "Zero-dependency signal, computed, batch and observe primitives.",
@@ -1,2 +1,3 @@
1
1
  // Generated from tooling/internal/abort-listener.d.mts. Run pnpm generate:internal.
2
+ export function assertAbortRuntime(any: unknown): void;
2
3
  export function listenForAbort(signal: AbortSignal, notify: () => void): () => void;
@@ -0,0 +1,45 @@
1
+ // Generated from tooling/internal/abort-listener.mjs. Run pnpm generate:internal.
2
+ // The private dependent must take its state from the source's controller. A runtime whose
3
+ // AbortSignal.any reads the public, shadowable properties instead would create a dependent
4
+ // already aborted with a forged reason, which never reports the real cancellation.
5
+ export function assertAbortRuntime(any) {
6
+ if (typeof any !== "function") {
7
+ throw new Error("Unsupported JavaScript runtime: AbortSignal.any is required");
8
+ }
9
+ const source = new AbortController().signal;
10
+ Object.defineProperty(source, "aborted", { value: true });
11
+ if (Reflect.get(AbortSignal.prototype, "aborted", any.call(AbortSignal, [source]))) {
12
+ throw new Error(
13
+ "Unsupported JavaScript runtime: AbortSignal.any must read native signal state",
14
+ );
15
+ }
16
+ }
17
+
18
+ assertAbortRuntime(AbortSignal.any);
19
+
20
+ // A separate closure prevents the dependent from retaining the source through its registration.
21
+ function weakAbortListener(reference) {
22
+ return () => reference.deref()?.();
23
+ }
24
+
25
+ export function listenForAbort(signal, notify) {
26
+ const dependent = AbortSignal.any([signal]);
27
+ const listener = weakAbortListener(new WeakRef(notify));
28
+ let registration = { signal, notify, dependent, listener };
29
+ function detach() {
30
+ if (!registration) return;
31
+ const attached = registration;
32
+ registration = undefined;
33
+ EventTarget.prototype.removeEventListener.call(attached.signal, "abort", attached.notify);
34
+ attached.dependent.removeEventListener("abort", attached.listener);
35
+ }
36
+ try {
37
+ // Source listeners preserve native composite lifetimes; the private dependent bypasses stopped events.
38
+ EventTarget.prototype.addEventListener.call(signal, "abort", notify);
39
+ dependent.addEventListener("abort", listener);
40
+ } catch (error) {
41
+ detach();
42
+ throw error;
43
+ }
44
+ return detach;
45
+ }
@@ -1,3 +0,0 @@
1
- export declare function assertSynchronous(value: unknown, message: string): void;
2
- export declare function captureThenable(value: unknown): Promise<unknown> | undefined;
3
- //# sourceMappingURL=synchronous-result.d.ts.map
@@ -1 +0,0 @@
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"}
@@ -1,26 +0,0 @@
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
- }
@@ -1,7 +0,0 @@
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
- }