@dougongjs/reactive 0.4.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/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,203 +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
16
  function p(t, e) {
17
- if (y(t))
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
- #c;
32
- #o;
33
- #a;
34
- constructor(e, s, i) {
35
- this.#e = { phase: "active", binding: { owner: e, source: s, observer: i } };
36
- }
37
- start() {
38
- const { owner: e, source: s } = this.#w(), i = s.get(), r = e.lifetime("observation");
39
- b(r), this.#i = r;
40
- const n = s.subscribe(() => this.#u());
41
- C(n, "Readable.subscribe()"), this.#s = n, this.#d(i, r), this.#n = { present: !0, value: i };
42
- const o = e.spawn((c) => this.#h(c));
43
- R(o), this.#t = o, o.result.then(
44
- () => this.#v(o),
45
- () => this.#v(o)
46
- );
47
- }
48
- attachOwnerCleanup(e) {
49
- this.#c = e;
50
- }
51
- dispose() {
52
- if (this.#a) return this.#a;
53
- const e = Promise.withResolvers();
54
- return this.#a = e.promise, this.#c = void 0, this.#y("disposed"), this.#o?.(), this.#p().then(e.resolve, e.reject), e.promise;
55
- }
56
- async #p() {
57
- const e = [];
58
- if (await a(this.#b(), e), await a(this.#O(), e), await a(this.#f(), e), e.length === 1) throw e[0];
59
- if (e.length > 1) throw new AggregateError(e, "Observation cleanup failed");
60
- }
61
- #u() {
62
- this.#e.phase === "active" && (this.#r = !0, this.#o?.());
63
- }
64
- async #h(e) {
65
- try {
66
- for (; this.#e.phase === "active" && !e.aborted; )
67
- for (await this.#m(e); this.#e.phase === "active" && !e.aborted && this.#r; )
68
- this.#r = !1, await this.#g();
69
- } catch (s) {
70
- if (this.#e.phase !== "active") throw s;
71
- await this.#l([s], "Observation stopped after a replacement failed");
72
- }
73
- }
74
- #m(e) {
75
- return this.#r || this.#e.phase !== "active" || e.aborted ? Promise.resolve() : new Promise((s) => {
76
- const i = () => {
77
- e.removeEventListener("abort", i), this.#o === i && (this.#o = void 0), s();
78
- };
79
- this.#o = i, e.addEventListener("abort", i, { once: !0 }), (this.#r || this.#e.phase !== "active" || e.aborted) && i();
80
- });
81
- }
82
- async #g() {
83
- if (this.#e.phase !== "active") return;
84
- const e = this.#e.binding.source.get();
85
- if (this.#n.present && Object.is(e, this.#n.value)) return;
86
- const s = this.#f();
87
- if (s)
88
- try {
89
- await s.dispose();
90
- } catch (r) {
91
- if (this.#e.phase !== "active") throw r;
92
- return this.#l(
93
- [r],
94
- "Observation stopped because the previous lifetime could not be disposed"
95
- );
96
- }
97
- if (this.#n = { present: !1 }, this.#e.phase !== "active") return;
98
- const i = this.#e.binding.owner.lifetime("observation");
99
- b(i), this.#i = i;
100
- try {
101
- this.#d(e, i);
102
- } catch (r) {
103
- const n = this.#f();
104
- try {
105
- n && await n.dispose();
106
- } catch (o) {
107
- if (this.#e.phase !== "active")
108
- throw new AggregateError(
109
- [r, o],
110
- "Observation callback failed and its resources could not be cleaned up"
111
- );
112
- return this.#l(
113
- [r, o],
114
- "Observation callback failed and its resources could not be cleaned up"
115
- );
116
- }
117
- throw r;
118
- }
119
- this.#e.phase === "active" && (this.#n = { present: !0, value: e });
120
- }
121
- #d(e, s) {
122
- const i = this.#w().observer(e, s);
123
- p(i, "Observers must be synchronous; use lifetime.spawn() for async work");
124
- }
125
- #b() {
126
- const e = this.#s;
127
- return this.#s = void 0, e;
128
- }
129
- #f() {
130
- const e = this.#i;
131
- return this.#i = void 0, e;
132
- }
133
- #O() {
134
- const e = this.#t;
135
- return this.#t = void 0, e;
136
- }
137
- #k() {
138
- const e = this.#c;
139
- return this.#c = void 0, e;
140
- }
141
- #v(e) {
142
- this.#t === e && (this.#t = void 0);
143
- }
144
- async #l(e, s) {
145
- throw this.#y("stopped"), this.#o?.(), await a(this.#b(), e), await a(this.#f(), e), this.#t = void 0, await a(this.#k(), e), e.length === 1 ? e[0] : new AggregateError(e, s);
146
- }
147
- #w() {
148
- const e = this.#e;
149
- if (e.phase !== "active") throw new Error("Observation is not active");
150
- return e.binding;
151
- }
152
- #y(e) {
153
- this.#e = { phase: e }, this.#n = { present: !1 }, this.#r = !1;
154
- }
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);
155
30
  }
