@dougongjs/reactive 0.3.0 → 0.5.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
@@ -81,7 +81,7 @@ On each change to `source`: release the previous `lifetime`, then build a new on
81
81
  - 文档站 / Documentation: https://tangerg.github.io/dougong/
82
82
  - 仓库 / Repository: https://github.com/Tangerg/dougong
83
83
 
84
- > 早期开发阶段(0.0.x),当前不承诺向后兼容。需要 Node.js ≥ 22,或 Chrome / Edge 119、Firefox 121、Safari 17.4 及以上浏览器 / WebView。
85
- > Early development (0.0.x); no backward-compatibility promises yet. Requires Node.js ≥ 22, or Chrome / Edge 119, Firefox 121 or Safari 17.4 and later.
84
+ > 早期 0.x 开发阶段,当前不承诺向后兼容。需要 Node.js ≥ 22,或 Chrome / Edge 119、Firefox 121、Safari 17.4 及以上浏览器 / WebView。
85
+ > 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.
86
86
 
87
87
  MIT
package/dist/index.d.ts CHANGED
@@ -1,15 +1,4 @@
1
- import { Readable } from './protocol';
1
+ export { batch, computed, signal, type ReadonlySignal, type Signal } from './signals';
2
2
  export type { AsyncDisposable, Disposable, Readable } from './protocol';
