@earendil-works/chord 1.1.0 → 1.3.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 +78 -0
- package/README.md +79 -195
- package/dist/cell.d.ts +122 -0
- package/dist/cell.d.ts.map +1 -0
- package/dist/cell.js +356 -0
- package/dist/cell.js.map +1 -0
- package/dist/cluster.d.ts +27 -0
- package/dist/cluster.d.ts.map +1 -0
- package/dist/cluster.js +230 -0
- package/dist/cluster.js.map +1 -0
- package/dist/connection.d.ts +270 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +1216 -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 +208 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +246 -0
- package/dist/contract.js.map +1 -0
- package/dist/declare.d.ts +33 -0
- package/dist/declare.d.ts.map +1 -0
- package/dist/declare.js +2 -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 +4 -3
- package/dist/delta/index.d.ts.map +1 -1
- package/dist/delta/index.js +45 -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 +28 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +59 -0
- package/dist/errors.js.map +1 -0
- package/dist/host.d.ts +348 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +1000 -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 +107 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +355 -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 +73 -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 +345 -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/object.d.ts +438 -0
- package/dist/object.d.ts.map +1 -0
- package/dist/object.js +1571 -0
- package/dist/object.js.map +1 -0
- package/dist/output.d.ts +85 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +350 -0
- package/dist/output.js.map +1 -0
- 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/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 +126 -0
- package/dist/reload.d.ts.map +1 -0
- package/dist/reload.js +653 -0
- package/dist/reload.js.map +1 -0
- package/dist/remote.d.ts +165 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +612 -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 +69 -258
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/websocket.d.ts +15 -0
- package/dist/websocket.d.ts.map +1 -0
- package/dist/websocket.js +16 -0
- package/dist/websocket.js.map +1 -0
- package/dist/wire.d.ts +216 -0
- package/dist/wire.d.ts.map +1 -0
- package/dist/wire.js +165 -0
- package/dist/wire.js.map +1 -0
- package/docs/guide.md +814 -0
- package/docs/spec.md +288 -0
- package/docs/tutorial.md +931 -0
- package/package.json +14 -10
- 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
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Replicated state: an immutable JSON value with ordered, serialized change notifications. Either authoritative and
|
|
3
|
+
* mutable (`replicatedState(initial)`) or a publication-only view of an external revision source
|
|
4
|
+
* (`replicatedState(source)`).
|
|
5
|
+
*/
|
|
6
|
+
import { BACKGROUND_CONTEXT } from "./context/index.js";
|
|
7
|
+
import { track } from "./delta/index.js";
|
|
5
8
|
/** One public subscription, independent of producer and other subscriber progress. */
|
|
6
9
|
class StateSubscriber {
|
|
7
10
|
#listener;
|
|
@@ -72,7 +75,6 @@ class StateSubscriber {
|
|
|
72
75
|
class ReplicatedStatePublisher {
|
|
73
76
|
#listeners = new Map();
|
|
74
77
|
#reportError;
|
|
75
|
-
#sourceListeners = new Set();
|
|
76
78
|
#publications = [];
|
|
77
79
|
#value;
|
|
78
80
|
#sequence = 0;
|
|
@@ -91,36 +93,24 @@ class ReplicatedStatePublisher {
|
|
|
91
93
|
const { value, sequence } = this.snapshot();
|
|
92
94
|
const subscriber = new StateSubscriber(listener, this.#reportError);
|
|
93
95
|
this.#listeners.set(subscriber, sequence);
|
|
94
|
-
|
|
96
|
+
// Hydration has no caller; it is delivered with the background context.
|
|
97
|
+
subscriber.push({ value, context: BACKGROUND_CONTEXT, delivery: { kind: "hydrate", sequence } });
|
|
95
98
|
subscriber.drain();
|
|
96
99
|
return () => {
|
|
97
100
|
subscriber.close();
|
|
98
101
|
this.#listeners.delete(subscriber);
|
|
99
102
|
};
|
|
100
103
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return () => this.#sourceListeners.delete(listener);
|
|
104
|
-
}
|
|
105
|
-
/** Publish an already-prepared immutable revision and return isolated listener failures. */
|
|
106
|
-
publish(value, ops, context) {
|
|
104
|
+
/** Publish an already-prepared immutable revision. Subscriber failures are reported, never thrown. */
|
|
105
|
+
publish(value, context) {
|
|
107
106
|
this.#value = value;
|
|
108
107
|
this.#sequence += 1;
|
|
109
|
-
this.#publications.push({ value,
|
|
108
|
+
this.#publications.push({ value, sequence: this.#sequence, context });
|
|
110
109
|
if (this.#delivering)
|
|
111
|
-
return
|
|
110
|
+
return;
|
|
112
111
|
this.#delivering = true;
|
|
113
|
-
const errors = [];
|
|
114
112
|
try {
|
|
115
113
|
for (let publication = this.#publications.shift(); publication !== undefined; publication = this.#publications.shift()) {
|
|
116
|
-
for (const listener of [...this.#sourceListeners]) {
|
|
117
|
-
try {
|
|
118
|
-
listener(publication.ops, publication.sequence, publication.context);
|
|
119
|
-
}
|
|
120
|
-
catch (error) {
|
|
121
|
-
errors.push(error);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
114
|
const delivery = { kind: "update", sequence: publication.sequence };
|
|
125
115
|
for (const [subscriber, hydratedSequence] of [...this.#listeners]) {
|
|
126
116
|
if (publication.sequence <= hydratedSequence)
|
|
@@ -133,20 +123,15 @@ class ReplicatedStatePublisher {
|
|
|
133
123
|
finally {
|
|
134
124
|
this.#delivering = false;
|
|
135
125
|
}
|
|
136
|
-
return errors;
|
|
137
126
|
}
|
|
138
127
|
}
|
|
139
|
-
|
|
128
|
+
class MutableReplicatedStateImpl {
|
|
140
129
|
#tracker;
|
|
141
130
|
#publisher;
|
|
142
131
|
#changing = false;
|
|
143
132
|
constructor(initial) {
|
|
144
133
|
this.#tracker = track(initial);
|
|
145
134
|
this.#publisher = new ReplicatedStatePublisher(this.#tracker.value);
|
|
146
|
-
registerReplicatedStateInternals(this, {
|
|
147
|
-
snapshot: () => this.#publisher.snapshot(),
|
|
148
|
-
subscribe: (listener) => this.#publisher.subscribeSource(listener),
|
|
149
|
-
});
|
|
150
135
|
}
|
|
151
136
|
get value() {
|
|
152
137
|
return this.#tracker.value;
|
|
@@ -175,18 +160,16 @@ export class MutableReplicatedStateImpl {
|
|
|
175
160
|
this.#changing = false;
|
|
176
161
|
}
|
|
177
162
|
this.#tracker.adopt(prepared);
|
|
178
|
-
if (prepared.ops.length
|
|
179
|
-
|
|
180
|
-
throwCollectedErrors(this.#publisher.publish(prepared.value, prepared.ops, context), "Replicated state listeners failed");
|
|
163
|
+
if (prepared.ops.length > 0)
|
|
164
|
+
this.#publisher.publish(prepared.value, context);
|
|
181
165
|
}
|
|
182
166
|
replace(context, value) {
|
|
183
167
|
if (this.#changing)
|
|
184
168
|
throw new Error("Replicated state cannot be replaced from a change callback");
|
|
185
169
|
const prepared = this.#tracker.prepareReplace(value);
|
|
186
170
|
this.#tracker.adopt(prepared);
|
|
187
|
-
if (prepared.ops.length
|
|
188
|
-
|
|
189
|
-
throwCollectedErrors(this.#publisher.publish(prepared.value, prepared.ops, context), "Replicated state listeners failed");
|
|
171
|
+
if (prepared.ops.length > 0)
|
|
172
|
+
this.#publisher.publish(prepared.value, context);
|
|
190
173
|
}
|
|
191
174
|
subscribe(listener) {
|
|
192
175
|
return this.#publisher.subscribe(listener);
|
|
@@ -205,10 +188,6 @@ class AttachedReplicatedStateImpl {
|
|
|
205
188
|
this.#cursor = snapshot.cursor;
|
|
206
189
|
this.#reportError = options.onError ?? reportErrorAsync;
|
|
207
190
|
this.#publisher = new ReplicatedStatePublisher(snapshot.value, (error) => this.#report(error));
|
|
208
|
-
registerReplicatedStateInternals(this, {
|
|
209
|
-
snapshot: () => this.#publisher.snapshot(),
|
|
210
|
-
subscribe: (listener) => this.#publisher.subscribeSource(listener),
|
|
211
|
-
});
|
|
212
191
|
}
|
|
213
192
|
get value() {
|
|
214
193
|
return this.#publisher.value;
|
|
@@ -235,11 +214,7 @@ class AttachedReplicatedStateImpl {
|
|
|
235
214
|
throw new Error(`Replicated state source cursor has a gap: expected ${expected}, received ${frame.cursor}`);
|
|
236
215
|
}
|
|
237
216
|
this.#cursor = frame.cursor;
|
|
238
|
-
|
|
239
|
-
if (errors.length === 1)
|
|
240
|
-
this.#report(errors[0]);
|
|
241
|
-
else if (errors.length > 1)
|
|
242
|
-
this.#report(new AggregateError(errors, "Replicated state listeners failed"));
|
|
217
|
+
this.#publisher.publish(frame.value, frame.context);
|
|
243
218
|
}
|
|
244
219
|
catch (error) {
|
|
245
220
|
this.#fail(toError(error));
|
|
@@ -268,7 +243,7 @@ class AttachedReplicatedStateImpl {
|
|
|
268
243
|
}
|
|
269
244
|
}
|
|
270
245
|
/** Attach a publication-only replicated state to one authoritative immutable source stream. */
|
|
271
|
-
|
|
246
|
+
function attachReplicatedStateSource(source, options = {}) {
|
|
272
247
|
const attachment = source.attach();
|
|
273
248
|
try {
|
|
274
249
|
const state = new AttachedReplicatedStateImpl(attachment, options);
|
|
@@ -285,93 +260,6 @@ export function attachReplicatedStateSource(source, options = {}) {
|
|
|
285
260
|
throw error;
|
|
286
261
|
}
|
|
287
262
|
}
|
|
288
|
-
/** A cold read-only state used by service consumers until a complete snapshot arrives. */
|
|
289
|
-
export class ReplicatedStateReplica {
|
|
290
|
-
#listeners = new Set();
|
|
291
|
-
#reportError;
|
|
292
|
-
#validator = new JsonRevisionValidator();
|
|
293
|
-
#value;
|
|
294
|
-
#sequence;
|
|
295
|
-
constructor(reportError) {
|
|
296
|
-
this.#reportError = reportError;
|
|
297
|
-
}
|
|
298
|
-
get value() {
|
|
299
|
-
return this.#value;
|
|
300
|
-
}
|
|
301
|
-
subscribe(listener) {
|
|
302
|
-
const subscriber = new StateSubscriber(listener, this.#reportError);
|
|
303
|
-
this.#listeners.add(subscriber);
|
|
304
|
-
if (this.#value !== undefined) {
|
|
305
|
-
subscriber.push({
|
|
306
|
-
value: this.#value,
|
|
307
|
-
context: serviceDeliveryContext(),
|
|
308
|
-
delivery: { kind: "hydrate", sequence: this.#sequence },
|
|
309
|
-
});
|
|
310
|
-
subscriber.drain();
|
|
311
|
-
}
|
|
312
|
-
return () => {
|
|
313
|
-
subscriber.close();
|
|
314
|
-
this.#listeners.delete(subscriber);
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
hydrate(sequence, ops, context) {
|
|
318
|
-
let next;
|
|
319
|
-
try {
|
|
320
|
-
if (!isBase(ops))
|
|
321
|
-
throw new Error("Replicated state snapshot is not a base operation batch");
|
|
322
|
-
next = this.#validator.validate(applyImmutable(undefined, ops));
|
|
323
|
-
}
|
|
324
|
-
catch (error) {
|
|
325
|
-
this.clear();
|
|
326
|
-
throw error;
|
|
327
|
-
}
|
|
328
|
-
this.#sequence = sequence;
|
|
329
|
-
this.#value = next;
|
|
330
|
-
this.#deliverAll(context, { kind: "hydrate", sequence });
|
|
331
|
-
}
|
|
332
|
-
update(sequence, ops, context) {
|
|
333
|
-
if (this.#sequence === undefined || this.#value === undefined) {
|
|
334
|
-
throw new Error("Replicated state received an update before hydration");
|
|
335
|
-
}
|
|
336
|
-
if (sequence !== this.#sequence + 1) {
|
|
337
|
-
this.clear();
|
|
338
|
-
throw new Error("Replicated state update sequence has a gap");
|
|
339
|
-
}
|
|
340
|
-
let next;
|
|
341
|
-
try {
|
|
342
|
-
next = this.#validator.validate(applyImmutable(this.#value, ops));
|
|
343
|
-
}
|
|
344
|
-
catch (error) {
|
|
345
|
-
this.clear();
|
|
346
|
-
throw error;
|
|
347
|
-
}
|
|
348
|
-
this.#sequence = sequence;
|
|
349
|
-
this.#value = next;
|
|
350
|
-
this.#deliverAll(context, { kind: "update", sequence });
|
|
351
|
-
}
|
|
352
|
-
clear() {
|
|
353
|
-
this.#value = undefined;
|
|
354
|
-
this.#sequence = undefined;
|
|
355
|
-
for (const subscriber of this.#listeners)
|
|
356
|
-
subscriber.clear();
|
|
357
|
-
}
|
|
358
|
-
#deliverAll(context, delivery) {
|
|
359
|
-
if (this.#value === undefined)
|
|
360
|
-
return;
|
|
361
|
-
const frame = { value: this.#value, context, delivery };
|
|
362
|
-
const subscribers = [...this.#listeners];
|
|
363
|
-
// Enqueue for everyone before user code can publish another revision reentrantly.
|
|
364
|
-
for (const subscriber of subscribers)
|
|
365
|
-
subscriber.push(frame);
|
|
366
|
-
for (const subscriber of subscribers)
|
|
367
|
-
subscriber.drain();
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
/** @internal Context for synthetic service deliveries without a caller. */
|
|
371
|
-
export function serviceDeliveryContext() {
|
|
372
|
-
// TODO: Add delivery-scoped cancellation or metadata if deliveries gain an owned lifecycle.
|
|
373
|
-
return BACKGROUND_CONTEXT;
|
|
374
|
-
}
|
|
375
263
|
function assertCursor(cursor, kind) {
|
|
376
264
|
if (!Number.isSafeInteger(cursor))
|
|
377
265
|
throw new TypeError(`Replicated state source ${kind} cursor must be a safe integer`);
|
|
@@ -380,12 +268,6 @@ function isPromiseLike(value) {
|
|
|
380
268
|
return (((typeof value === "object" && value !== null) || typeof value === "function") &&
|
|
381
269
|
typeof value.then === "function");
|
|
382
270
|
}
|
|
383
|
-
function throwCollectedErrors(errors, message) {
|
|
384
|
-
if (errors.length === 1)
|
|
385
|
-
throw errors[0];
|
|
386
|
-
if (errors.length > 1)
|
|
387
|
-
throw new AggregateError(errors, message);
|
|
388
|
-
}
|
|
389
271
|
function reportErrorAsync(error) {
|
|
390
272
|
queueMicrotask(() => {
|
|
391
273
|
throw error;
|
|
@@ -394,4 +276,12 @@ function reportErrorAsync(error) {
|
|
|
394
276
|
function toError(error) {
|
|
395
277
|
return error instanceof Error ? error : new Error(String(error));
|
|
396
278
|
}
|
|
279
|
+
export function replicatedState(initialOrSource, options) {
|
|
280
|
+
if (isReplicatedStateSource(initialOrSource))
|
|
281
|
+
return attachReplicatedStateSource(initialOrSource, options);
|
|
282
|
+
return new MutableReplicatedStateImpl(initialOrSource);
|
|
283
|
+
}
|
|
284
|
+
function isReplicatedStateSource(value) {
|
|
285
|
+
return typeof value.attach === "function";
|
|
286
|
+
}
|
|
397
287
|
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Replicated state: an immutable JSON value with ordered, serialized change notifications. Either authoritative and\n * mutable (`replicatedState(initial)`) or a publication-only view of an external revision source\n * (`replicatedState(source)`).\n */\nimport { BACKGROUND_CONTEXT, type Context } from \"./context/index.ts\";\nimport { type Draft, type Op, type Prepared, type Tracker, track } from \"./delta/index.ts\";\n\nexport interface ReplicatedStateDelivery {\n\treadonly kind: \"hydrate\" | \"update\";\n\treadonly sequence: number;\n}\n\nexport interface ReplicatedState<T> {\n\t/**\n\t * Contract-immutable value, or undefined until hydration. It is not frozen and may share containers with an\n\t * in-process provider; consumers must not mutate it. Later updates do not mutate previously returned values.\n\t */\n\treadonly value: T | undefined;\n\t/**\n\t * Each subscription serializes callbacks, awaiting hydration before updates. Values are immutable and may\n\t * share unchanged revision data. At most 100 deliveries wait behind the running callback; overflow keeps\n\t * only the newest pending value/context/delivery, so update sequences may skip. Failures are reported in\n\t * isolation and delivery continues. Unsubscribe discards pending work without aborting or joining a callback.\n\t */\n\tsubscribe(listener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => Promise<void>): () => void;\n\tsubscribe(listener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => void): () => void;\n}\n\nexport interface MutableReplicatedState<T extends object> extends ReplicatedState<T> {\n\treadonly value: T;\n\t/**\n\t * Atomically publish one synchronous overlay mutation. Draft handles are unusable after the callback returns.\n\t * Values placed through the draft are cloned by value; assigning `undefined` to an object property deletes it.\n\t */\n\tchange(context: Context, mutate: (draft: Draft<T>) => void): void;\n\t/**\n\t * Atomically take immutable ownership of an alias-free strict-JSON replacement.\n\t * The caller must not mutate the transferred root after this call.\n\t */\n\treplace(context: Context, value: T): void;\n}\n\n/** One immutable authoritative revision committed after an attachment snapshot. */\nexport interface ReplicatedStateSourceFrame<T> {\n\t/** Monotonic source cursor. The first frame after a snapshot must be `snapshot.cursor + 1`. */\n\treadonly cursor: number;\n\t/** The exact immutable value produced by this commit. */\n\treadonly value: T;\n\t/** The exact immutable operation batch that produced `value` from the preceding source revision. */\n\treadonly ops: readonly Op[];\n\treadonly context: Context;\n}\n\nexport interface ReplicatedStateSourceAttachment<T> {\n\t/** Fixed immutable snapshot captured at the atomic attachment boundary. */\n\treadonly snapshot: { readonly value: T; readonly cursor: number };\n\t/**\n\t * Install the sole listener and synchronously drain every buffered frame in source commit order.\n\t * This method is single-use. After it begins, every new committed frame must also be delivered in order\n\t * until disposal, including commits made reentrantly while a prior frame is being delivered.\n\t */\n\tactivate(listener: (frame: ReplicatedStateSourceFrame<T>) => void): void;\n\t/** Stop delivery and release source resources. Disposal must be idempotent. */\n\tdispose(): void;\n}\n\n/**\n * An authoritative immutable revision source.\n *\n * `attach()` must synchronously and atomically capture one snapshot and register the returned attachment to buffer\n * every later committed frame. The snapshot must include every commit before that boundary; buffered frames must\n * include every commit after it, with no overlap or gap. Snapshot values, frame values, and operation batches are\n * immutable and remain valid after delivery. Chord only publishes these references; it never applies or re-diffs them.\n */\nexport interface ReplicatedStateSource<T> {\n\tattach(): ReplicatedStateSourceAttachment<T>;\n}\n\nexport interface ReplicatedStateSourceOptions {\n\t/** Receives source-contract and publication-listener failures without throwing them into the source. */\n\treadonly onError?: (error: Error) => void;\n}\n\n/** A synchronously hydrated publication-only state backed by one source attachment. */\nexport interface AttachedReplicatedState<T> extends ReplicatedState<T> {\n\treadonly value: T;\n\t/** Idempotently release the source attachment. The last published value remains readable. */\n\tdispose(): void;\n}\n\n/** The void signature also accepts synchronous callbacks that return an ignored value. */\ntype StateListener<T> = (value: T, context: Context, delivery: ReplicatedStateDelivery) => void;\n\ntype StateDelivery<T> = {\n\treadonly value: T;\n\treadonly context: Context;\n\treadonly delivery: ReplicatedStateDelivery;\n};\n\n/** One public subscription, independent of producer and other subscriber progress. */\nclass StateSubscriber<T> {\n\treadonly #listener: StateListener<T>;\n\treadonly #reportError: (error: Error) => void;\n\treadonly #pending: StateDelivery<T>[] = [];\n\t#running = false;\n\t#started = false;\n\t#closed = false;\n\n\tconstructor(listener: StateListener<T>, reportError: (error: Error) => void) {\n\t\tthis.#listener = listener;\n\t\tthis.#reportError = reportError;\n\t}\n\n\tpush(frame: StateDelivery<T>): void {\n\t\tif (this.#closed) return;\n\t\tif (this.#pending.length === 100) {\n\t\t\t// A cold replica can queue updates reentrantly before this subscriber's first hydration starts.\n\t\t\tconst hydration = this.#started ? undefined : this.#pending[0];\n\t\t\tthis.#pending.length = 0;\n\t\t\tif (hydration !== undefined) this.#pending.push(hydration);\n\t\t}\n\t\tthis.#pending.push(frame);\n\t}\n\n\tdrain(): void {\n\t\tif (this.#running || this.#closed) return;\n\t\tthis.#running = true;\n\t\tfor (let frame = this.#pending.shift(); frame !== undefined; frame = this.#pending.shift()) {\n\t\t\tthis.#started = true;\n\t\t\ttry {\n\t\t\t\tconst result: unknown = this.#listener(frame.value, frame.context, frame.delivery);\n\t\t\t\tif (isPromiseLike(result)) {\n\t\t\t\t\tvoid Promise.resolve(result).then(\n\t\t\t\t\t\t() => this.#resume(),\n\t\t\t\t\t\t(error: unknown) => {\n\t\t\t\t\t\t\tthis.#report(error);\n\t\t\t\t\t\t\tthis.#resume();\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tthis.#report(error);\n\t\t\t}\n\t\t}\n\t\tthis.#running = false;\n\t}\n\n\tclear(): void {\n\t\tthis.#pending.length = 0;\n\t}\n\n\tclose(): void {\n\t\tthis.#closed = true;\n\t\tthis.clear();\n\t}\n\n\t#resume(): void {\n\t\tthis.#running = false;\n\t\tthis.drain();\n\t}\n\n\t#report(error: unknown): void {\n\t\ttry {\n\t\t\tthis.#reportError(toError(error));\n\t\t} catch (reportError) {\n\t\t\treportErrorAsync(toError(reportError));\n\t\t}\n\t}\n}\n\ntype Publication<T> = {\n\treadonly value: T;\n\treadonly sequence: number;\n\treadonly context: Context;\n};\n\n/** Maintains local publication order independently of how revisions are produced. */\nclass ReplicatedStatePublisher<T> {\n\treadonly #listeners = new Map<StateSubscriber<T>, number>();\n\treadonly #reportError: (error: Error) => void;\n\treadonly #publications: Publication<T>[] = [];\n\t#value: T;\n\t#sequence = 0;\n\t#delivering = false;\n\n\tconstructor(initial: T, reportError: (error: Error) => void = reportErrorAsync) {\n\t\tthis.#value = initial;\n\t\tthis.#reportError = reportError;\n\t}\n\n\tget value(): T {\n\t\treturn this.#value;\n\t}\n\n\tsnapshot(): { readonly value: T; readonly sequence: number } {\n\t\treturn { value: this.#value, sequence: this.#sequence };\n\t}\n\n\tsubscribe(listener: StateListener<T>): () => void {\n\t\tconst { value, sequence } = this.snapshot();\n\t\tconst subscriber = new StateSubscriber(listener, this.#reportError);\n\t\tthis.#listeners.set(subscriber, sequence);\n\t\t// Hydration has no caller; it is delivered with the background context.\n\t\tsubscriber.push({ value, context: BACKGROUND_CONTEXT, delivery: { kind: \"hydrate\", sequence } });\n\t\tsubscriber.drain();\n\t\treturn () => {\n\t\t\tsubscriber.close();\n\t\t\tthis.#listeners.delete(subscriber);\n\t\t};\n\t}\n\n\t/** Publish an already-prepared immutable revision. Subscriber failures are reported, never thrown. */\n\tpublish(value: T, context: Context): void {\n\t\tthis.#value = value;\n\t\tthis.#sequence += 1;\n\t\tthis.#publications.push({ value, sequence: this.#sequence, context });\n\t\tif (this.#delivering) return;\n\n\t\tthis.#delivering = true;\n\t\ttry {\n\t\t\tfor (\n\t\t\t\tlet publication = this.#publications.shift();\n\t\t\t\tpublication !== undefined;\n\t\t\t\tpublication = this.#publications.shift()\n\t\t\t) {\n\t\t\t\tconst delivery = { kind: \"update\", sequence: publication.sequence } as const;\n\t\t\t\tfor (const [subscriber, hydratedSequence] of [...this.#listeners]) {\n\t\t\t\t\tif (publication.sequence <= hydratedSequence) continue;\n\t\t\t\t\tsubscriber.push({ value: publication.value, context: publication.context, delivery });\n\t\t\t\t\tsubscriber.drain();\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.#delivering = false;\n\t\t}\n\t}\n}\n\nclass MutableReplicatedStateImpl<T extends object> implements MutableReplicatedState<T> {\n\treadonly #tracker: Tracker<T>;\n\treadonly #publisher: ReplicatedStatePublisher<T>;\n\t#changing = false;\n\n\tconstructor(initial: T) {\n\t\tthis.#tracker = track(initial);\n\t\tthis.#publisher = new ReplicatedStatePublisher(this.#tracker.value);\n\t}\n\n\tget value(): T {\n\t\treturn this.#tracker.value;\n\t}\n\n\tchange(context: Context, mutate: Parameters<MutableReplicatedState<T>[\"change\"]>[1]): void {\n\t\tif (this.#changing) throw new Error(\"Replicated state cannot be changed reentrantly from a change callback\");\n\t\tthis.#changing = true;\n\t\tlet prepared: Prepared<T>;\n\t\ttry {\n\t\t\tconst change = this.#tracker.beginChange();\n\t\t\ttry {\n\t\t\t\tconst outcome = (mutate as (draft: typeof change.state) => unknown)(change.state);\n\t\t\t\tif (isPromiseLike(outcome)) {\n\t\t\t\t\tvoid Promise.resolve(outcome).catch(() => undefined);\n\t\t\t\t\tthrow new TypeError(\"Replicated state change callbacks must be synchronous\");\n\t\t\t\t}\n\t\t\t\tprepared = change.prepare();\n\t\t\t} catch (error) {\n\t\t\t\tchange.abort();\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.#changing = false;\n\t\t}\n\n\t\tthis.#tracker.adopt(prepared);\n\t\tif (prepared.ops.length > 0) this.#publisher.publish(prepared.value, context);\n\t}\n\n\treplace(context: Context, value: T): void {\n\t\tif (this.#changing) throw new Error(\"Replicated state cannot be replaced from a change callback\");\n\t\tconst prepared = this.#tracker.prepareReplace(value);\n\t\tthis.#tracker.adopt(prepared);\n\t\tif (prepared.ops.length > 0) this.#publisher.publish(prepared.value, context);\n\t}\n\n\tsubscribe(listener: StateListener<T>): () => void {\n\t\treturn this.#publisher.subscribe(listener);\n\t}\n}\n\nclass AttachedReplicatedStateImpl<T> implements AttachedReplicatedState<T> {\n\treadonly #publisher: ReplicatedStatePublisher<T>;\n\treadonly #attachment: ReplicatedStateSourceAttachment<T>;\n\treadonly #reportError: (error: Error) => void;\n\t#cursor: number;\n\t#disposed = false;\n\n\tconstructor(attachment: ReplicatedStateSourceAttachment<T>, options: ReplicatedStateSourceOptions) {\n\t\tconst { snapshot } = attachment;\n\t\tassertCursor(snapshot.cursor, \"snapshot\");\n\t\tthis.#attachment = attachment;\n\t\tthis.#cursor = snapshot.cursor;\n\t\tthis.#reportError = options.onError ?? reportErrorAsync;\n\t\tthis.#publisher = new ReplicatedStatePublisher(snapshot.value, (error) => this.#report(error));\n\t}\n\n\tget value(): T {\n\t\treturn this.#publisher.value;\n\t}\n\n\tsubscribe(listener: StateListener<T>): () => void {\n\t\treturn this.#publisher.subscribe(listener);\n\t}\n\n\tactivate(): void {\n\t\tthis.#attachment.activate((frame) => this.#receive(frame));\n\t}\n\n\tdispose(): void {\n\t\tif (this.#disposed) return;\n\t\tthis.#disposed = true;\n\t\tthis.#attachment.dispose();\n\t}\n\n\t#receive(frame: ReplicatedStateSourceFrame<T>): void {\n\t\tif (this.#disposed) return;\n\t\ttry {\n\t\t\tassertCursor(frame.cursor, \"frame\");\n\t\t\tconst expected = this.#cursor + 1;\n\t\t\tif (frame.cursor !== expected) {\n\t\t\t\tthrow new Error(`Replicated state source cursor has a gap: expected ${expected}, received ${frame.cursor}`);\n\t\t\t}\n\t\t\tthis.#cursor = frame.cursor;\n\t\t\tthis.#publisher.publish(frame.value, frame.context);\n\t\t} catch (error) {\n\t\t\tthis.#fail(toError(error));\n\t\t}\n\t}\n\n\t#fail(error: Error): void {\n\t\tif (this.#disposed) return;\n\t\tthis.#disposed = true;\n\t\ttry {\n\t\t\tthis.#attachment.dispose();\n\t\t} catch (disposeError) {\n\t\t\tthis.#report(new AggregateError([error, disposeError], \"Replicated state source contract failed\"));\n\t\t\treturn;\n\t\t}\n\t\tthis.#report(error);\n\t}\n\n\t#report(error: unknown): void {\n\t\ttry {\n\t\t\tthis.#reportError(toError(error));\n\t\t} catch (reportError) {\n\t\t\treportErrorAsync(toError(reportError));\n\t\t}\n\t}\n}\n\n/** Attach a publication-only replicated state to one authoritative immutable source stream. */\nfunction attachReplicatedStateSource<T>(\n\tsource: ReplicatedStateSource<T>,\n\toptions: ReplicatedStateSourceOptions = {},\n): AttachedReplicatedState<T> {\n\tconst attachment = source.attach();\n\ttry {\n\t\tconst state = new AttachedReplicatedStateImpl(attachment, options);\n\t\tstate.activate();\n\t\treturn state;\n\t} catch (error) {\n\t\ttry {\n\t\t\tattachment.dispose();\n\t\t} catch (disposeError) {\n\t\t\tthrow new AggregateError([error, disposeError], \"Failed to attach replicated state source\");\n\t\t}\n\t\tthrow error;\n\t}\n}\n\nfunction assertCursor(cursor: number, kind: \"snapshot\" | \"frame\"): void {\n\tif (!Number.isSafeInteger(cursor))\n\t\tthrow new TypeError(`Replicated state source ${kind} cursor must be a safe integer`);\n}\n\nfunction isPromiseLike(value: unknown): value is PromiseLike<unknown> {\n\treturn (\n\t\t((typeof value === \"object\" && value !== null) || typeof value === \"function\") &&\n\t\ttypeof (value as { then?: unknown }).then === \"function\"\n\t);\n}\n\nfunction reportErrorAsync(error: Error): void {\n\tqueueMicrotask(() => {\n\t\tthrow error;\n\t});\n}\n\nfunction toError(error: unknown): Error {\n\treturn error instanceof Error ? error : new Error(String(error));\n}\n\nexport function replicatedState<T>(\n\tsource: ReplicatedStateSource<T>,\n\toptions?: ReplicatedStateSourceOptions,\n): AttachedReplicatedState<T>;\n/**\n * Create authoritative state by taking immutable ownership of an alias-free strict-JSON root.\n * The caller must not mutate `initial` after this call.\n */\nexport function replicatedState<T extends object>(initial: T): MutableReplicatedState<T>;\nexport function replicatedState(\n\tinitialOrSource: object | ReplicatedStateSource<unknown>,\n\toptions?: ReplicatedStateSourceOptions,\n): MutableReplicatedState<object> | AttachedReplicatedState<unknown> {\n\tif (isReplicatedStateSource(initialOrSource)) return attachReplicatedStateSource(initialOrSource, options);\n\treturn new MutableReplicatedStateImpl(initialOrSource);\n}\n\nfunction isReplicatedStateSource(value: object): value is ReplicatedStateSource<unknown> {\n\treturn typeof (value as { readonly attach?: unknown }).attach === \"function\";\n}\n"]}
|