156
- function L(t, e, s) {
157
- if (!e || typeof e.get != "function" || typeof e.subscribe != "function")
158
- throw new TypeError("observe() expects a readable source");
159
- if (typeof s != "function") throw new TypeError("Observer must be a function");
160
- if (!t || typeof t.cleanup != "function" || typeof t.lifetime != "function" || typeof t.spawn != "function")
161
- throw new TypeError("observe() expects an observation owner");
162
- const i = new D(t, e, s), r = t.cleanup(() => i.dispose());
163
- z(r, "ObservationOwner.cleanup()"), i.attachOwnerCleanup(r);
164
- try {
165
- return i.start(), r;
166
- } catch (n) {
167
- throw i.dispose().catch(() => {
168
- }), n;
169
- }
170
- }
171
- function C(t, e) {
172
- if (!t || typeof t.dispose != "function" || typeof t[w] != "function")
173
- throw new TypeError(`${e} must return a Disposable`);
174
- }
175
- function z(t, e) {
176
- if (!t || typeof t.dispose != "function" || typeof t[d] != "function")
177
- throw new TypeError(`${e} must return an AsyncDisposable`);
178
- }
179
- function b(t) {
180
- if (!t || typeof t.dispose != "function" || typeof t[d] != "function" || typeof t.cleanup != "function" || typeof t.lifetime != "function" || typeof t.spawn != "function")
181
- throw new TypeError("ObservationOwner.lifetime() must return an ObservationLifetime");
182
- }
183
- function R(t) {
184
- if (!t || typeof t.dispose != "function" || typeof t[d] != "function" || !y(t.result))
185
- throw new TypeError("ObservationOwner.spawn() must return an ObservationTask");
186
- }
187
- async function a(t, e) {
188
- if (t)
189
- try {
190
- await t.dispose();
191
- } catch (s) {
192
- e.push(s);
193
- }
194
- }
195
- let u = 0, h, f;
196
- const l = /* @__PURE__ */ new WeakMap();
197
- function m(t) {
198
- return new P(t);
199
- }
200
- class P {
31
+ class R {
201
32
  #e;
202
33
  constructor(e) {
203
34
  this.#e = e, Object.freeze(this);
@@ -206,47 +37,52 @@ class P {
206
37
  const e = this.#e;
207
38
  this.#e = void 0, e?.();
208
39
  }
209
- [w]() {
40
+ [y]() {
210
41
  this.dispose();
211
42
  }
212
43
  }
213
- function j() {
44
+ function S() {
45
+ if (l) return;
214
46
  const t = [];
215
- for (; h?.size; ) {
216
- const e = h;
217
- 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;
218
62
  }
219
- h = void 0, k(t);
63
+ z(t);
220
64
  }
221
- function g(t) {
222
- if (u) {
223
- const s = h ??= /* @__PURE__ */ new Set();
224
- for (const i of t) s.add(i);
225
- return;
226
- }
227
- const e = [];
228
- 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();
229
69
  }
230
- function O(t, e) {
231
- for (const s of t) {
232
- const i = s.listener;
233
- if (i)
234
- try {
235
- p(i(), "Signal subscribers must be synchronous");
236
- } catch (r) {
237
- e.push(r);
238
- }
239
- }
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
+ });
240
76
  }
241
- function k(t) {
77
+ function z(t) {
242
78
  if (t.length === 1) throw t[0];
243
79
  if (t.length > 1)
244
80
  throw new AggregateError(t, "Reactive subscribers failed");
245
81
  }
246
82
  function E(t) {
247
- f?.(t);
83
+ h?.(t);
248
84
  }
249
- function x(t) {
85
+ function I(t) {
250
86
  if (typeof t != "function") throw new TypeError("batch() expects a function");
251
87
  u++;
252
88
  let e;
@@ -260,127 +96,306 @@ function x(t) {
260
96
  }
261
97
  if (!u)
262
98
  try {
263
- j();
99
+ S();
264
100
  } catch (s) {
265
101
  e = e.ok ? { ok: !1, error: s } : { ok: !1, error: new AggregateError([e.error, s], "Reactive batch failed") };
266
102
  }
267
103
  if (!e.ok) throw e.error;
268
104
  return e.value;
269
105
  }
270
- function q(t) {
106
+ function L(t) {
271
107
  let e = t, s = 0;
272
- const i = /* @__PURE__ */ new Set(), r = {
108
+ const i = /* @__PURE__ */ new Set(), n = {
273
109
  get() {
274
- return E(r), e;
110
+ return E(n), e;
275
111
  },
276
- set(n) {
277
- 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));
278
116
  },
279
- subscribe(n) {
280
- S(n);
281
- const o = { listener: n };
282
- return i.add(o), m(() => {
283
- o.listener = void 0, i.delete(o);
284
- });
117
+ subscribe(r) {
118
+ return v(i, r, "observer");
285
119
  }
286
120
  };
287
- return l.set(r, {
121
+ return d.set(n, {
288
122
  get version() {
289
123
  return s;
290
124
  },
291
125
  refresh() {
292
- }
293
- }), Object.freeze(r);
126
+ },
127
+ subscribeInvalidation: (r) => v(i, r, "dependency")
128
+ }), Object.freeze(n);
294
129
  }
295
- function B(t) {
130
+ function q(t) {
296
131
  if (typeof t != "function") throw new TypeError("computed() expects a function");
297
- return new A(t).view;
132
+ return new j(t).view;
298
133
  }
299
- class A {
134
+ class j {
300
135
  #e;
301
136
  #s = /* @__PURE__ */ new Set();
302
137
  #i = /* @__PURE__ */ new Map();
303
- #n = { phase: "uninitialized" };
138
+ #n;
304
139
  #r = !1;
305
140
  #t = !0;
306
- #c = 0;
141
+ #a = 0;
307
142
  view;
308
143
  constructor(e) {
309
144
  this.#e = e, this.view = Object.freeze({
310
145
  get: () => this.#o(),
311
- subscribe: (s) => this.#a(s)
312
- }), l.set(this.view, this);
146
+ subscribe: (s) => this.#h(s)
147
+ }), d.set(this.view, this);
313
148
  }
314
149
  get version() {
315
- return this.#c;
150
+ return this.#a;
316
151
  }
317
152
  refresh() {
318
- this.#h();
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
+ }
319
160
  }
320
161
  #o() {
321
- return E(this.view), this.#h();
162
+ return this.refresh(), E(this.view), this.#c();
322
163
  }
323
- #a(e) {
324
- S(e);
325
- const s = { listener: e }, i = this.#s.size === 0;
326
- if (this.#s.add(s), i)
164
+ #c() {
165
+ const e = this.#n;
166
+ if (e.phase === "error") throw e.error;
167
+ return e.value;
168
+ }
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)
327
176
  try {
328
- this.#t = !0, this.#h();
177
+ this.refresh(), s === "observer" && this.#c(), this.#f();
329
178
  } catch (r) {
330
- throw this.#s.delete(s), this.#s.size || this.#u(), r;
179
+ throw this.#s.delete(i), this.#s.size || this.#u(), r;
331
180
  }
