@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 +2 -2
- package/dist/index.d.ts +2 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +286 -263
- package/dist/observe.d.ts +3 -8
- package/dist/observe.d.ts.map +1 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/signals.d.ts +30 -0
- package/dist/signals.d.ts.map +1 -0
- package/dist/sync-result.d.ts.map +1 -1
- package/package.json +1 -1
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
|
-
>
|
|
85
|
-
> Early
|
|
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
|
-
|
|
1
|
+
export { batch, computed, signal, type ReadonlySignal, type Signal } from './signals';
|
|
2
2
|
export type { AsyncDisposable, Disposable, Readable } from './protocol';
|
|
3
|
-
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
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
|
|
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
|
|
5
|
+
function w(t, e) {
|
|
6
6
|
return t ?? Symbol.for(e);
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
const
|
|
8
|
+
D(Promise.withResolvers);
|
|
9
|
+
const y = w(
|
|
10
10
|
Symbol.dispose,
|
|
11
11
|
"Symbol.dispose"
|
|
12
|
-
),
|
|
12
|
+
), m = w(
|
|
13
13
|
Symbol.asyncDispose,
|
|
14
14
|
"Symbol.asyncDispose"
|
|
15
15
|
);
|
|
16
|
-
function
|
|
17
|
-
if (
|
|
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
|
|
21
|
+
function g(t) {
|
|
22
22
|
return t === null || typeof t != "object" && typeof t != "function" ? !1 : typeof t.then == "function";
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
[
|
|
40
|
+
[y]() {
|
|
202
41
|
this.dispose();
|
|
203
42
|
}
|
|
204
43
|
}
|
|
205
|
-
function
|
|
44
|
+
function S() {
|
|
45
|
+
if (l) return;
|
|
206
46
|
const t = [];
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
63
|
+
z(t);
|
|
212
64
|
}
|
|
213
|
-
function
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
|
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
|
-
|
|
83
|
+
h?.(t);
|
|
240
84
|
}
|
|
241
|
-
function
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
106
|
+
function L(t) {
|
|
263
107
|
let e = t, s = 0;
|
|
264
|
-
const i = /* @__PURE__ */ new Set(),
|
|
108
|
+
const i = /* @__PURE__ */ new Set(), n = {
|
|
265
109
|
get() {
|
|
266
|
-
return E(
|
|
110
|
+
return E(n), e;
|
|
267
111
|
},
|
|
268
|
-
set(
|
|
269
|
-
|
|
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(
|
|
272
|
-
|
|
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
|
|
121
|
+
return d.set(n, {
|
|
280
122
|
get version() {
|
|
281
123
|
return s;
|
|
282
124
|
},
|
|
283
125
|
refresh() {
|
|
284
|
-
}
|
|
285
|
-
|
|
126
|
+
},
|
|
127
|
+
subscribeInvalidation: (r) => v(i, r, "dependency")
|
|
128
|
+
}), Object.freeze(n);
|
|
286
129
|
}
|
|
287
|
-
function
|
|
130
|
+
function q(t) {
|
|
288
131
|
if (typeof t != "function") throw new TypeError("computed() expects a function");
|
|
289
|
-
return new
|
|
132
|
+
return new j(t).view;
|
|
290
133
|
}
|
|
291
|
-
class
|
|
134
|
+
class j {
|
|
292
135
|
#e;
|
|
293
136
|
#s = /* @__PURE__ */ new Set();
|
|
294
137
|
#i = /* @__PURE__ */ new Map();
|
|
295
|
-
#n
|
|
138
|
+
#n;
|
|
296
139
|
#r = !1;
|
|
297
140
|
#t = !0;
|
|
298
|
-
#
|
|
141
|
+
#a = 0;
|
|
299
142
|
view;
|
|
300
143
|
constructor(e) {
|
|
301
144
|
this.#e = e, this.view = Object.freeze({
|
|
302
|
-
get: () => this.#
|
|
303
|
-
subscribe: (s) => this.#
|
|
304
|
-
}),
|
|
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.#
|
|
150
|
+
return this.#a;
|
|
308
151
|
}
|
|
309
152
|
refresh() {
|
|
310
|
-
this.#
|
|
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
|
-
|
|
165
|
+
const e = this.#n;
|
|
166
|
+
if (e.phase === "error") throw e.error;
|
|
167
|
+
return e.value;
|
|
314
168
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
|
177
|
+
this.refresh(), s === "observer" && this.#c(), this.#f();
|
|
321
178
|
} catch (r) {
|
|
322
|
-
throw this.#s.delete(
|
|
179
|
+
throw this.#s.delete(i), this.#s.size || this.#u(), r;
|
|
323
180
|
}
|
|
324
|
-
return
|
|
325
|
-
|
|
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,
|
|
186
|
+
this.#t || (this.#t = !0, k(this.#s));
|
|
330
187
|
};
|
|
331
|
-
#
|
|
188
|
+
#u() {
|
|
332
189
|
for (const e of this.#i.values())
|
|
333
190
|
e.subscription?.dispose(), e.subscription = void 0;
|
|
334
191
|
}
|
|
335
|
-
#
|
|
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
|
|
338
|
-
for (const
|
|
339
|
-
if (
|
|
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
|
|
204
|
+
if (!this.#t) return;
|
|
344
205
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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()
|
|
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
|
-
|
|
215
|
+
h = i;
|
|
353
216
|
}
|
|
354
|
-
for (const [
|
|
355
|
-
s.has(
|
|
356
|
-
for (const
|
|
357
|
-
const o =
|
|
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
|
|
222
|
+
const b = this.#i.get(r) ?? {
|
|
360
223
|
node: o,
|
|
361
224
|
version: o.version,
|
|
362
225
|
subscription: void 0
|
|
363
226
|
};
|
|
364
|
-
|
|
227
|
+
b.version = o.version, this.#i.set(r, b);
|
|
365
228
|
}
|
|
366
|
-
|
|
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
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
package/dist/observe.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/protocol.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"
|
|
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":"
|
|
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"}
|