3
- declare const dougongSignal: unique symbol;
4
- /** A reactive node created by Dougong and safe to compose with computed(). */
5
- export interface ReadonlySignal<T> extends Readable<T> {
6
- readonly [dougongSignal]: true;
7
- }
8
- export interface Signal<T> extends ReadonlySignal<T> {
9
- readonly set: (value: T) => void;
10
- }
11
- export { observe, type ObservationLifetime, type ObservationOwner, type ObservationTask, type Observer, } from './observe';
12
- export declare function batch<T>(callback: () => T): T;
13
- export declare function signal<T>(initialValue: T): Signal<T>;
14
- export declare function computed<T>(calculate: () => T): ReadonlySignal<T>;
3
+ export { observe, type ObservationOwner, type ObservationTask, type Observer } from './observe';
15
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3E,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAExE,OAAO,CAAC,MAAM,aAAa,EAAE,OAAO,MAAM,CAAC;AAE3C,8EAA8E;AAC9E,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;AAED,OAAO,EACL,OAAO,EACP,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,QAAQ,GACd,MAAM,WAAW,CAAC;AA8FnB,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,CAsCpD;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAGjE"}
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"}
package/dist/index.js CHANGED
@@ -1,195 +1,34 @@
1
- function T(t) {
1
+ function D(t) {
2
2
  if (typeof t != "function")
3
3
  throw new Error("Unsupported JavaScript runtime: Promise.withResolvers is required");
4
4
  }
5
- function v(t, e) {
5
+ function w(t, e) {
6
6
  return t ?? Symbol.for(e);
7
7
  }
8
- T(Promise.withResolvers);
9
- const w = v(
8
+ D(Promise.withResolvers);
9
+ const y = w(
10
10
  Symbol.dispose,
11
11
  "Symbol.dispose"
12
- ), d = v(
12
+ ), m = w(
13
13
  Symbol.asyncDispose,
14
14
  "Symbol.asyncDispose"
15
15
  );
16
- function l(t, e) {
17
- if (y(t))
16
+ function p(t, e) {
17
+ if (g(t))
18
18
  throw Promise.resolve(t).catch(() => {
19
19
  }), new TypeError(e);
20
20
  }
21
- function y(t) {
21
+ function g(t) {
22
22
  return t === null || typeof t != "object" && typeof t != "function" ? !1 : typeof t.then == "function";
23
23
  }
24
- class D {
25
- #e;
26
- #s;
27
- #i;
28
- #n = { present: !1 };
29
- #r = !1;
30
- #t;
31
- #o;
32
- #c;
33
- constructor(e, s, i) {
34
- this.#e = { phase: "active", binding: { owner: e, source: s, observer: i } };
35
- }
36
- start() {
37
- const { owner: e, source: s } = this.#w(), i = s.get(), r = e.lifetime("observation");
38
- b(r), this.#i = r;
39
- const n = s.subscribe(() => this.#p());
40
- z(n, "Readable.subscribe()"), this.#s = n, this.#l(i, r), this.#n = { present: !0, value: i };
41
- const o = e.spawn((c) => this.#h(c));
42
- P(o), this.#t = o, o.result.then(
43
- () => this.#v(o),
44
- () => this.#v(o)
45
- );
46
- }
47
- dispose() {
48
- if (this.#c) return this.#c;
49
- const e = Promise.withResolvers();
50
- return this.#c = e.promise, this.#y("disposed"), this.#o?.(), this.#f().then(e.resolve, e.reject), e.promise;
51
- }
52
- async #f() {
53
- const e = [];
54
- if (await a(this.#b(), e), await a(this.#g(), e), await a(this.#u(), e), e.length === 1) throw e[0];
55
- if (e.length > 1) throw new AggregateError(e, "Observation cleanup failed");
56
- }
57
- #p() {
58
- this.#e.phase === "active" && (this.#r = !0, this.#o?.());
59
- }
60
- async #h(e) {
61
- try {
62
- for (; this.#e.phase === "active" && !e.aborted; )
63
- for (await this.#a(e); this.#e.phase === "active" && !e.aborted && this.#r; )
64
- this.#r = !1, await this.#m();
65
- } catch (s) {
66
- if (this.#e.phase !== "active") throw s;
67
- await this.#d([s], "Observation stopped after a replacement failed");
68
- }
69
- }
70
- #a(e) {
71
- return this.#r || this.#e.phase !== "active" || e.aborted ? Promise.resolve() : new Promise((s) => {
72
- const i = () => {
73
- e.removeEventListener("abort", i), this.#o === i && (this.#o = void 0), s();
74
- };
75
- this.#o = i, e.addEventListener("abort", i, { once: !0 }), (this.#r || this.#e.phase !== "active" || e.aborted) && i();
76
- });
77
- }
78
- async #m() {
79
- if (this.#e.phase !== "active") return;
80
- const e = this.#e.binding.source.get();
81
- if (this.#n.present && Object.is(e, this.#n.value)) return;
82
- const s = this.#u();
83
- if (s)
84
- try {
85
- await s.dispose();
86
- } catch (r) {
87
- if (this.#e.phase !== "active") throw r;
88
- return this.#d(
89
- [r],
90
- "Observation stopped because the previous lifetime could not be disposed"
91
- );
92
- }
93
- if (this.#n = { present: !1 }, this.#e.phase !== "active") return;
94
- const i = this.#e.binding.owner.lifetime("observation");
95
- b(i), this.#i = i;
96
- try {
97
- this.#l(e, i);
98
- } catch (r) {
99
- const n = this.#u();
100
- try {
101
- n && await n.dispose();
102
- } catch (o) {
103
- if (this.#e.phase !== "active")
104
- throw new AggregateError(
105
- [r, o],
106
- "Observation callback failed and its resources could not be cleaned up"
107
- );
108
- return this.#d(
109
- [r, o],
110
- "Observation callback failed and its resources could not be cleaned up"
111
- );
112
- }
113
- throw r;
114
- }
115
- this.#e.phase === "active" && (this.#n = { present: !0, value: e });
116
- }
117
- #l(e, s) {
118
- const i = this.#w().observer(e, s);
119
- l(i, "Observers must be synchronous; use lifetime.spawn() for async work");
120
- }
121
- #b() {
122
- const e = this.#s;
123
- return this.#s = void 0, e;
124
- }
125
- #u() {
126
- const e = this.#i;
127
- return this.#i = void 0, e;
128
- }
129
- #g() {
130
- const e = this.#t;
131
- return this.#t = void 0, e;
132
- }
133
- #v(e) {
134
- this.#t === e && (this.#t = void 0);
135
- }
136
- async #d(e, s) {
137
- throw this.#y("stopped"), this.#o?.(), await a(this.#b(), e), await a(this.#u(), e), e.length === 1 ? e[0] : new AggregateError(e, s);
138
- }
139
- #w() {
140
- const e = this.#e;
141
- if (e.phase !== "active") throw new Error("Observation is not active");
142
- return e.binding;
143
- }
144
- #y(e) {
145
- this.#e = { phase: e }, this.#n = { present: !1 }, this.#r = !1;
146
- }
147
- }
148
- function L(t, e, s) {
149
- if (!e || typeof e.get != "function" || typeof e.subscribe != "function")
150
- throw new TypeError("observe() expects a readable source");
151
- if (typeof s != "function") throw new TypeError("Observer must be a function");
152
- if (!t || typeof t.cleanup != "function" || typeof t.lifetime != "function" || typeof t.spawn != "function")
153
- throw new TypeError("observe() expects an observation owner");
154
- const i = new D(t, e, s), r = t.cleanup(() => i.dispose());
155
- R(r, "ObservationOwner.cleanup()");
156
- try {
157
- return i.start(), r;
158
- } catch (n) {
159
- throw i.dispose().catch(() => {
160
- }), n;
161
- }
24
+ let u = 0, l = !1;
25
+ const f = /* @__PURE__ */ new Set(), c = /* @__PURE__ */ new Set();
26
+ let h;
27
+ const d = /* @__PURE__ */ new WeakMap();
28
+ function O(t) {
29
+ return new R(t);
162
30
  }
163
- function z(t, e) {
164
- if (!t || typeof t.dispose != "function" || typeof t[w] != "function")
165
- throw new TypeError(`${e} must return a Disposable`);
166
- }
167
- function R(t, e) {
168
- if (!t || typeof t.dispose != "function" || typeof t[d] != "function")
169
- throw new TypeError(`${e} must return an AsyncDisposable`);
170
- }
171
- function b(t) {
172
- if (!t || typeof t.dispose != "function" || typeof t[d] != "function" || typeof t.cleanup != "function" || typeof t.lifetime != "function" || typeof t.spawn != "function")
173
- throw new TypeError("ObservationOwner.lifetime() must return an ObservationLifetime");
174
- }
175
- function P(t) {
176
- if (!t || typeof t.dispose != "function" || typeof t[d] != "function" || !y(t.result))
177
- throw new TypeError("ObservationOwner.spawn() must return an ObservationTask");
178
- }
179
- async function a(t, e) {
180
- if (t)
181
- try {
182
- await t.dispose();
183
- } catch (s) {
184
- e.push(s);
185
- }
186
- }
187
- let u = 0, h, f;
188
- const p = /* @__PURE__ */ new WeakMap();
189
- function m(t) {
190
- return new j(t);
191
- }
192
- class j {
31
+ class R {
193
32
  #e;
194
33
  constructor(e) {
195
34
  this.#e = e, Object.freeze(this);
@@ -198,53 +37,58 @@ class j {
198
37
  const e = this.#e;
199
38
  this.#e = void 0, e?.();
200
39
  }
201
- [w]() {
40
+ [y]() {
202
41
  this.dispose();
203
42
  }
204
43
  }
205
- function A() {
44
+ function S() {
45
+ if (l) return;
206
46
  const t = [];
207
- for (; h?.size; ) {
208
- const e = h;
209
- h = /* @__PURE__ */ new Set(), O(e, t);
47
+ l = !0;
48
+ try {
49
+ for (; c.size || f.size; ) {
50
+ const e = c.size ? c : f, s = e.values().next().value;
51
+ e.delete(s);
52
+ const i = s.listener;
53
+ if (i)
54
+ try {
55
+ p(i(), "Signal subscribers must be synchronous");
56
+ } catch (n) {
57
+ t.push(n);
58
+ }
59
+ }
60
+ } finally {
61
+ f.clear(), c.clear(), l = !1;
210
62
  }
211
- h = void 0, k(t);
63
+ z(t);
212
64
  }
213
- function g(t) {
214
- if (u) {
215
- const s = h ??= /* @__PURE__ */ new Set();
216
- for (const i of t) s.add(i);
217
- return;
218
- }
219
- const e = [];
220
- O(t, e), k(e);
65
+ function k(t) {
66
+ for (const e of t)
67
+ (e.kind === "dependency" ? c : f).add(e);
68
+ u || S();
221
69
  }
222
- function O(t, e) {
223
- for (const s of t) {
224
- const i = s.listener;
225
- if (i)
226
- try {
227
- i();
228
- } catch (r) {
229
- e.push(r);
230
- }
231
- }
70
+ function v(t, e, s) {
71
+ T(e);
72
+ const i = { kind: s, listener: e };
73
+ return t.add(i), O(() => {
74
+ i.listener = void 0, t.delete(i);
75
+ });
232
76
  }
233
- function k(t) {
77
+ function z(t) {
234
78
  if (t.length === 1) throw t[0];
235
79
  if (t.length > 1)
236
80
  throw new AggregateError(t, "Reactive subscribers failed");
237
81
  }
238
82
  function E(t) {
239
- f?.(t);
83
+ h?.(t);
240
84
  }
241
- function x(t) {
85
+ function I(t) {
242
86
  if (typeof t != "function") throw new TypeError("batch() expects a function");
243
87
  u++;
244
88
  let e;
245
89
  try {
246
90
  const s = t();
247
- l(s, "Reactive batches must be synchronous"), e = { ok: !0, value: s };
91
+ p(s, "Reactive batches must be synchronous"), e = { ok: !0, value: s };
248
92
  } catch (s) {
249
93
  e = { ok: !1, error: s };
250
94
  } finally {
@@ -252,127 +96,306 @@ function x(t) {
252
96
  }
253
97
  if (!u)
254
98
  try {
255
- A();
99
+ S();
256
100
  } catch (s) {
257
101
  e = e.ok ? { ok: !1, error: s } : { ok: !1, error: new AggregateError([e.error, s], "Reactive batch failed") };
258
102
  }
259
103
  if (!e.ok) throw e.error;
260
104
  return e.value;
261
105
  }
262
- function q(t) {
106
+ function L(t) {
263
107
  let e = t, s = 0;
264
- const i = /* @__PURE__ */ new Set(), r = {
108
+ const i = /* @__PURE__ */ new Set(), n = {
265
109
  get() {
266
- return E(r), e;
110
+ return E(n), e;
267
111
  },
268
- set(n) {
269
- Object.is(e, n) || (e = n, s++, g(i));
112
+ set(r) {
113
+ if (h)
114
+ throw new TypeError("Computed signal calculations cannot write signals");
115
+ Object.is(e, r) || (e = r, s++, k(i));
270
116
  },
271
- subscribe(n) {
272
- S(n);
273
- const o = { listener: n };
274
- return i.add(o), m(() => {
275
- o.listener = void 0, i.delete(o);
276
- });
117
+ subscribe(r) {
118
+ return v(i, r, "observer");
277
119
  }
278
120
  };
279
- return p.set(r, {
121
+ return d.set(n, {
280
122
  get version() {
281
123
  return s;
282
124
  },
283
125
  refresh() {
284
- }
285
- }), Object.freeze(r);
126
+ },
127
+ subscribeInvalidation: (r) => v(i, r, "dependency")
128
+ }), Object.freeze(n);
286
129
  }
287
- function B(t) {
130
+ function q(t) {
288
131
  if (typeof t != "function") throw new TypeError("computed() expects a function");
289
- return new C(t).view;
132
+ return new j(t).view;
290
133
  }
291
- class C {
134
+ class j {
292
135
  #e;
293
136
  #s = /* @__PURE__ */ new Set();
294
137
  #i = /* @__PURE__ */ new Map();
295
- #n = { phase: "uninitialized" };
138
+ #n;
296
139
  #r = !1;
297
140
  #t = !0;
298
- #o = 0;
141
+ #a = 0;
299
142
  view;
300
143
  constructor(e) {
301
144
  this.#e = e, this.view = Object.freeze({
302
- get: () => this.#c(),
303
- subscribe: (s) => this.#f(s)
304
- }), p.set(this.view, this);
145
+ get: () => this.#o(),
146
+ subscribe: (s) => this.#h(s)
147
+ }), d.set(this.view, this);
305
148
  }
306
149
  get version() {
307
- return this.#o;
150
+ return this.#a;
308
151
  }
309
152
  refresh() {
310
- this.#a();
153
+ if (this.#r) throw new TypeError("Circular computed signal");
154
+ this.#r = !0;
155
+ try {
156
+ this.#l();
157
+ } finally {
158
+ this.#r = !1;
159
+ }
160
+ }
161
+ #o() {
162
+ return this.refresh(), E(this.view), this.#c();
311
163
  }
312
164
  #c() {
313
- return E(this.view), this.#a();
165
+ const e = this.#n;
166
+ if (e.phase === "error") throw e.error;
167
+ return e.value;
314
168
  }
315
- #f(e) {
316
- S(e);
317
- const s = { listener: e }, i = this.#s.size === 0;
318
- if (this.#s.add(s), i)
169
+ subscribeInvalidation(e) {
170
+ return this.#h(e, "dependency");
171
+ }
172
+ #h(e, s = "observer") {
173
+ T(e);
174
+ const i = { kind: s, listener: e }, n = this.#s.size === 0;
175
+ if (this.#s.add(i), n)
319
176
  try {
320
- this.#t = !0, this.#a();
177
+ this.refresh(), s === "observer" && this.#c(), this.#f();
321
178
  } catch (r) {
322
- throw this.#s.delete(s), this.#s.size || this.#h(), r;
179
+ throw this.#s.delete(i), this.#s.size || this.#u(), r;
323
180
  }
324
- return m(() => {
325
- s.listener = void 0, this.#s.delete(s), this.#s.size || this.#h();
181
+ return O(() => {
182
+ i.listener = void 0, this.#s.delete(i), this.#s.size || this.#u();
326
183
  });
327
184
  }
328
185
  #p = () => {
329
- this.#t || (this.#t = !0, g(this.#s));
186
+ this.#t || (this.#t = !0, k(this.#s));
330
187
  };
331
- #h() {
188
+ #u() {
332
189
  for (const e of this.#i.values())
333
190
  e.subscription?.dispose(), e.subscription = void 0;
334
191
  }
335
- #a() {
192
+ #f() {
193
+ for (const e of this.#i.values())
194
+ e.subscription ??= e.node.subscribeInvalidation(this.#p);
195
+ }
196
+ #l() {
336
197
  const e = this.#n;
337
- if (e.phase === "cached" && !this.#t) {
338
- for (const n of this.#i.values())
339
- if (n.node.refresh(), n.version !== n.node.version) {
198
+ if (e && !this.#t) {
199
+ for (const r of this.#i.values())
200
+ if (r.node.refresh(), r.version !== r.node.version) {
340
201
  this.#t = !0;
341
202
  break;
342
203
  }
343
- if (!this.#t) return e.value;
204
+ if (!this.#t) return;
344
205
  }
345
- if (this.#r) throw new TypeError("Circular computed signal");
346
- const s = /* @__PURE__ */ new Set(), i = f;
347
- this.#r = !0, f = (n) => s.add(n);
348
- let r;
206
+ const s = /* @__PURE__ */ new Set(), i = h;
207
+ h = (r) => s.add(r);
208
+ let n;
349
209
  try {
350
- r = this.#e(), l(r, "Computed signal calculations must be synchronous");
210
+ const r = this.#e();
211
+ p(r, "Computed signal calculations must be synchronous"), n = { phase: "value", value: r };
212
+ } catch (r) {
213
+ n = { phase: "error", error: r };
351
214
  } finally {
352
- f = i, this.#r = !1;
215
+ h = i;
353
216
  }
354
- for (const [n, o] of this.#i)
355
- s.has(n) || (o.subscription?.dispose(), this.#i.delete(n));
356
- for (const n of s) {
357
- const o = p.get(n);
217
+ for (const [r, o] of this.#i)
218
+ s.has(r) || (o.subscription?.dispose(), this.#i.delete(r));
219
+ for (const r of s) {
220
+ const o = d.get(r);
358
221
  if (!o) continue;
359
- const c = this.#i.get(n) ?? {
222
+ const b = this.#i.get(r) ?? {
360
223
  node: o,
361
224
  version: o.version,
362
225
  subscription: void 0
363
226
  };
364
- c.version = o.version, this.#s.size && !c.subscription && (c.subscription = n.subscribe(this.#p)), this.#i.set(n, c);
227
+ b.version = o.version, this.#i.set(r, b);
365
228
  }
366
- return (e.phase === "uninitialized" || !Object.is(e.value, r)) && this.#o++, this.#n = { phase: "cached", value: r }, this.#t = !1, r;
229
+ (!e || (n.phase === "value" ? e.phase !== "value" || !Object.is(e.value, n.value) : e.phase !== "error" || !Object.is(e.error, n.error))) && this.#a++, this.#n = n, this.#t = !1, this.#s.size && this.#f();
367
230
  }
368
231
  }
369
- function S(t) {
232
+ function T(t) {
370
233
  if (typeof t != "function")
371
234
  throw new TypeError("Signal subscriber must be a function");
372
235
  }
236
+ class P {
237
+ #e;
238
+ #s;
239
+ #i;
240
+ #n = { present: !1 };
241
+ #r = !1;
242
+ #t;
243
+ #a;
244
+ #o;
245
+ #c;
246
+ constructor(e, s, i) {
247
+ this.#e = { phase: "active", binding: { owner: e, source: s, observer: i } };
248
+ }
249
+ start() {
250
+ const { owner: e, source: s } = this.#b(), i = s.subscribe(() => this.#p());
251
+ x(i, "Readable.subscribe()"), this.#s = i, this.#v();
252
+ const n = e.spawn((r) => this.#u(r));
253
+ A(n), this.#t = n, n.result.then(
254
+ () => this.#y(n),
255
+ () => this.#y(n)
256
+ );
257
+ }
258
+ attachOwnerCleanup(e) {
259
+ this.#a = e;
260
+ }
261
+ dispose() {
262
+ if (this.#c) return this.#c;
263
+ const e = Promise.withResolvers();
264
+ return this.#c = e.promise, this.#a = void 0, this.#m("disposed"), this.#o?.(), this.#h().then(e.resolve, e.reject), e.promise;
265
+ }
266
+ async #h() {
267
+ const e = [];
268
+ if (await a(this.#w(), e), await a(this.#O(), e), await a(this.#d(), e), e.length === 1) throw e[0];
269
+ if (e.length > 1) throw new AggregateError(e, "Observation cleanup failed");
270
+ }
271
+ #p() {
272
+ this.#e.phase === "active" && (this.#r = !0, this.#o?.());
273
+ }
274
+ /**
275
+ * The serialization point. Sleeps until invalidated, then replaces the current
276
+ * lifetime, repeating while the value keeps changing. The inner loop collapses
277
+ * a burst of changes into one replacement per settled value rather than one per
278
+ * notification.
279
+ *
280
+ * A failure here stops the observation and reports it. There is no caller to
281
+ * return to — this runs in a spawned task — so continuing would mean silently
282
+ * observing a value with a lifetime that failed to establish.
283
+ */
284
+ async #u(e) {
285
+ try {
286
+ for (; this.#e.phase === "active" && !e.aborted; )
287
+ for (await this.#f(e); this.#e.phase === "active" && !e.aborted && this.#r; )
288
+ this.#r = !1, await this.#l();
289
+ } catch (s) {
290
+ if (this.#e.phase !== "active") throw s;
291
+ await this.#k(s);
292
+ }
293
+ }
294
+ #f(e) {
295
+ return this.#r || this.#e.phase !== "active" || e.aborted ? Promise.resolve() : new Promise((s) => {
296
+ const i = () => {
297
+ e.removeEventListener("abort", i), this.#o === i && (this.#o = void 0), s();
298
+ };
299
+ this.#o = i, e.addEventListener("abort", i, { once: !0 }), (this.#r || this.#e.phase !== "active" || e.aborted) && i();
300
+ });
301
+ }
302
+ /**
303
+ * Dispose the old lifetime, then create the new one, then call the observer.
304
+ * Strictly in that order: overlapping them would run two owners of whatever the
305
+ * observer set up, and a failed observer would leave the previous lifetime half
306
+ * disposed.
307
+ *
308
+ * `#observed` is cleared while no lifetime is current, so an interrupted
309
+ * replacement cannot leave a value marked as observed when nothing is
310
+ * observing it.
311
+ */
312
+ async #l() {
313
+ if (this.#e.phase !== "active") return;
314
+ const e = this.#e.binding.source.get();
315
+ if (this.#n.present && Object.is(e, this.#n.value)) return;
316
+ const s = this.#d();
317
+ s && await s.dispose(), this.#n = { present: !1 }, this.#e.phase === "active" && this.#v();
318
+ }
319
+ #v() {
320
+ const { owner: e, source: s } = this.#b(), i = e.lifetime("observation");
321
+ C(i, "ObservationOwner.lifetime()"), this.#i = i;
322
+ const n = s.get();
323
+ this.#g(n, i), this.#e.phase === "active" && (this.#n = { present: !0, value: n });
324
+ }
325
+ #g(e, s) {
326
+ const i = this.#b().observer(e, s);
327
+ p(i, "Observers must be synchronous; use owner.spawn() for async work");
328
+ }
329
+ #w() {
330
+ const e = this.#s;
331
+ return this.#s = void 0, e;
332
+ }
333
+ #d() {
334
+ const e = this.#i;
335
+ return this.#i = void 0, e;
336
+ }
337
+ #O() {
338
+ const e = this.#t;
339
+ return this.#t = void 0, e;
340
+ }
341
+ #S() {
342
+ const e = this.#a;
343
+ return this.#a = void 0, e;
344
+ }
345
+ #y(e) {
346
+ this.#t === e && (this.#t = void 0);
347
+ }
348
+ async #k(e) {
349
+ const s = [e];
350
+ throw this.#m("stopped"), this.#o?.(), await a(this.#w(), s), await a(this.#d(), s), this.#t = void 0, await a(this.#S(), s), s.length === 1 ? s[0] : new AggregateError(s, "Observation stopped after a replacement failed");
351
+ }
352
+ #b() {
353
+ const e = this.#e;
354
+ if (e.phase !== "active") throw new Error("Observation is not active");
355
+ return e.binding;
356
+ }
357
+ #m(e) {
358
+ this.#e = { phase: e }, this.#n = { present: !1 }, this.#r = !1;
359
+ }
360
+ }
361
+ function B(t, e, s) {
362
+ if (!e || typeof e.get != "function" || typeof e.subscribe != "function")
363
+ throw new TypeError("observe() expects a readable source");
364
+ if (typeof s != "function") throw new TypeError("Observer must be a function");
365
+ if (!t || typeof t.cleanup != "function" || typeof t.lifetime != "function" || typeof t.spawn != "function")
366
+ throw new TypeError("observe() expects an observation owner");
367
+ const i = new P(t, e, s), n = t.cleanup(() => i.dispose());
368
+ C(n, "ObservationOwner.cleanup()"), i.attachOwnerCleanup(n);
369
+ try {
370
+ return i.start(), n;
371
+ } catch (r) {
372
+ throw i.dispose().catch(() => {
373
+ }), r;
374
+ }
375
+ }
376
+ function x(t, e) {
377
+ if (!t || typeof t.dispose != "function" || typeof t[y] != "function")
378
+ throw new TypeError(`${e} must return a Disposable`);
379
+ }
380
+ function C(t, e) {
381
+ if (!t || typeof t.dispose != "function" || typeof t[m] != "function")
382
+ throw new TypeError(`${e} must return an AsyncDisposable`);
383
+ }
384
+ function A(t) {
385
+ if (!t || typeof t.dispose != "function" || typeof t[m] != "function" || !g(t.result))
386
+ throw new TypeError("ObservationOwner.spawn() must return an ObservationTask");
387
+ }
388
+ async function a(t, e) {
389
+ if (t)
390
+ try {
391
+ await t.dispose();
392
+ } catch (s) {
393
+ e.push(s);
394
+ }
395
+ }
373
396
  export {
374
- x as batch,
375
- B as computed,
376
- L as observe,
377
- q as signal
397
+ I as batch,
398
+ q as computed,
399
+ B as observe,
400
+ L as signal
378
401
  };
package/dist/observe.d.ts CHANGED
@@ -2,20 +2,15 @@ import { AsyncDisposable, Readable } from './protocol';
2
2
  export interface ObservationTask<T = void> extends AsyncDisposable {
3
3
  readonly result: Promise<T>;
4
4
  }
5
- export interface ObservationLifetime extends AsyncDisposable {
6
- readonly cleanup: (dispose: () => unknown) => AsyncDisposable;
7
- readonly lifetime: (label: string) => ObservationLifetime;
8
- readonly spawn: <T>(task: (signal: AbortSignal) => T | PromiseLike<T>) => ObservationTask<T>;
9
- }
10
- export interface ObservationOwner<Child extends ObservationLifetime = ObservationLifetime> {
5
+ export interface ObservationOwner<Child extends AsyncDisposable = AsyncDisposable> {
11
6
  readonly cleanup: (dispose: () => unknown) => AsyncDisposable;
12
7
  readonly lifetime: (label: string) => Child;
13
8
  readonly spawn: <T>(task: (signal: AbortSignal) => T | PromiseLike<T>) => ObservationTask<T>;
14
9
  }
15
- export type Observer<T, Child extends ObservationLifetime> = (value: T, lifetime: Child) => void;
10
+ export type Observer<T, Child extends AsyncDisposable> = (value: T, lifetime: Child) => void;
16
11
  /**
17
12
  * Lifetime-aware synchronization built only from the public source and
18
13
  * Lifetime protocols. It is a reactive-layer combinator, not a Core hook.
19
14
  */
20
- export declare function observe<T, Child extends ObservationLifetime>(owner: ObservationOwner<Child>, source: Readable<T>, observer: Observer<T, Child>): AsyncDisposable;
15
+ export declare function observe<T, Child extends AsyncDisposable>(owner: ObservationOwner<Child>, source: Readable<T>, observer: Observer<T, Child>): AsyncDisposable;
21
16
  //# sourceMappingURL=observe.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"observe.d.ts","sourceRoot":"","sources":["../src/observe.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,eAAe,EAEpB,KAAK,QAAQ,EAEd,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,eAAe,CAAC,CAAC,GAAG,IAAI,CAAE,SAAQ,eAAe;IAChE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAoB,SAAQ,eAAe;IAC1D,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,OAAO,KAAK,eAAe,CAAC;IAC9D,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,mBAAmB,CAAC;IAC1D,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,WAAW,gBAAgB,CAAC,KAAK,SAAS,mBAAmB,GAAG,mBAAmB;IACvF,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,mBAAmB,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,KAAK,IAAI,CAAC;AA+MjG;;;GAGG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,SAAS,mBAAmB,EAC1D,KAAK,EAAE,gBAAgB,CAAC,KAAK,CAAC,EAC9B,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EACnB,QAAQ,EAAE,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,GAC3B,eAAe,CA0BjB"}
1
+ {"version":3,"file":"observe.d.ts","sourceRoot":"","sources":["../src/observe.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,eAAe,EAEpB,KAAK,QAAQ,EAEd,MAAM,YAAY,CAAC;AAwBpB,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;AA2N7F;;;GAGG;AACH,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,CA2BjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,wBAAgB,oBAAoB,CAClC,aAAa,EAAE,OAAO,GACrB,OAAO,CAAC,aAAa,IAAI,OAAO,OAAO,CAAC,aAAa,CAIvD;AAED,8FAA8F;AAC9F,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,IAAI,EAAE,gBAAgB,GAAG,qBAAqB,UAG/C;AAID,eAAO,MAAM,aAAa,EAAE,OAAO,MAAM,CAAC,OAGhB,CAAC;AAC3B,eAAO,MAAM,kBAAkB,EAAE,OAAO,MAAM,CAAC,YAGhB,CAAC;AAEhC,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,MAAM,QAAQ,GAAG,UAAU,GAAG,eAAe,CAAC;AAEpD,2FAA2F;AAC3F,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":"AAMA,2FAA2F;AAC3F,wBAAgB,oBAAoB,CAClC,aAAa,EAAE,OAAO,GACrB,OAAO,CAAC,aAAa,IAAI,OAAO,OAAO,CAAC,aAAa,CAIvD;AAED,8FAA8F;AAC9F,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,IAAI,EAAE,gBAAgB,GAAG,qBAAqB,UAG/C;AAID,eAAO,MAAM,aAAa,EAAE,OAAO,MAAM,CAAC,OAGhB,CAAC;AAC3B,eAAO,MAAM,kBAAkB,EAAE,OAAO,MAAM,CAAC,YAGhB,CAAC;AAEhC,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,MAAM,QAAQ,GAAG,UAAU,GAAG,eAAe,CAAC;AAEpD,2FAA2F;AAC3F,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,GAAG,IAAI,CAAC,CAAC;IACT,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,UAAU,CAAC;CAC7C"}
@@ -0,0 +1,30 @@
1
+ import { Readable } from './protocol';
2
+ declare const dougongSignal: unique symbol;
3
+ /** A reactive node created by Dougong and safe to compose with computed(). */
4
+ export interface ReadonlySignal<T> extends Readable<T> {
5
+ readonly [dougongSignal]: true;
6
+ }
7
+ export interface Signal<T> extends ReadonlySignal<T> {
8
+ readonly set: (value: T) => void;
9
+ }
10
+ /**
11
+ * Defers notification until the outermost batch returns, so a group of writes is
12
+ * observed once. Nested calls join the outer batch.
13
+ *
14
+ * Notifications are queued by subscription, not by callback, so the same
15
+ * function subscribed to two signals is still called twice — deduplicating by
16
+ * callback would silently merge two independent subscriptions.
17
+ *
18
+ * If both the body and the flush fail, both errors are reported. A subscriber
19
+ * failing during flush must not hide why the batch itself failed.
20
+ */
21
+ export declare function batch<T>(callback: () => T): T;
22
+ /**
23
+ * A value with subscribers. Writes are compared with `Object.is`, so setting the
24
+ * same value notifies nobody — which is what makes `computed` chains stable
25
+ * under idempotent updates.
26
+ */
27
+ export declare function signal<T>(initialValue: T): Signal<T>;
28
+ export declare function computed<T>(calculate: () => T): ReadonlySignal<T>;
29
+ export {};
30
+ //# sourceMappingURL=signals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signals.d.ts","sourceRoot":"","sources":["../src/signals.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAkC,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3E,OAAO,CAAC,MAAM,aAAa,EAAE,OAAO,MAAM,CAAC;AAE3C,8EAA8E;AAC9E,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;AA4HD;;;;;;;;;;GAUG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,CA4B7C;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAoCpD;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAGjE"}
@@ -1 +1 @@
1
- {"version":3,"file":"sync-result.d.ts","sourceRoot":"","sources":["../src/sync-result.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAQvE;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAAC,OAAO,CAAC,CAGxE"}
1
+ {"version":3,"file":"sync-result.d.ts","sourceRoot":"","sources":["../src/sync-result.ts"],"names":[],"mappings":"AAaA,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAQvE;AAID,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAAC,OAAO,CAAC,CAGxE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dougongjs/reactive",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"