332
- return m(() => {
333
- s.listener = void 0, this.#s.delete(s), this.#s.size || this.#u();
181
+ return O(() => {
182
+ i.listener = void 0, this.#s.delete(i), this.#s.size || this.#u();
334
183
  });
335
184
  }
336
185
  #p = () => {
337
- this.#t || (this.#t = !0, g(this.#s));
186
+ this.#t || (this.#t = !0, k(this.#s));
338
187
  };
339
188
  #u() {
340
189
  for (const e of this.#i.values())
341
190
  e.subscription?.dispose(), e.subscription = void 0;
342
191
  }
343
- #h() {
192
+ #f() {
193
+ for (const e of this.#i.values())
194
+ e.subscription ??= e.node.subscribeInvalidation(this.#p);
195
+ }
196
+ #l() {
344
197
  const e = this.#n;
345
- if (e.phase === "cached" && !this.#t) {
346
- for (const n of this.#i.values())
347
- 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) {
348
201
  this.#t = !0;
349
202
  break;
350
203
  }
351
- if (!this.#t) return e.value;
204
+ if (!this.#t) return;
352
205
  }
353
- if (this.#r) throw new TypeError("Circular computed signal");
354
- const s = /* @__PURE__ */ new Set(), i = f;
355
- this.#r = !0, f = (n) => s.add(n);
356
- let r;
206
+ const s = /* @__PURE__ */ new Set(), i = h;
207
+ h = (r) => s.add(r);
208
+ let n;
357
209
  try {
358
- r = this.#e(), p(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 };
359
214
  } finally {
360
- f = i, this.#r = !1;
215
+ h = i;
361
216
  }
362
- for (const [n, o] of this.#i)
363
- s.has(n) || (o.subscription?.dispose(), this.#i.delete(n));
364
- for (const n of s) {
365
- const o = l.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);
366
221
  if (!o) continue;
367
- const c = this.#i.get(n) ?? {
222
+ const b = this.#i.get(r) ?? {
368
223
  node: o,
369
224
  version: o.version,
370
225
  subscription: void 0
371
226
  };
372
- 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);
373
228
  }
374
- return (e.phase === "uninitialized" || !Object.is(e.value, r)) && this.#c++, 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();
375
230
  }
376
231
  }
377
- function S(t) {
232
+ function T(t) {
378
233
  if (typeof t != "function")
379
234
  throw new TypeError("Signal subscriber must be a function");
380
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
+ }
381
396
  export {
382
- x as batch,
383
- B as computed,
384
- L as observe,
385
- q as signal
397
+ I as batch,
398
+ q as computed,
399
+ B as observe,
400
+ L as signal
386
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+NjG;;;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,CA2BjB"}
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.4.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"