@earendil-works/chord 1.0.4 → 1.2.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/CHANGELOG.md +20 -0
- package/README.md +77 -194
- package/dist/cell.d.ts +101 -0
- package/dist/cell.d.ts.map +1 -0
- package/dist/cell.js +316 -0
- package/dist/cell.js.map +1 -0
- package/dist/cluster.d.ts +26 -0
- package/dist/cluster.d.ts.map +1 -0
- package/dist/cluster.js +214 -0
- package/dist/cluster.js.map +1 -0
- package/dist/connection.d.ts +157 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +757 -0
- package/dist/connection.js.map +1 -0
- package/dist/context/index.d.ts +12 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js.map +1 -1
- package/dist/contract.d.ts +133 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +157 -0
- package/dist/contract.js.map +1 -0
- package/dist/declare.d.ts +70 -0
- package/dist/declare.d.ts.map +1 -0
- package/dist/declare.js +22 -0
- package/dist/declare.js.map +1 -0
- package/dist/delta/apply-immutable-trusted.js.map +1 -1
- package/dist/delta/diff.d.ts +1 -1
- package/dist/delta/diff.d.ts.map +1 -1
- package/dist/delta/diff.js.map +1 -1
- package/dist/delta/index.d.ts +2 -2
- package/dist/delta/index.d.ts.map +1 -1
- package/dist/delta/index.js +41 -0
- package/dist/delta/index.js.map +1 -1
- package/dist/delta/revision-validator.d.ts +1 -1
- package/dist/delta/revision-validator.d.ts.map +1 -1
- package/dist/delta/revision-validator.js.map +1 -1
- package/dist/delta/tracker.d.ts +1 -1
- package/dist/delta/tracker.d.ts.map +1 -1
- package/dist/delta/tracker.js +40 -4
- package/dist/delta/tracker.js.map +1 -1
- package/dist/errors.d.ts +27 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +44 -0
- package/dist/errors.js.map +1 -0
- package/dist/host.d.ts +254 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +795 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +17 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -8
- package/dist/index.js.map +1 -1
- package/dist/json.d.ts +9 -1
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js.map +1 -1
- package/dist/link.d.ts +72 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +169 -0
- package/dist/link.js.map +1 -0
- package/dist/loader.d.ts +4 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +68 -0
- package/dist/loader.js.map +1 -0
- package/dist/manager.d.ts +115 -0
- package/dist/manager.d.ts.map +1 -0
- package/dist/manager.js +319 -0
- package/dist/manager.js.map +1 -0
- package/dist/node.d.ts +3 -4
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +3 -2
- package/dist/node.js.map +1 -1
- package/dist/publish.d.ts +5 -0
- package/dist/publish.d.ts.map +1 -0
- package/dist/publish.js +40 -0
- package/dist/publish.js.map +1 -0
- package/dist/record.d.ts +320 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/record.js +1292 -0
- package/dist/record.js.map +1 -0
- package/dist/registry.d.ts +25 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +6 -0
- package/dist/registry.js.map +1 -0
- package/dist/reload.d.ts +118 -0
- package/dist/reload.d.ts.map +1 -0
- package/dist/reload.js +589 -0
- package/dist/reload.js.map +1 -0
- package/dist/remote.d.ts +70 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +308 -0
- package/dist/remote.js.map +1 -0
- package/dist/schema.d.ts +21 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +228 -0
- package/dist/schema.js.map +1 -0
- package/dist/state.d.ts +92 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/{services/state.js → state.js} +28 -138
- package/dist/state.js.map +1 -0
- package/dist/types.d.ts +62 -262
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/websocket.d.ts +16 -0
- package/dist/websocket.d.ts.map +1 -0
- package/dist/websocket.js +15 -0
- package/dist/websocket.js.map +1 -0
- package/dist/wire.d.ts +135 -0
- package/dist/wire.d.ts.map +1 -0
- package/dist/wire.js +112 -0
- package/dist/wire.js.map +1 -0
- package/docs/guide.md +750 -0
- package/docs/spec.md +261 -0
- package/docs/tutorial.md +909 -0
- package/package.json +10 -11
- package/src/delta/README.md +5 -3
- package/dist/api.d.ts +0 -20
- package/dist/api.d.ts.map +0 -1
- package/dist/api.js +0 -77
- package/dist/api.js.map +0 -1
- package/dist/bundler.d.ts +0 -6
- package/dist/bundler.d.ts.map +0 -1
- package/dist/bundler.js +0 -3
- package/dist/bundler.js.map +0 -1
- package/dist/facets/host.d.ts +0 -12
- package/dist/facets/host.d.ts.map +0 -1
- package/dist/facets/host.js +0 -763
- package/dist/facets/host.js.map +0 -1
- package/dist/facets/loader.d.ts +0 -3
- package/dist/facets/loader.d.ts.map +0 -1
- package/dist/facets/loader.js +0 -5
- package/dist/facets/loader.js.map +0 -1
- package/dist/node/bundle-loader.d.ts +0 -30
- package/dist/node/bundle-loader.d.ts.map +0 -1
- package/dist/node/bundle-loader.js +0 -377
- package/dist/node/bundle-loader.js.map +0 -1
- package/dist/node/bundle.d.ts +0 -26
- package/dist/node/bundle.d.ts.map +0 -1
- package/dist/node/bundle.js +0 -183
- package/dist/node/bundle.js.map +0 -1
- package/dist/node/manifest.d.ts +0 -36
- package/dist/node/manifest.d.ts.map +0 -1
- package/dist/node/manifest.js +0 -6
- package/dist/node/manifest.js.map +0 -1
- package/dist/node/package.d.ts +0 -15
- package/dist/node/package.d.ts.map +0 -1
- package/dist/node/package.js +0 -199
- package/dist/node/package.js.map +0 -1
- package/dist/services/consumer.d.ts +0 -11
- package/dist/services/consumer.d.ts.map +0 -1
- package/dist/services/consumer.js +0 -585
- package/dist/services/consumer.js.map +0 -1
- package/dist/services/errors.d.ts +0 -8
- package/dist/services/errors.d.ts.map +0 -1
- package/dist/services/errors.js +0 -22
- package/dist/services/errors.js.map +0 -1
- package/dist/services/handle.d.ts +0 -15
- package/dist/services/handle.d.ts.map +0 -1
- package/dist/services/handle.js +0 -101
- package/dist/services/handle.js.map +0 -1
- package/dist/services/instances.d.ts +0 -26
- package/dist/services/instances.d.ts.map +0 -1
- package/dist/services/instances.js +0 -140
- package/dist/services/instances.js.map +0 -1
- package/dist/services/loopback.d.ts +0 -5
- package/dist/services/loopback.d.ts.map +0 -1
- package/dist/services/loopback.js +0 -15
- package/dist/services/loopback.js.map +0 -1
- package/dist/services/provider.d.ts +0 -37
- package/dist/services/provider.d.ts.map +0 -1
- package/dist/services/provider.js +0 -521
- package/dist/services/provider.js.map +0 -1
- package/dist/services/state-codec.d.ts +0 -15
- package/dist/services/state-codec.d.ts.map +0 -1
- package/dist/services/state-codec.js +0 -133
- package/dist/services/state-codec.js.map +0 -1
- package/dist/services/state-internals.d.ts +0 -13
- package/dist/services/state-internals.d.ts.map +0 -1
- package/dist/services/state-internals.js +0 -10
- package/dist/services/state-internals.js.map +0 -1
- package/dist/services/state.d.ts +0 -28
- package/dist/services/state.d.ts.map +0 -1
- package/dist/services/state.js.map +0 -1
- package/dist/services/wire.d.ts +0 -63
- package/dist/services/wire.d.ts.map +0 -1
- package/dist/services/wire.js +0 -186
- package/dist/services/wire.js.map +0 -1
package/dist/cell.js
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { diffRevisions, track } from "./delta/index.js";
|
|
2
|
+
import { ChordError } from "./errors.js";
|
|
3
|
+
import { enqueue, holdPublications } from "./publish.js";
|
|
4
|
+
import { assertValid } from "./schema.js";
|
|
5
|
+
let sequence = 0;
|
|
6
|
+
/** Fans out values and delta events; listener errors are reported, not thrown to the writer. */
|
|
7
|
+
class Fanout {
|
|
8
|
+
#values = new Set();
|
|
9
|
+
#events = new Set();
|
|
10
|
+
#report;
|
|
11
|
+
constructor(report) {
|
|
12
|
+
this.#report = report;
|
|
13
|
+
}
|
|
14
|
+
subscribe(listener, current) {
|
|
15
|
+
this.#values.add(listener);
|
|
16
|
+
this.#call(listener, current);
|
|
17
|
+
return () => this.#values.delete(listener);
|
|
18
|
+
}
|
|
19
|
+
events(listener, current) {
|
|
20
|
+
this.#events.add(listener);
|
|
21
|
+
this.#call(listener, { type: "snap", value: current });
|
|
22
|
+
return () => this.#events.delete(listener);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Queues delivery to the listeners subscribed now. A listener that subscribes later already got this revision in
|
|
26
|
+
* its snapshot; one that unsubscribes before delivery is skipped.
|
|
27
|
+
*/
|
|
28
|
+
emit(event) {
|
|
29
|
+
const events = [...this.#events];
|
|
30
|
+
const values = [...this.#values];
|
|
31
|
+
enqueue(() => {
|
|
32
|
+
for (const listener of events)
|
|
33
|
+
if (this.#events.has(listener))
|
|
34
|
+
this.#call(listener, event);
|
|
35
|
+
for (const listener of values)
|
|
36
|
+
if (this.#values.has(listener))
|
|
37
|
+
this.#call(listener, event.value);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
#call(listener, value) {
|
|
41
|
+
try {
|
|
42
|
+
listener(value);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
this.#report(error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** A record-owned state source (cell or derivation). Writes recompute dependents atomically. */
|
|
50
|
+
export class Source {
|
|
51
|
+
key;
|
|
52
|
+
seq = ++sequence;
|
|
53
|
+
dependents = new Set();
|
|
54
|
+
/** Schemas of remote state members currently exposing this source (checked per write in dev mode). */
|
|
55
|
+
schemas = [];
|
|
56
|
+
options;
|
|
57
|
+
fanout;
|
|
58
|
+
constructor(key, options) {
|
|
59
|
+
this.key = key;
|
|
60
|
+
this.options = options;
|
|
61
|
+
this.fanout = new Fanout(options.report);
|
|
62
|
+
}
|
|
63
|
+
subscribe(listener) {
|
|
64
|
+
return this.fanout.subscribe(listener, this.value);
|
|
65
|
+
}
|
|
66
|
+
events(listener) {
|
|
67
|
+
return this.fanout.events(listener, this.value);
|
|
68
|
+
}
|
|
69
|
+
/** Per-write validation, dev mode only. */
|
|
70
|
+
validate(value) {
|
|
71
|
+
if (!this.options.dev)
|
|
72
|
+
return;
|
|
73
|
+
for (const schema of this.ownSchemas())
|
|
74
|
+
assertValid("state", `state ${this.key}`, schema, value);
|
|
75
|
+
for (const schema of this.schemas)
|
|
76
|
+
assertValid("state", `state ${this.key}`, schema, value);
|
|
77
|
+
}
|
|
78
|
+
ownSchemas() {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function unbox(ops, value) {
|
|
83
|
+
const result = [];
|
|
84
|
+
for (const op of ops) {
|
|
85
|
+
if (op[0] === "r" || op[1].length === 0)
|
|
86
|
+
return [["r", value]];
|
|
87
|
+
const path = op[1].slice(1);
|
|
88
|
+
switch (op[0]) {
|
|
89
|
+
case "p":
|
|
90
|
+
result.push(["p", path, op[2], op[3], op[4]]);
|
|
91
|
+
break;
|
|
92
|
+
case "m":
|
|
93
|
+
result.push(["m", path, op[2]]);
|
|
94
|
+
break;
|
|
95
|
+
default:
|
|
96
|
+
// s/d/a/t on the value itself replace it.
|
|
97
|
+
if (path.length === 0)
|
|
98
|
+
return [["r", value]];
|
|
99
|
+
if (op[0] === "d")
|
|
100
|
+
result.push(["d", path]);
|
|
101
|
+
else
|
|
102
|
+
result.push([op[0], path, op[2]]);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
/** A state cell holding any JSON value, validated against its schema at creation and (dev mode) on writes. */
|
|
108
|
+
export class Cell extends Source {
|
|
109
|
+
/** Replaced at a reload commit that changes the schema (state-version bump). */
|
|
110
|
+
schema;
|
|
111
|
+
#tracker;
|
|
112
|
+
#guard;
|
|
113
|
+
/** `initial` is always validated against `schema`; ownership moves to the cell. */
|
|
114
|
+
constructor(key, schema, initial, guard, options) {
|
|
115
|
+
super(key, options);
|
|
116
|
+
assertValid("state", `initial value of state ${key}`, schema, initial);
|
|
117
|
+
this.schema = schema;
|
|
118
|
+
this.#tracker = track({ v: initial });
|
|
119
|
+
this.#guard = guard;
|
|
120
|
+
}
|
|
121
|
+
ownSchemas() {
|
|
122
|
+
return [this.schema];
|
|
123
|
+
}
|
|
124
|
+
get value() {
|
|
125
|
+
return this.#tracker.value.v;
|
|
126
|
+
}
|
|
127
|
+
change(ctx, fn) {
|
|
128
|
+
this.#guard(ctx);
|
|
129
|
+
const change = this.#tracker.beginChange();
|
|
130
|
+
let prepared;
|
|
131
|
+
try {
|
|
132
|
+
const draft = change.state;
|
|
133
|
+
const result = fn(draft.v);
|
|
134
|
+
if (result instanceof Promise)
|
|
135
|
+
throw new TypeError("cell.change callbacks must be synchronous");
|
|
136
|
+
if (result !== undefined) {
|
|
137
|
+
if (result !== draft.v)
|
|
138
|
+
assertValid("state", `state ${this.key}`, this.schema, result);
|
|
139
|
+
draft.v = result;
|
|
140
|
+
}
|
|
141
|
+
prepared = change.prepare();
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
change.abort();
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
this.#publish(prepared, "ops");
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Replaces the whole value. Ownership of `value` moves to the cell: never mutate it afterwards.
|
|
151
|
+
*
|
|
152
|
+
* Subscribers get a structural diff (`diffRevisions`) when `diff` is true, or by default when the new value shares
|
|
153
|
+
* containers with the old one (a new immutable revision of the same state, e.g. mirrored from another store);
|
|
154
|
+
* otherwise a single `r` op. The resulting value is the same either way; only the ops differ.
|
|
155
|
+
*/
|
|
156
|
+
replace(ctx, value, options = {}) {
|
|
157
|
+
this.#guard(ctx);
|
|
158
|
+
const before = this.value;
|
|
159
|
+
const diff = options.diff ?? sharesStructure(before, value);
|
|
160
|
+
const ops = diff ? diffRevisions(before, value) : undefined;
|
|
161
|
+
this.#publish(this.#tracker.prepareReplace({ v: value }), "ops", true, ops);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Installs a migrated value and its schema (reload commit). The caller validated the value and the derivations
|
|
165
|
+
* over it against every schema that will apply, so nothing is checked here. Subscribers receive a reset.
|
|
166
|
+
*/
|
|
167
|
+
reset(value, schema) {
|
|
168
|
+
this.schema = schema;
|
|
169
|
+
this.#publish(this.#tracker.prepareReplace({ v: value }), "reset", false);
|
|
170
|
+
}
|
|
171
|
+
/** Adopts this revision and every derived one, then queues all notifications (cell first, then derivations). */
|
|
172
|
+
#publish(prepared, type, validate = true, ops) {
|
|
173
|
+
if (prepared.ops.length === 0 && type === "ops") {
|
|
174
|
+
this.#tracker.adopt(prepared);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const value = prepared.value.v;
|
|
178
|
+
let candidates;
|
|
179
|
+
try {
|
|
180
|
+
if (validate)
|
|
181
|
+
this.validate(value);
|
|
182
|
+
candidates = computeDependents(this, value, validate);
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
prepared.abort();
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
holdPublications(() => {
|
|
189
|
+
this.#tracker.adopt(prepared);
|
|
190
|
+
const published = ops !== undefined && ops.length > 0 ? ops : unbox(prepared.ops, value);
|
|
191
|
+
this.fanout.emit(type === "reset" ? { type, value } : { type, ops: published, value });
|
|
192
|
+
for (const [derived, next] of candidates)
|
|
193
|
+
derived.install(next);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/** True if `b` reuses at least one container of `a` at the same key: a new revision of the same state. */
|
|
198
|
+
function sharesStructure(a, b) {
|
|
199
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object" || a === b)
|
|
200
|
+
return false;
|
|
201
|
+
if (Array.isArray(a) !== Array.isArray(b))
|
|
202
|
+
return false;
|
|
203
|
+
for (const [key, child] of Object.entries(b)) {
|
|
204
|
+
if (child !== null && typeof child === "object" && a[key] === child)
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
/** Recomputes every transitive dependent of `changed` against candidate values and validates them. */
|
|
210
|
+
function computeDependents(changed, value, validate = true) {
|
|
211
|
+
const pending = new Set();
|
|
212
|
+
const visit = (source) => {
|
|
213
|
+
for (const derived of source.dependents) {
|
|
214
|
+
if (pending.has(derived))
|
|
215
|
+
continue;
|
|
216
|
+
pending.add(derived);
|
|
217
|
+
visit(derived);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
visit(changed);
|
|
221
|
+
const values = new Map([[changed, value]]);
|
|
222
|
+
const result = new Map();
|
|
223
|
+
for (const derived of [...pending].sort((a, b) => a.seq - b.seq)) {
|
|
224
|
+
const next = derived.compute((source) => (values.has(source) ? values.get(source) : source.value));
|
|
225
|
+
if (validate)
|
|
226
|
+
derived.validate(next);
|
|
227
|
+
values.set(derived, next);
|
|
228
|
+
result.set(derived, next);
|
|
229
|
+
}
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
const isReadable = (value) => value !== null &&
|
|
233
|
+
typeof value === "object" &&
|
|
234
|
+
typeof value.subscribe === "function" &&
|
|
235
|
+
"value" in value;
|
|
236
|
+
/**
|
|
237
|
+
* A synchronous derivation. Sources are record cells/derivations (recomputed atomically with the write) or other
|
|
238
|
+
* readables such as handle state members (recomputed when they publish). Subscribers receive diffs.
|
|
239
|
+
*/
|
|
240
|
+
export class Derived extends Source {
|
|
241
|
+
sources;
|
|
242
|
+
#fn;
|
|
243
|
+
#value;
|
|
244
|
+
#external = [];
|
|
245
|
+
constructor(key, sources, fn, options) {
|
|
246
|
+
super(key, options);
|
|
247
|
+
this.sources = sources.map((source) => {
|
|
248
|
+
if (!isReadable(source))
|
|
249
|
+
throw new TypeError(`derive ${key}: sources must be readables`);
|
|
250
|
+
return source;
|
|
251
|
+
});
|
|
252
|
+
this.#fn = fn;
|
|
253
|
+
this.#value = undefined;
|
|
254
|
+
}
|
|
255
|
+
get value() {
|
|
256
|
+
return this.#value;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Computes the first value (or takes it from `precomputed`, a reload's validated dry run) and starts following
|
|
260
|
+
* the sources (at install, never inside a factory).
|
|
261
|
+
*/
|
|
262
|
+
attach(precomputed) {
|
|
263
|
+
this.#value = precomputed?.has(this) ? precomputed.get(this) : this.compute((source) => source.value);
|
|
264
|
+
for (const source of this.sources) {
|
|
265
|
+
if (source instanceof Source)
|
|
266
|
+
source.dependents.add(this);
|
|
267
|
+
else
|
|
268
|
+
this.#external.push(source.subscribe(() => this.refresh()));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
detach() {
|
|
272
|
+
for (const source of this.sources)
|
|
273
|
+
if (source instanceof Source)
|
|
274
|
+
source.dependents.delete(this);
|
|
275
|
+
for (const unsubscribe of this.#external.splice(0))
|
|
276
|
+
unsubscribe();
|
|
277
|
+
}
|
|
278
|
+
compute(read, fn = this.#fn) {
|
|
279
|
+
const result = fn(...this.sources.map(read));
|
|
280
|
+
if (result instanceof Promise)
|
|
281
|
+
throw new TypeError(`derive ${this.key}: fn must be synchronous`);
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
/** Replaces the function without recomputing (reload commit, before migrated cells are installed). */
|
|
285
|
+
setFn(fn) {
|
|
286
|
+
this.#fn = fn;
|
|
287
|
+
}
|
|
288
|
+
/** Recomputes and republishes, including dependents. Errors are reported (the source write already happened). */
|
|
289
|
+
refresh() {
|
|
290
|
+
try {
|
|
291
|
+
const next = this.compute((source) => source.value);
|
|
292
|
+
const dependents = computeDependents(this, next);
|
|
293
|
+
holdPublications(() => {
|
|
294
|
+
this.install(next);
|
|
295
|
+
for (const [derived, value] of dependents)
|
|
296
|
+
derived.install(value);
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
this.options.report(error);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
install(next) {
|
|
304
|
+
const before = this.#value;
|
|
305
|
+
this.#value = next;
|
|
306
|
+
const ops = before === undefined || next === undefined
|
|
307
|
+
? [["r", (next ?? null)]]
|
|
308
|
+
: diffRevisions(before, next);
|
|
309
|
+
if (ops.length > 0)
|
|
310
|
+
this.fanout.emit({ type: "ops", ops, value: next });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
export const isSource = (value) => value instanceof Source;
|
|
314
|
+
/** Thrown by placeholders used before activation finished. */
|
|
315
|
+
export const notReady = (what) => new ChordError("not_ready", `${what} is not ready`);
|
|
316
|
+
//# sourceMappingURL=cell.js.map
|
package/dist/cell.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cell.js","sourceRoot":"","sources":["../src/cell.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TSchema } from \"typebox\";\nimport { type Draft, diffRevisions, type JsonValue, type Op, type Tracker, track } from \"./delta/index.ts\";\nimport { ChordError } from \"./errors.ts\";\nimport { enqueue, holdPublications } from \"./publish.ts\";\nimport { assertValid } from \"./schema.ts\";\nimport type { Ctx, Readable, StateEvent } from \"./types.ts\";\n\nexport interface SourceOptions {\n\treadonly dev: boolean;\n\treadonly report: (error: unknown) => void;\n}\n\n/** Checks that `ctx` may write a record's cells now; throws otherwise. */\nexport type WriteGuard = (ctx: Ctx) => void;\n\nlet sequence = 0;\n\n/** Fans out values and delta events; listener errors are reported, not thrown to the writer. */\nclass Fanout<T> {\n\treadonly #values = new Set<(value: T) => void>();\n\treadonly #events = new Set<(event: StateEvent<T>) => void>();\n\treadonly #report: (error: unknown) => void;\n\n\tconstructor(report: (error: unknown) => void) {\n\t\tthis.#report = report;\n\t}\n\n\tsubscribe(listener: (value: T) => void, current: T): () => void {\n\t\tthis.#values.add(listener);\n\t\tthis.#call(listener, current);\n\t\treturn () => this.#values.delete(listener);\n\t}\n\n\tevents(listener: (event: StateEvent<T>) => void, current: T): () => void {\n\t\tthis.#events.add(listener);\n\t\tthis.#call(listener, { type: \"snap\", value: current });\n\t\treturn () => this.#events.delete(listener);\n\t}\n\n\t/**\n\t * Queues delivery to the listeners subscribed now. A listener that subscribes later already got this revision in\n\t * its snapshot; one that unsubscribes before delivery is skipped.\n\t */\n\temit(event: StateEvent<T>): void {\n\t\tconst events = [...this.#events];\n\t\tconst values = [...this.#values];\n\t\tenqueue(() => {\n\t\t\tfor (const listener of events) if (this.#events.has(listener)) this.#call(listener, event);\n\t\t\tfor (const listener of values) if (this.#values.has(listener)) this.#call(listener, event.value);\n\t\t});\n\t}\n\n\t#call<V>(listener: (value: V) => void, value: V): void {\n\t\ttry {\n\t\t\tlistener(value);\n\t\t} catch (error) {\n\t\t\tthis.#report(error);\n\t\t}\n\t}\n}\n\n/** A record-owned state source (cell or derivation). Writes recompute dependents atomically. */\nexport abstract class Source<T> implements Readable<T> {\n\treadonly key: string;\n\treadonly seq = ++sequence;\n\treadonly dependents = new Set<Derived<unknown>>();\n\t/** Schemas of remote state members currently exposing this source (checked per write in dev mode). */\n\tschemas: readonly TSchema[] = [];\n\tprotected readonly options: SourceOptions;\n\tprotected readonly fanout: Fanout<T>;\n\n\tconstructor(key: string, options: SourceOptions) {\n\t\tthis.key = key;\n\t\tthis.options = options;\n\t\tthis.fanout = new Fanout(options.report);\n\t}\n\n\tabstract get value(): T;\n\n\tsubscribe(listener: (value: T) => void): () => void {\n\t\treturn this.fanout.subscribe(listener, this.value);\n\t}\n\n\tevents(listener: (event: StateEvent<T>) => void): () => void {\n\t\treturn this.fanout.events(listener, this.value);\n\t}\n\n\t/** Per-write validation, dev mode only. */\n\tvalidate(value: unknown): void {\n\t\tif (!this.options.dev) return;\n\t\tfor (const schema of this.ownSchemas()) assertValid(\"state\", `state ${this.key}`, schema, value);\n\t\tfor (const schema of this.schemas) assertValid(\"state\", `state ${this.key}`, schema, value);\n\t}\n\n\tprotected ownSchemas(): readonly TSchema[] {\n\t\treturn [];\n\t}\n}\n\n/** The tracker tracks containers, so a cell tracks `{ v: value }` and strips the box from published ops. */\ninterface Box<T> {\n\tv: T;\n}\n\nfunction unbox(ops: readonly Op[], value: unknown): Op[] {\n\tconst result: Op[] = [];\n\tfor (const op of ops) {\n\t\tif (op[0] === \"r\" || op[1].length === 0) return [[\"r\", value as JsonValue]];\n\t\tconst path = op[1].slice(1);\n\t\tswitch (op[0]) {\n\t\t\tcase \"p\":\n\t\t\t\tresult.push([\"p\", path, op[2], op[3], op[4]]);\n\t\t\t\tbreak;\n\t\t\tcase \"m\":\n\t\t\t\tresult.push([\"m\", path, op[2]]);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\t// s/d/a/t on the value itself replace it.\n\t\t\t\tif (path.length === 0) return [[\"r\", value as JsonValue]];\n\t\t\t\tif (op[0] === \"d\") result.push([\"d\", path as [string | number, ...(string | number)[]]]);\n\t\t\t\telse result.push([op[0], path as [string | number, ...(string | number)[]], op[2]] as Op);\n\t\t}\n\t}\n\treturn result;\n}\n\n/** A state cell holding any JSON value, validated against its schema at creation and (dev mode) on writes. */\nexport class Cell<T> extends Source<T> {\n\t/** Replaced at a reload commit that changes the schema (state-version bump). */\n\tschema: TSchema;\n\treadonly #tracker: Tracker<Box<T>>;\n\treadonly #guard: WriteGuard;\n\n\t/** `initial` is always validated against `schema`; ownership moves to the cell. */\n\tconstructor(key: string, schema: TSchema, initial: T, guard: WriteGuard, options: SourceOptions) {\n\t\tsuper(key, options);\n\t\tassertValid(\"state\", `initial value of state ${key}`, schema, initial);\n\t\tthis.schema = schema;\n\t\tthis.#tracker = track<Box<T>>({ v: initial });\n\t\tthis.#guard = guard;\n\t}\n\n\tprotected override ownSchemas(): readonly TSchema[] {\n\t\treturn [this.schema];\n\t}\n\n\tget value(): T {\n\t\treturn this.#tracker.value.v;\n\t}\n\n\t/**\n\t * Mutates a draft (only mutated paths are published), or returns a new value (`n => n + 1`). A returned value is\n\t * always validated against the schema. The draft is unusable after `fn` returns.\n\t */\n\tchange(ctx: Ctx, fn: (draft: Draft<T>) => T): void;\n\tchange(ctx: Ctx, fn: (draft: Draft<T>) => void): void;\n\tchange(ctx: Ctx, fn: (draft: Draft<T>) => unknown): void {\n\t\tthis.#guard(ctx);\n\t\tconst change = this.#tracker.beginChange();\n\t\tlet prepared: ReturnType<typeof change.prepare>;\n\t\ttry {\n\t\t\tconst draft = change.state as Draft<Box<T>>;\n\t\t\tconst result: unknown = fn(draft.v as Draft<T>);\n\t\t\tif (result instanceof Promise) throw new TypeError(\"cell.change callbacks must be synchronous\");\n\t\t\tif (result !== undefined) {\n\t\t\t\tif (result !== draft.v) assertValid(\"state\", `state ${this.key}`, this.schema, result);\n\t\t\t\tdraft.v = result as Draft<T>;\n\t\t\t}\n\t\t\tprepared = change.prepare();\n\t\t} catch (error) {\n\t\t\tchange.abort();\n\t\t\tthrow error;\n\t\t}\n\t\tthis.#publish(prepared, \"ops\");\n\t}\n\n\t/**\n\t * Replaces the whole value. Ownership of `value` moves to the cell: never mutate it afterwards.\n\t *\n\t * Subscribers get a structural diff (`diffRevisions`) when `diff` is true, or by default when the new value shares\n\t * containers with the old one (a new immutable revision of the same state, e.g. mirrored from another store);\n\t * otherwise a single `r` op. The resulting value is the same either way; only the ops differ.\n\t */\n\treplace(ctx: Ctx, value: T, options: { readonly diff?: boolean } = {}): void {\n\t\tthis.#guard(ctx);\n\t\tconst before = this.value;\n\t\tconst diff = options.diff ?? sharesStructure(before, value);\n\t\tconst ops = diff ? diffRevisions(before as JsonValue, value as JsonValue) : undefined;\n\t\tthis.#publish(this.#tracker.prepareReplace({ v: value }), \"ops\", true, ops);\n\t}\n\n\t/**\n\t * Installs a migrated value and its schema (reload commit). The caller validated the value and the derivations\n\t * over it against every schema that will apply, so nothing is checked here. Subscribers receive a reset.\n\t */\n\treset(value: T, schema: TSchema): void {\n\t\tthis.schema = schema;\n\t\tthis.#publish(this.#tracker.prepareReplace({ v: value }), \"reset\", false);\n\t}\n\n\t/** Adopts this revision and every derived one, then queues all notifications (cell first, then derivations). */\n\t#publish(\n\t\tprepared: ReturnType<Tracker<Box<T>>[\"prepareReplace\"]>,\n\t\ttype: \"ops\" | \"reset\",\n\t\tvalidate = true,\n\t\tops?: readonly Op[],\n\t): void {\n\t\tif (prepared.ops.length === 0 && type === \"ops\") {\n\t\t\tthis.#tracker.adopt(prepared);\n\t\t\treturn;\n\t\t}\n\t\tconst value = prepared.value.v;\n\t\tlet candidates: Map<Derived<unknown>, unknown>;\n\t\ttry {\n\t\t\tif (validate) this.validate(value);\n\t\t\tcandidates = computeDependents(this as Source<unknown>, value, validate);\n\t\t} catch (error) {\n\t\t\tprepared.abort();\n\t\t\tthrow error;\n\t\t}\n\t\tholdPublications(() => {\n\t\t\tthis.#tracker.adopt(prepared);\n\t\t\tconst published = ops !== undefined && ops.length > 0 ? ops : unbox(prepared.ops, value);\n\t\t\tthis.fanout.emit(type === \"reset\" ? { type, value } : { type, ops: published, value });\n\t\t\tfor (const [derived, next] of candidates) derived.install(next);\n\t\t});\n\t}\n}\n\n/** True if `b` reuses at least one container of `a` at the same key: a new revision of the same state. */\nfunction sharesStructure(a: unknown, b: unknown): boolean {\n\tif (a === null || b === null || typeof a !== \"object\" || typeof b !== \"object\" || a === b) return false;\n\tif (Array.isArray(a) !== Array.isArray(b)) return false;\n\tfor (const [key, child] of Object.entries(b)) {\n\t\tif (child !== null && typeof child === \"object\" && (a as Record<string, unknown>)[key] === child) return true;\n\t}\n\treturn false;\n}\n\n/** Recomputes every transitive dependent of `changed` against candidate values and validates them. */\nfunction computeDependents(changed: Source<unknown>, value: unknown, validate = true): Map<Derived<unknown>, unknown> {\n\tconst pending = new Set<Derived<unknown>>();\n\tconst visit = (source: Source<unknown>): void => {\n\t\tfor (const derived of source.dependents) {\n\t\t\tif (pending.has(derived)) continue;\n\t\t\tpending.add(derived);\n\t\t\tvisit(derived);\n\t\t}\n\t};\n\tvisit(changed);\n\tconst values = new Map<Readable<unknown>, unknown>([[changed, value]]);\n\tconst result = new Map<Derived<unknown>, unknown>();\n\tfor (const derived of [...pending].sort((a, b) => a.seq - b.seq)) {\n\t\tconst next = derived.compute((source) => (values.has(source) ? values.get(source) : source.value));\n\t\tif (validate) derived.validate(next);\n\t\tvalues.set(derived, next);\n\t\tresult.set(derived, next);\n\t}\n\treturn result;\n}\n\nexport type Sources = readonly Readable<unknown>[];\nexport type SourceValues<S extends Sources> = { [K in keyof S]: S[K] extends Readable<infer T> ? T : never };\n\nconst isReadable = (value: unknown): value is Readable<unknown> =>\n\tvalue !== null &&\n\ttypeof value === \"object\" &&\n\ttypeof (value as Readable<unknown>).subscribe === \"function\" &&\n\t\"value\" in value;\n\n/**\n * A synchronous derivation. Sources are record cells/derivations (recomputed atomically with the write) or other\n * readables such as handle state members (recomputed when they publish). Subscribers receive diffs.\n */\nexport class Derived<T> extends Source<T> {\n\treadonly sources: readonly Readable<unknown>[];\n\t#fn: (...values: never[]) => unknown;\n\t#value: T;\n\t#external: (() => void)[] = [];\n\n\tconstructor(key: string, sources: Sources, fn: (...values: never[]) => unknown, options: SourceOptions) {\n\t\tsuper(key, options);\n\t\tthis.sources = sources.map((source) => {\n\t\t\tif (!isReadable(source)) throw new TypeError(`derive ${key}: sources must be readables`);\n\t\t\treturn source;\n\t\t});\n\t\tthis.#fn = fn;\n\t\tthis.#value = undefined as T;\n\t}\n\n\tget value(): T {\n\t\treturn this.#value;\n\t}\n\n\t/**\n\t * Computes the first value (or takes it from `precomputed`, a reload's validated dry run) and starts following\n\t * the sources (at install, never inside a factory).\n\t */\n\tattach(precomputed?: ReadonlyMap<unknown, unknown>): void {\n\t\tthis.#value = precomputed?.has(this) ? (precomputed.get(this) as T) : this.compute((source) => source.value);\n\t\tfor (const source of this.sources) {\n\t\t\tif (source instanceof Source) source.dependents.add(this as Derived<unknown>);\n\t\t\telse this.#external.push(source.subscribe(() => this.refresh()));\n\t\t}\n\t}\n\n\tdetach(): void {\n\t\tfor (const source of this.sources)\n\t\t\tif (source instanceof Source) source.dependents.delete(this as Derived<unknown>);\n\t\tfor (const unsubscribe of this.#external.splice(0)) unsubscribe();\n\t}\n\n\tcompute(read: (source: Readable<unknown>) => unknown, fn: (...values: never[]) => unknown = this.#fn): T {\n\t\tconst result: unknown = fn(...(this.sources.map(read) as never[]));\n\t\tif (result instanceof Promise) throw new TypeError(`derive ${this.key}: fn must be synchronous`);\n\t\treturn result as T;\n\t}\n\n\t/** Replaces the function without recomputing (reload commit, before migrated cells are installed). */\n\tsetFn(fn: (...values: never[]) => unknown): void {\n\t\tthis.#fn = fn;\n\t}\n\n\t/** Recomputes and republishes, including dependents. Errors are reported (the source write already happened). */\n\trefresh(): void {\n\t\ttry {\n\t\t\tconst next = this.compute((source) => source.value);\n\t\t\tconst dependents = computeDependents(this as Derived<unknown>, next);\n\t\t\tholdPublications(() => {\n\t\t\t\tthis.install(next);\n\t\t\t\tfor (const [derived, value] of dependents) derived.install(value);\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.options.report(error);\n\t\t}\n\t}\n\n\tinstall(next: T): void {\n\t\tconst before = this.#value;\n\t\tthis.#value = next;\n\t\tconst ops: Op[] =\n\t\t\tbefore === undefined || next === undefined\n\t\t\t\t? [[\"r\", (next ?? null) as JsonValue]]\n\t\t\t\t: diffRevisions(before as JsonValue, next as JsonValue);\n\t\tif (ops.length > 0) this.fanout.emit({ type: \"ops\", ops, value: next });\n\t}\n}\n\nexport const isSource = (value: unknown): value is Cell<unknown> | Derived<unknown> => value instanceof Source;\n\n/** Thrown by placeholders used before activation finished. */\nexport const notReady = (what: string): ChordError => new ChordError(\"not_ready\", `${what} is not ready`);\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Connection } from "./connection.ts";
|
|
2
|
+
import type { HostImpl, PluginRef } from "./host.ts";
|
|
3
|
+
import { type ReloadOptions, type ReloadReport } from "./reload.ts";
|
|
4
|
+
import { type Frame } from "./wire.ts";
|
|
5
|
+
export declare function runClusterReload(host: HostImpl, plugins: readonly PluginRef[], options: ReloadOptions): Promise<ReloadReport>;
|
|
6
|
+
/**
|
|
7
|
+
* Brings a child that connected behind this host's epoch up to it: a reload of the child's modules (the ones
|
|
8
|
+
* cluster reloads gave its name) to this host's current epoch, prepared, voted and committed like a cluster reload.
|
|
9
|
+
* Until then the child is fenced: nothing is routed to it. The child's old code is never relabeled as a newer epoch.
|
|
10
|
+
*/
|
|
11
|
+
export declare function reconcileChild(host: HostImpl, connection: Connection): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* The child side: prepare and vote, then wait for the server's commit or abort. An abort or a lost parent link can
|
|
14
|
+
* arrive at any point: while loading modules, while preparing (pending opens close themselves when they complete),
|
|
15
|
+
* or after the vote. The operation is unwound once its pending work settles, and the reload slot is freed.
|
|
16
|
+
*/
|
|
17
|
+
export declare class ChildReloads {
|
|
18
|
+
#private;
|
|
19
|
+
constructor(host: HostImpl);
|
|
20
|
+
handle(connection: Connection, frame: Extract<Frame, {
|
|
21
|
+
t: "prepare" | "commit" | "abort";
|
|
22
|
+
}>): void;
|
|
23
|
+
/** The parent link died: nobody will commit, so roll back (now, or once pending work settles). */
|
|
24
|
+
parentLost(): void;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=cluster.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cluster.d.ts","sourceRoot":"","sources":["../src/cluster.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAuC,KAAK,aAAa,EAAE,KAAK,YAAY,EAAe,MAAM,aAAa,CAAC;AACtH,OAAO,EAAE,KAAK,KAAK,EAAe,MAAM,WAAW,CAAC;AAkCpD,wBAAsB,gBAAgB,CACrC,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,SAAS,SAAS,EAAE,EAC7B,OAAO,EAAE,aAAa,GACpB,OAAO,CAAC,YAAY,CAAC,CAkEvB;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA0B1F;AAUD;;;;GAIG;AACH,qBAAa,YAAY;;IAIxB,YAAY,IAAI,EAAE,QAAQ,EAEzB;IAED,MAAM,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE;QAAE,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAA;KAAE,CAAC,GAAG,IAAI,CAyBjG;IAED,kGAAkG;IAClG,UAAU,IAAI,IAAI,CAEjB;CAwCD"}
|
package/dist/cluster.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { ChordError } from "./errors.js";
|
|
2
|
+
import { beginReload, endReload, reloadError } from "./reload.js";
|
|
3
|
+
import { toWireError } from "./wire.js";
|
|
4
|
+
// Cross-host reload, coordinated by the server (the host without a parent):
|
|
5
|
+
// server: prepare itself and send `prepare` to every synced child host; collect votes
|
|
6
|
+
// all ok: the server installs its new epoch, sends `commit` to every child, and only then starts what may make
|
|
7
|
+
// new-epoch calls (tasks, listener and registry callbacks). Links are FIFO, so a child commits before it
|
|
8
|
+
// can receive any frame of a root started in the new epoch.
|
|
9
|
+
// any failure or missed vote deadline: `abort` everywhere; each host rolls back to where it was.
|
|
10
|
+
// Clients (browsers) are not participants: they see shape/reset pushes, or `upgrade_required`.
|
|
11
|
+
// A child that connects behind the server's epoch is fenced and reconciled with the same protocol.
|
|
12
|
+
let reloadSeq = 0;
|
|
13
|
+
const modulesFor = (remote, hostName) => {
|
|
14
|
+
let best;
|
|
15
|
+
for (const entry of Object.entries(remote ?? {})) {
|
|
16
|
+
if (hostName?.startsWith(entry[0]) && (best === undefined || entry[0].length > best[0].length))
|
|
17
|
+
best = entry;
|
|
18
|
+
}
|
|
19
|
+
return [...(best?.[1] ?? [])];
|
|
20
|
+
};
|
|
21
|
+
/** A vote, or its rejection after `deadline` milliseconds. */
|
|
22
|
+
function awaitVote(connection, id, deadline) {
|
|
23
|
+
let timer;
|
|
24
|
+
const vote = new Promise((resolve, reject) => {
|
|
25
|
+
connection.votes.set(id, { resolve, reject });
|
|
26
|
+
timer = setTimeout(() => {
|
|
27
|
+
connection.votes.delete(id);
|
|
28
|
+
reject(new ChordError("aborted", `votes missing after ${deadline} ms`, { reason: "vote_deadline" }));
|
|
29
|
+
}, deadline);
|
|
30
|
+
});
|
|
31
|
+
return vote.finally(() => clearTimeout(timer));
|
|
32
|
+
}
|
|
33
|
+
export async function runClusterReload(host, plugins, options) {
|
|
34
|
+
// Fenced children (behind, being reconciled) are not participants; they reconcile to the new epoch afterwards.
|
|
35
|
+
const participants = [...host.connections].filter((connection) => connection.role === "downstream" && connection.peer.kind === "host" && connection.open && connection.synced);
|
|
36
|
+
const epoch = host.peekNextEpoch();
|
|
37
|
+
const reload = await beginReload(host, plugins, options, epoch);
|
|
38
|
+
const id = ++reloadSeq;
|
|
39
|
+
const deadline = options.voteDeadline ?? (options.gateDeadline ?? 5000) + (options.retireDeadline ?? 5000) + 5000;
|
|
40
|
+
const votes = participants.map((connection) => {
|
|
41
|
+
const name = connection.name ?? "child";
|
|
42
|
+
const vote = awaitVote(connection, id, deadline);
|
|
43
|
+
connection.send({
|
|
44
|
+
t: "prepare",
|
|
45
|
+
reload: id,
|
|
46
|
+
epoch,
|
|
47
|
+
plugins: modulesFor(options.remote, name),
|
|
48
|
+
options: {
|
|
49
|
+
remove: modulesFor(options.remoteRemove, name),
|
|
50
|
+
gateDeadline: options.gateDeadline,
|
|
51
|
+
retireDeadline: options.retireDeadline,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
return vote.then((report) => [name, report], (error) => {
|
|
55
|
+
const cause = reloadError(error);
|
|
56
|
+
throw new ChordError(cause.code, `${name}: ${cause.message}`, { cause, reason: cause.reason });
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
let local = false;
|
|
60
|
+
try {
|
|
61
|
+
const results = await Promise.allSettled([
|
|
62
|
+
reload.prepare().then(() => {
|
|
63
|
+
local = true;
|
|
64
|
+
}),
|
|
65
|
+
...votes,
|
|
66
|
+
]);
|
|
67
|
+
const failed = results.find((result) => result.status === "rejected");
|
|
68
|
+
if (failed !== undefined) {
|
|
69
|
+
for (const connection of participants) {
|
|
70
|
+
connection.votes.delete(id);
|
|
71
|
+
connection.send({ t: "abort", reload: id });
|
|
72
|
+
}
|
|
73
|
+
if (local)
|
|
74
|
+
await reload.rollback();
|
|
75
|
+
throw failed.reason;
|
|
76
|
+
}
|
|
77
|
+
const report = reload.commit(() => {
|
|
78
|
+
for (const connection of participants) {
|
|
79
|
+
connection.send({ t: "commit", reload: id });
|
|
80
|
+
connection.peerEpoch = epoch;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
host.rememberRemoteModules(options.remote, options.remoteRemove);
|
|
84
|
+
const children = {};
|
|
85
|
+
for (const result of results.slice(1)) {
|
|
86
|
+
if (result.status !== "fulfilled")
|
|
87
|
+
continue;
|
|
88
|
+
const [name, child] = result.value;
|
|
89
|
+
if (child !== undefined)
|
|
90
|
+
children[name] = child;
|
|
91
|
+
}
|
|
92
|
+
return { ...report, children };
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
endReload(host);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Brings a child that connected behind this host's epoch up to it: a reload of the child's modules (the ones
|
|
100
|
+
* cluster reloads gave its name) to this host's current epoch, prepared, voted and committed like a cluster reload.
|
|
101
|
+
* Until then the child is fenced: nothing is routed to it. The child's old code is never relabeled as a newer epoch.
|
|
102
|
+
*/
|
|
103
|
+
export async function reconcileChild(host, connection) {
|
|
104
|
+
for (;;) {
|
|
105
|
+
const epoch = host.current.id;
|
|
106
|
+
if (!connection.open)
|
|
107
|
+
return;
|
|
108
|
+
if (connection.peerEpoch === epoch) {
|
|
109
|
+
connection.markSynced(epoch);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const id = ++reloadSeq;
|
|
113
|
+
const vote = awaitVote(connection, id, 30_000);
|
|
114
|
+
connection.send({
|
|
115
|
+
t: "prepare",
|
|
116
|
+
reload: id,
|
|
117
|
+
epoch,
|
|
118
|
+
plugins: modulesFor(host.remoteModules, connection.name),
|
|
119
|
+
options: { remove: modulesFor(host.remoteRemoved, connection.name) },
|
|
120
|
+
});
|
|
121
|
+
await vote;
|
|
122
|
+
if (host.current.id !== epoch) {
|
|
123
|
+
// This host moved on meanwhile: abort and try again with the newer epoch.
|
|
124
|
+
connection.send({ t: "abort", reload: id });
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
connection.send({ t: "commit", reload: id });
|
|
128
|
+
connection.peerEpoch = epoch;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The child side: prepare and vote, then wait for the server's commit or abort. An abort or a lost parent link can
|
|
133
|
+
* arrive at any point: while loading modules, while preparing (pending opens close themselves when they complete),
|
|
134
|
+
* or after the vote. The operation is unwound once its pending work settles, and the reload slot is freed.
|
|
135
|
+
*/
|
|
136
|
+
export class ChildReloads {
|
|
137
|
+
#host;
|
|
138
|
+
#operation;
|
|
139
|
+
constructor(host) {
|
|
140
|
+
this.#host = host;
|
|
141
|
+
}
|
|
142
|
+
handle(connection, frame) {
|
|
143
|
+
if (frame.t === "prepare") {
|
|
144
|
+
if (this.#operation !== undefined) {
|
|
145
|
+
const error = new ChordError("unavailable", "a reload is already running", { reason: "reload_running" });
|
|
146
|
+
connection.send({ t: "vote", reload: frame.reload, error: toWireError(error) });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const operation = { id: frame.reload, reload: undefined, state: "loading", cancelled: false };
|
|
150
|
+
this.#operation = operation;
|
|
151
|
+
this.#run(operation, connection, frame).catch((error) => this.#host.report(error));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const operation = this.#operation;
|
|
155
|
+
if (operation === undefined || operation.id !== frame.reload)
|
|
156
|
+
return;
|
|
157
|
+
if (frame.t === "abort") {
|
|
158
|
+
this.#cancel(operation);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (operation.state !== "prepared")
|
|
162
|
+
return;
|
|
163
|
+
this.#operation = undefined;
|
|
164
|
+
try {
|
|
165
|
+
operation.reload?.commit();
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
endReload(this.#host);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/** The parent link died: nobody will commit, so roll back (now, or once pending work settles). */
|
|
172
|
+
parentLost() {
|
|
173
|
+
if (this.#operation !== undefined)
|
|
174
|
+
this.#cancel(this.#operation);
|
|
175
|
+
}
|
|
176
|
+
#cancel(operation) {
|
|
177
|
+
operation.cancelled = true;
|
|
178
|
+
if (operation.state !== "prepared") {
|
|
179
|
+
// Loading or preparing: make prepare fail now; #run unwinds when it settles.
|
|
180
|
+
operation.reload?.rollback().catch((error) => this.#host.report(error));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
this.#operation = undefined;
|
|
184
|
+
operation.reload
|
|
185
|
+
?.rollback()
|
|
186
|
+
.catch((error) => this.#host.report(error))
|
|
187
|
+
.finally(() => endReload(this.#host));
|
|
188
|
+
}
|
|
189
|
+
async #run(operation, connection, frame) {
|
|
190
|
+
const cancelled = () => new ChordError("aborted", "reload aborted by the server", { reason: "aborted" });
|
|
191
|
+
try {
|
|
192
|
+
operation.reload = await beginReload(this.#host, frame.plugins, frame.options, frame.epoch);
|
|
193
|
+
if (operation.cancelled)
|
|
194
|
+
throw cancelled();
|
|
195
|
+
operation.state = "preparing";
|
|
196
|
+
await operation.reload.prepare();
|
|
197
|
+
if (operation.cancelled)
|
|
198
|
+
throw cancelled();
|
|
199
|
+
operation.state = "prepared";
|
|
200
|
+
connection.send({ t: "vote", reload: frame.reload, report: operation.reload.summary() });
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
if (operation.reload !== undefined) {
|
|
204
|
+
await operation.reload.rollback().catch((cleanup) => this.#host.report(cleanup));
|
|
205
|
+
endReload(this.#host);
|
|
206
|
+
}
|
|
207
|
+
if (this.#operation === operation)
|
|
208
|
+
this.#operation = undefined;
|
|
209
|
+
if (!operation.cancelled)
|
|
210
|
+
connection.send({ t: "vote", reload: frame.reload, error: toWireError(error) });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=cluster.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cluster.js","sourceRoot":"","sources":["../src/cluster.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Connection } from \"./connection.ts\";\nimport { ChordError } from \"./errors.ts\";\nimport type { HostImpl, PluginRef } from \"./host.ts\";\nimport { beginReload, endReload, type Reload, type ReloadOptions, type ReloadReport, reloadError } from \"./reload.ts\";\nimport { type Frame, toWireError } from \"./wire.ts\";\n\n// Cross-host reload, coordinated by the server (the host without a parent):\n// server: prepare itself and send `prepare` to every synced child host; collect votes\n// all ok: the server installs its new epoch, sends `commit` to every child, and only then starts what may make\n// new-epoch calls (tasks, listener and registry callbacks). Links are FIFO, so a child commits before it\n// can receive any frame of a root started in the new epoch.\n// any failure or missed vote deadline: `abort` everywhere; each host rolls back to where it was.\n// Clients (browsers) are not participants: they see shape/reset pushes, or `upgrade_required`.\n// A child that connects behind the server's epoch is fenced and reconciled with the same protocol.\n\nlet reloadSeq = 0;\n\nconst modulesFor = (remote: ReloadOptions[\"remote\"], hostName: string | undefined): string[] => {\n\tlet best: [string, readonly string[]] | undefined;\n\tfor (const entry of Object.entries(remote ?? {})) {\n\t\tif (hostName?.startsWith(entry[0]) && (best === undefined || entry[0].length > best[0].length)) best = entry;\n\t}\n\treturn [...(best?.[1] ?? [])];\n};\n\n/** A vote, or its rejection after `deadline` milliseconds. */\nfunction awaitVote(connection: Connection, id: number, deadline: number): Promise<unknown> {\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tconst vote = new Promise<unknown>((resolve, reject) => {\n\t\tconnection.votes.set(id, { resolve, reject });\n\t\ttimer = setTimeout(() => {\n\t\t\tconnection.votes.delete(id);\n\t\t\treject(new ChordError(\"aborted\", `votes missing after ${deadline} ms`, { reason: \"vote_deadline\" }));\n\t\t}, deadline);\n\t});\n\treturn vote.finally(() => clearTimeout(timer));\n}\n\nexport async function runClusterReload(\n\thost: HostImpl,\n\tplugins: readonly PluginRef[],\n\toptions: ReloadOptions,\n): Promise<ReloadReport> {\n\t// Fenced children (behind, being reconciled) are not participants; they reconcile to the new epoch afterwards.\n\tconst participants = [...host.connections].filter(\n\t\t(connection) =>\n\t\t\tconnection.role === \"downstream\" && connection.peer.kind === \"host\" && connection.open && connection.synced,\n\t);\n\tconst epoch = host.peekNextEpoch();\n\tconst reload = await beginReload(host, plugins, options, epoch);\n\tconst id = ++reloadSeq;\n\tconst deadline = options.voteDeadline ?? (options.gateDeadline ?? 5000) + (options.retireDeadline ?? 5000) + 5000;\n\tconst votes = participants.map((connection) => {\n\t\tconst name = connection.name ?? \"child\";\n\t\tconst vote = awaitVote(connection, id, deadline);\n\t\tconnection.send({\n\t\t\tt: \"prepare\",\n\t\t\treload: id,\n\t\t\tepoch,\n\t\t\tplugins: modulesFor(options.remote, name),\n\t\t\toptions: {\n\t\t\t\tremove: modulesFor(options.remoteRemove, name),\n\t\t\t\tgateDeadline: options.gateDeadline,\n\t\t\t\tretireDeadline: options.retireDeadline,\n\t\t\t},\n\t\t});\n\t\treturn vote.then(\n\t\t\t(report) => [name, report as ReloadReport | undefined] as const,\n\t\t\t(error: unknown) => {\n\t\t\t\tconst cause = reloadError(error);\n\t\t\t\tthrow new ChordError(cause.code, `${name}: ${cause.message}`, { cause, reason: cause.reason });\n\t\t\t},\n\t\t);\n\t});\n\tlet local = false;\n\ttry {\n\t\tconst results = await Promise.allSettled([\n\t\t\treload.prepare().then(() => {\n\t\t\t\tlocal = true;\n\t\t\t}),\n\t\t\t...votes,\n\t\t]);\n\t\tconst failed = results.find((result) => result.status === \"rejected\");\n\t\tif (failed !== undefined) {\n\t\t\tfor (const connection of participants) {\n\t\t\t\tconnection.votes.delete(id);\n\t\t\t\tconnection.send({ t: \"abort\", reload: id });\n\t\t\t}\n\t\t\tif (local) await reload.rollback();\n\t\t\tthrow failed.reason;\n\t\t}\n\t\tconst report = reload.commit(() => {\n\t\t\tfor (const connection of participants) {\n\t\t\t\tconnection.send({ t: \"commit\", reload: id });\n\t\t\t\tconnection.peerEpoch = epoch;\n\t\t\t}\n\t\t});\n\t\thost.rememberRemoteModules(options.remote, options.remoteRemove);\n\t\tconst children: Record<string, ReloadReport> = {};\n\t\tfor (const result of results.slice(1)) {\n\t\t\tif (result.status !== \"fulfilled\") continue;\n\t\t\tconst [name, child] = result.value as readonly [string, ReloadReport | undefined];\n\t\t\tif (child !== undefined) children[name] = child;\n\t\t}\n\t\treturn { ...report, children };\n\t} finally {\n\t\tendReload(host);\n\t}\n}\n\n/**\n * Brings a child that connected behind this host's epoch up to it: a reload of the child's modules (the ones\n * cluster reloads gave its name) to this host's current epoch, prepared, voted and committed like a cluster reload.\n * Until then the child is fenced: nothing is routed to it. The child's old code is never relabeled as a newer epoch.\n */\nexport async function reconcileChild(host: HostImpl, connection: Connection): Promise<void> {\n\tfor (;;) {\n\t\tconst epoch = host.current.id;\n\t\tif (!connection.open) return;\n\t\tif (connection.peerEpoch === epoch) {\n\t\t\tconnection.markSynced(epoch);\n\t\t\treturn;\n\t\t}\n\t\tconst id = ++reloadSeq;\n\t\tconst vote = awaitVote(connection, id, 30_000);\n\t\tconnection.send({\n\t\t\tt: \"prepare\",\n\t\t\treload: id,\n\t\t\tepoch,\n\t\t\tplugins: modulesFor(host.remoteModules, connection.name),\n\t\t\toptions: { remove: modulesFor(host.remoteRemoved, connection.name) },\n\t\t});\n\t\tawait vote;\n\t\tif (host.current.id !== epoch) {\n\t\t\t// This host moved on meanwhile: abort and try again with the newer epoch.\n\t\t\tconnection.send({ t: \"abort\", reload: id });\n\t\t\tcontinue;\n\t\t}\n\t\tconnection.send({ t: \"commit\", reload: id });\n\t\tconnection.peerEpoch = epoch;\n\t}\n}\n\n/** One prepare the child received, registered before its first await so an abort or disconnect is never lost. */\ninterface ChildOperation {\n\treadonly id: number;\n\treload: Reload | undefined;\n\tstate: \"loading\" | \"preparing\" | \"prepared\";\n\tcancelled: boolean;\n}\n\n/**\n * The child side: prepare and vote, then wait for the server's commit or abort. An abort or a lost parent link can\n * arrive at any point: while loading modules, while preparing (pending opens close themselves when they complete),\n * or after the vote. The operation is unwound once its pending work settles, and the reload slot is freed.\n */\nexport class ChildReloads {\n\treadonly #host: HostImpl;\n\t#operation: ChildOperation | undefined;\n\n\tconstructor(host: HostImpl) {\n\t\tthis.#host = host;\n\t}\n\n\thandle(connection: Connection, frame: Extract<Frame, { t: \"prepare\" | \"commit\" | \"abort\" }>): void {\n\t\tif (frame.t === \"prepare\") {\n\t\t\tif (this.#operation !== undefined) {\n\t\t\t\tconst error = new ChordError(\"unavailable\", \"a reload is already running\", { reason: \"reload_running\" });\n\t\t\t\tconnection.send({ t: \"vote\", reload: frame.reload, error: toWireError(error) });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst operation: ChildOperation = { id: frame.reload, reload: undefined, state: \"loading\", cancelled: false };\n\t\t\tthis.#operation = operation;\n\t\t\tthis.#run(operation, connection, frame).catch((error: unknown) => this.#host.report(error));\n\t\t\treturn;\n\t\t}\n\t\tconst operation = this.#operation;\n\t\tif (operation === undefined || operation.id !== frame.reload) return;\n\t\tif (frame.t === \"abort\") {\n\t\t\tthis.#cancel(operation);\n\t\t\treturn;\n\t\t}\n\t\tif (operation.state !== \"prepared\") return;\n\t\tthis.#operation = undefined;\n\t\ttry {\n\t\t\toperation.reload?.commit();\n\t\t} finally {\n\t\t\tendReload(this.#host);\n\t\t}\n\t}\n\n\t/** The parent link died: nobody will commit, so roll back (now, or once pending work settles). */\n\tparentLost(): void {\n\t\tif (this.#operation !== undefined) this.#cancel(this.#operation);\n\t}\n\n\t#cancel(operation: ChildOperation): void {\n\t\toperation.cancelled = true;\n\t\tif (operation.state !== \"prepared\") {\n\t\t\t// Loading or preparing: make prepare fail now; #run unwinds when it settles.\n\t\t\toperation.reload?.rollback().catch((error: unknown) => this.#host.report(error));\n\t\t\treturn;\n\t\t}\n\t\tthis.#operation = undefined;\n\t\toperation.reload\n\t\t\t?.rollback()\n\t\t\t.catch((error: unknown) => this.#host.report(error))\n\t\t\t.finally(() => endReload(this.#host));\n\t}\n\n\tasync #run(\n\t\toperation: ChildOperation,\n\t\tconnection: Connection,\n\t\tframe: Extract<Frame, { t: \"prepare\" }>,\n\t): Promise<void> {\n\t\tconst cancelled = (): ChordError =>\n\t\t\tnew ChordError(\"aborted\", \"reload aborted by the server\", { reason: \"aborted\" });\n\t\ttry {\n\t\t\toperation.reload = await beginReload(this.#host, frame.plugins, frame.options, frame.epoch);\n\t\t\tif (operation.cancelled) throw cancelled();\n\t\t\toperation.state = \"preparing\";\n\t\t\tawait operation.reload.prepare();\n\t\t\tif (operation.cancelled) throw cancelled();\n\t\t\toperation.state = \"prepared\";\n\t\t\tconnection.send({ t: \"vote\", reload: frame.reload, report: operation.reload.summary() });\n\t\t} catch (error) {\n\t\t\tif (operation.reload !== undefined) {\n\t\t\t\tawait operation.reload.rollback().catch((cleanup: unknown) => this.#host.report(cleanup));\n\t\t\t\tendReload(this.#host);\n\t\t\t}\n\t\t\tif (this.#operation === operation) this.#operation = undefined;\n\t\t\tif (!operation.cancelled) connection.send({ t: \"vote\", reload: frame.reload, error: toWireError(error) });\n\t\t}\n\t}\n}\n"]}
|