@earendil-works/chord 0.0.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.
Files changed (98) hide show
  1. package/README.md +205 -0
  2. package/dist/api.d.ts +15 -0
  3. package/dist/api.d.ts.map +1 -0
  4. package/dist/api.js +72 -0
  5. package/dist/api.js.map +1 -0
  6. package/dist/bundler.d.ts +6 -0
  7. package/dist/bundler.d.ts.map +1 -0
  8. package/dist/bundler.js +3 -0
  9. package/dist/bundler.js.map +1 -0
  10. package/dist/context/index.d.ts +24 -0
  11. package/dist/context/index.d.ts.map +1 -0
  12. package/dist/context/index.js +97 -0
  13. package/dist/context/index.js.map +1 -0
  14. package/dist/delta/index.d.ts +119 -0
  15. package/dist/delta/index.d.ts.map +1 -0
  16. package/dist/delta/index.js +1213 -0
  17. package/dist/delta/index.js.map +1 -0
  18. package/dist/facets/host.d.ts +12 -0
  19. package/dist/facets/host.d.ts.map +1 -0
  20. package/dist/facets/host.js +763 -0
  21. package/dist/facets/host.js.map +1 -0
  22. package/dist/facets/loader.d.ts +3 -0
  23. package/dist/facets/loader.d.ts.map +1 -0
  24. package/dist/facets/loader.js +5 -0
  25. package/dist/facets/loader.js.map +1 -0
  26. package/dist/index.d.ts +11 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +10 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/json.d.ts +4 -0
  31. package/dist/json.d.ts.map +1 -0
  32. package/dist/json.js +57 -0
  33. package/dist/json.js.map +1 -0
  34. package/dist/node/bundle-loader.d.ts +30 -0
  35. package/dist/node/bundle-loader.d.ts.map +1 -0
  36. package/dist/node/bundle-loader.js +377 -0
  37. package/dist/node/bundle-loader.js.map +1 -0
  38. package/dist/node/bundle.d.ts +26 -0
  39. package/dist/node/bundle.d.ts.map +1 -0
  40. package/dist/node/bundle.js +183 -0
  41. package/dist/node/bundle.js.map +1 -0
  42. package/dist/node/manifest.d.ts +36 -0
  43. package/dist/node/manifest.d.ts.map +1 -0
  44. package/dist/node/manifest.js +6 -0
  45. package/dist/node/manifest.js.map +1 -0
  46. package/dist/node/package.d.ts +15 -0
  47. package/dist/node/package.d.ts.map +1 -0
  48. package/dist/node/package.js +199 -0
  49. package/dist/node/package.js.map +1 -0
  50. package/dist/node.d.ts +5 -0
  51. package/dist/node.d.ts.map +1 -0
  52. package/dist/node.js +3 -0
  53. package/dist/node.js.map +1 -0
  54. package/dist/services/consumer.d.ts +11 -0
  55. package/dist/services/consumer.d.ts.map +1 -0
  56. package/dist/services/consumer.js +537 -0
  57. package/dist/services/consumer.js.map +1 -0
  58. package/dist/services/errors.d.ts +8 -0
  59. package/dist/services/errors.d.ts.map +1 -0
  60. package/dist/services/errors.js +22 -0
  61. package/dist/services/errors.js.map +1 -0
  62. package/dist/services/handle.d.ts +15 -0
  63. package/dist/services/handle.d.ts.map +1 -0
  64. package/dist/services/handle.js +99 -0
  65. package/dist/services/handle.js.map +1 -0
  66. package/dist/services/instances.d.ts +25 -0
  67. package/dist/services/instances.d.ts.map +1 -0
  68. package/dist/services/instances.js +137 -0
  69. package/dist/services/instances.js.map +1 -0
  70. package/dist/services/loopback.d.ts +5 -0
  71. package/dist/services/loopback.d.ts.map +1 -0
  72. package/dist/services/loopback.js +15 -0
  73. package/dist/services/loopback.js.map +1 -0
  74. package/dist/services/provider.d.ts +37 -0
  75. package/dist/services/provider.d.ts.map +1 -0
  76. package/dist/services/provider.js +472 -0
  77. package/dist/services/provider.js.map +1 -0
  78. package/dist/services/state-codec.d.ts +15 -0
  79. package/dist/services/state-codec.d.ts.map +1 -0
  80. package/dist/services/state-codec.js +115 -0
  81. package/dist/services/state-codec.js.map +1 -0
  82. package/dist/services/state-internals.d.ts +11 -0
  83. package/dist/services/state-internals.d.ts.map +1 -0
  84. package/dist/services/state-internals.js +10 -0
  85. package/dist/services/state-internals.js.map +1 -0
  86. package/dist/services/state.d.ts +23 -0
  87. package/dist/services/state.d.ts.map +1 -0
  88. package/dist/services/state.js +124 -0
  89. package/dist/services/state.js.map +1 -0
  90. package/dist/services/wire.d.ts +60 -0
  91. package/dist/services/wire.d.ts.map +1 -0
  92. package/dist/services/wire.js +173 -0
  93. package/dist/services/wire.js.map +1 -0
  94. package/dist/types.d.ts +203 -0
  95. package/dist/types.d.ts.map +1 -0
  96. package/dist/types.js +2 -0
  97. package/dist/types.js.map +1 -0
  98. package/package.json +72 -0
@@ -0,0 +1,763 @@
1
+ import { BACKGROUND_CONTEXT } from "../context/index.js";
2
+ import { RemoteServiceBindingImpl } from "../services/consumer.js";
3
+ import { ServiceSlot } from "../services/handle.js";
4
+ import { InstanceDirectory } from "../services/instances.js";
5
+ import { createLoopbackServiceTransport } from "../services/loopback.js";
6
+ import { RemoteServiceProvider, validateRemoteServiceImplementation } from "../services/provider.js";
7
+ import { MutableReplicatedStateImpl } from "../services/state.js";
8
+ class FacetLifecycle {
9
+ id;
10
+ #effects = [];
11
+ #observations = [];
12
+ #activate = [];
13
+ #state = "setting_up";
14
+ #serviceAccess = false;
15
+ constructor(id) {
16
+ this.id = id;
17
+ }
18
+ assertSettingUp(operation) {
19
+ if (this.#state !== "setting_up") {
20
+ throw new Error(`Facet ${this.id} can ${operation} only during setup`);
21
+ }
22
+ }
23
+ assertRunning(operation) {
24
+ if (this.#state !== "setting_up" && this.#state !== "active") {
25
+ throw new Error(`Facet ${this.id} cannot ${operation} while ${this.#state}`);
26
+ }
27
+ }
28
+ assertActive(operation) {
29
+ if (this.#state !== "active")
30
+ throw new Error(`Facet ${this.id} can ${operation} only while active`);
31
+ }
32
+ assertServiceAccess() {
33
+ if (!this.#serviceAccess) {
34
+ throw new Error(`Facet ${this.id} service handles cannot be used while ${this.#state}`);
35
+ }
36
+ }
37
+ revoke() {
38
+ this.#serviceAccess = false;
39
+ }
40
+ own(disposal) {
41
+ this.assertRunning("own resources");
42
+ this.#effects.push(disposal);
43
+ }
44
+ observe(start) {
45
+ this.assertSettingUp("observe services");
46
+ this.#observations.push(start);
47
+ }
48
+ onActivate(callback) {
49
+ this.assertSettingUp("register activation callbacks");
50
+ this.#activate.push(callback);
51
+ }
52
+ prepared() {
53
+ this.assertSettingUp("finish setup");
54
+ this.#state = "prepared";
55
+ }
56
+ async activate() {
57
+ if (this.#state !== "prepared")
58
+ throw new Error(`Facet ${this.id} is not prepared`);
59
+ this.#state = "active";
60
+ this.#serviceAccess = true;
61
+ for (const start of this.#observations)
62
+ this.#effects.push(start());
63
+ for (const callback of this.#activate)
64
+ await callback();
65
+ }
66
+ async dispose() {
67
+ if (this.#state === "dead")
68
+ return;
69
+ this.#state = "disposing";
70
+ const errors = [];
71
+ for (const effect of this.#effects.splice(0).reverse()) {
72
+ try {
73
+ await effect();
74
+ }
75
+ catch (error) {
76
+ errors.push(error);
77
+ }
78
+ }
79
+ this.#observations.length = 0;
80
+ this.#activate.length = 0;
81
+ this.#serviceAccess = false;
82
+ this.#state = "dead";
83
+ if (errors.length === 1)
84
+ throw errors[0];
85
+ if (errors.length > 1)
86
+ throw new AggregateError(errors, `Failed to dispose facet ${this.id}`);
87
+ }
88
+ }
89
+ class LocalKeyedServiceRegistry {
90
+ #registrations = new Map();
91
+ #disposed = false;
92
+ constructor(services, onError) {
93
+ const ids = services.map(({ id }) => id);
94
+ if (new Set(ids).size !== ids.length)
95
+ throw new TypeError("Local keyed service registry has duplicate IDs");
96
+ for (const serviceId of ids) {
97
+ this.#registrations.set(serviceId, {
98
+ generations: new Map(),
99
+ directory: new InstanceDirectory({ ready: true, onError }),
100
+ });
101
+ }
102
+ }
103
+ spawn(service, key, implementation) {
104
+ this.#assertActive();
105
+ if (key.length === 0)
106
+ throw new TypeError("Local service instance key must not be empty");
107
+ if (typeof implementation !== "object" || implementation === null || Array.isArray(implementation)) {
108
+ throw new TypeError(`Local service ${service.id} implementation must be an object`);
109
+ }
110
+ const registration = this.#registration(service.id);
111
+ if (registration.directory.get(key) !== undefined) {
112
+ throw new Error(`Local service ${service.id} already has a live instance with key ${key}`);
113
+ }
114
+ const generation = (registration.generations.get(key) ?? 0) + 1;
115
+ registration.generations.set(key, generation);
116
+ const instance = {
117
+ key,
118
+ generation,
119
+ service: implementation,
120
+ deactivate() { },
121
+ };
122
+ registration.directory.insert(instance);
123
+ let closed = false;
124
+ return () => {
125
+ if (closed)
126
+ return;
127
+ closed = true;
128
+ registration.directory.remove(instance);
129
+ };
130
+ }
131
+ observe(service, handler) {
132
+ this.#assertActive();
133
+ return this.#registration(service.id).directory.observe(handler);
134
+ }
135
+ dispose() {
136
+ if (this.#disposed)
137
+ return;
138
+ this.#disposed = true;
139
+ for (const registration of this.#registrations.values())
140
+ registration.directory.dispose();
141
+ this.#registrations.clear();
142
+ }
143
+ #registration(serviceId) {
144
+ const registration = this.#registrations.get(serviceId);
145
+ if (registration === undefined)
146
+ throw new Error(`Local keyed service ${serviceId} is not registered`);
147
+ return registration;
148
+ }
149
+ #assertActive() {
150
+ if (this.#disposed)
151
+ throw new Error("Local keyed service registry is disposed");
152
+ }
153
+ }
154
+ class HostServiceSlots {
155
+ #singletons = new Map();
156
+ #keyedSources = new Map();
157
+ getSingleton(service, assertAccess) {
158
+ let slot = this.#singletons.get(service.id);
159
+ if (slot === undefined) {
160
+ slot = new ServiceSlot(service.id, !service.local);
161
+ this.#singletons.set(service.id, slot);
162
+ }
163
+ return slot.view(assertAccess);
164
+ }
165
+ hasSingleton(serviceId) {
166
+ return this.#singletons.has(serviceId);
167
+ }
168
+ observe(service, assertAccess, handler) {
169
+ const source = this.#keyedSources.get(service.id);
170
+ if (source === undefined)
171
+ throw new Error(`Service ${service.id} is disconnected`);
172
+ let stopped = false;
173
+ const stop = source.observe(service, (target, context) => {
174
+ const slot = new ServiceSlot(service.id, !service.local);
175
+ slot.bind(target);
176
+ return handler(slot.view(() => {
177
+ assertAccess();
178
+ if (stopped || context.abortSignal?.aborted) {
179
+ throw new Error(`Keyed service ${service.id} observation is closed`);
180
+ }
181
+ }), context);
182
+ });
183
+ return () => {
184
+ if (stopped)
185
+ return;
186
+ stopped = true;
187
+ stop();
188
+ };
189
+ }
190
+ bindSingleton(serviceId, target) {
191
+ this.#singletons.get(serviceId)?.bind(target);
192
+ }
193
+ bindKeyed(serviceId, services) {
194
+ this.#keyedSources.set(serviceId, services);
195
+ }
196
+ dispose() {
197
+ for (const slot of this.#singletons.values())
198
+ slot.unbind();
199
+ this.#singletons.clear();
200
+ this.#keyedSources.clear();
201
+ }
202
+ }
203
+ class StagedServiceSpawner {
204
+ #lifecycle;
205
+ #validate;
206
+ #instances = new Map();
207
+ #installer;
208
+ constructor(lifecycle, validate) {
209
+ this.#lifecycle = lifecycle;
210
+ this.#validate = validate;
211
+ }
212
+ connect(installer) {
213
+ if (this.#installer !== undefined)
214
+ throw new Error("Facet service provider is already connected");
215
+ this.#installer = installer;
216
+ for (const instance of this.#instances.values()) {
217
+ instance.release = installer(instance.key, instance.implementation);
218
+ }
219
+ }
220
+ spawn(key, implementation) {
221
+ this.#lifecycle.assertActive("spawn service instances");
222
+ this.#validate(key, implementation);
223
+ if (this.#instances.has(key))
224
+ throw new Error(`Facet service already has a live instance with key ${key}`);
225
+ const instance = { key, implementation };
226
+ this.#instances.set(key, instance);
227
+ if (this.#installer !== undefined)
228
+ instance.release = this.#installer(key, implementation);
229
+ const close = () => {
230
+ if (this.#instances.get(key) !== instance)
231
+ return;
232
+ this.#instances.delete(key);
233
+ instance.release?.();
234
+ };
235
+ this.#lifecycle.own(close);
236
+ return close;
237
+ }
238
+ }
239
+ /** Private lifecycle and dependency kernel behind the atomic host entry point. */
240
+ export class FacetKernel {
241
+ #initialFacets;
242
+ #serviceSources;
243
+ #onError;
244
+ #facets = new Map();
245
+ #serviceSlots;
246
+ #sourceBindings = new Map();
247
+ #activationOrder = [];
248
+ #provider;
249
+ #internalServices;
250
+ #localKeyedServices;
251
+ #phase = "setup";
252
+ constructor(options) {
253
+ const ids = options.facets.map((facet) => facet.id);
254
+ if (ids.some((id) => id.length === 0))
255
+ throw new Error("Facet ID must not be empty");
256
+ if (new Set(ids).size !== ids.length)
257
+ throw new Error("Facet IDs must be unique within a generation");
258
+ this.#initialFacets = options.facets;
259
+ this.#serviceSources = options.serviceSources ?? [];
260
+ this.#onError = options.onError ?? (() => { });
261
+ this.#serviceSlots = new HostServiceSlots();
262
+ }
263
+ get provider() {
264
+ if (this.#provider === undefined)
265
+ throw new Error("Facet service provider is not assembled");
266
+ return this.#provider;
267
+ }
268
+ #createFacetRuntime(facetId) {
269
+ return {
270
+ facetId,
271
+ requires: [],
272
+ provides: [],
273
+ lifecycle: new FacetLifecycle(facetId),
274
+ provisions: [],
275
+ singletonViews: new Map(),
276
+ };
277
+ }
278
+ #setupFacet(facet, record) {
279
+ const result = facet.setup(this.#environment(record));
280
+ if (isPromiseLike(result)) {
281
+ void Promise.resolve(result).catch(() => { });
282
+ throw new Error(`Facet ${facet.id} setup must be synchronous`);
283
+ }
284
+ record.lifecycle.prepared();
285
+ }
286
+ async activate() {
287
+ const records = [];
288
+ try {
289
+ for (const facet of this.#initialFacets) {
290
+ const record = this.#createFacetRuntime(facet.id);
291
+ this.#facets.set(facet.id, record);
292
+ this.#setupFacet(facet, record);
293
+ records.push(record);
294
+ }
295
+ this.#phase = "assembling";
296
+ const externalServices = await this.#resolveExternalServices(records);
297
+ this.#activationOrder = validateFacets(records, externalServices);
298
+ this.#assembleProviders();
299
+ this.#bindServices(externalServices);
300
+ this.#phase = "connecting";
301
+ await Promise.all([...this.#sourceBindings.values(), this.#internalServiceBinding].map((services) => services.ready(BACKGROUND_CONTEXT)));
302
+ this.#phase = "activating";
303
+ for (const id of this.#activationOrder)
304
+ await this.#facets.get(id).lifecycle.activate();
305
+ this.#phase = "active";
306
+ }
307
+ catch (error) {
308
+ const cleanupErrors = await this.#terminate();
309
+ if (cleanupErrors.length > 0) {
310
+ throw new AggregateError([error, ...cleanupErrors], "Facet generation startup and cleanup failed");
311
+ }
312
+ throw error;
313
+ }
314
+ }
315
+ async reload(facets) {
316
+ if (this.#phase !== "active")
317
+ throw new Error(`Facet host cannot reload while ${this.#phase}`);
318
+ const ids = facets.map(({ id }) => id);
319
+ if (ids.some((id) => id.length === 0))
320
+ throw new Error("Facet ID must not be empty");
321
+ if (new Set(ids).size !== ids.length)
322
+ throw new Error("Reloaded facet IDs must be unique");
323
+ for (const id of ids) {
324
+ if (!this.#facets.has(id))
325
+ throw new Error(`Facet ${id} is not active`);
326
+ }
327
+ this.#phase = "reloading";
328
+ const staged = [];
329
+ const candidates = [];
330
+ try {
331
+ for (const facet of facets) {
332
+ const record = this.#createFacetRuntime(facet.id);
333
+ staged.push(record);
334
+ this.#setupFacet(facet, record);
335
+ const previous = this.#facets.get(facet.id);
336
+ if (!sameFacetShape(previous, record)) {
337
+ throw new Error(`Reloaded facet ${facet.id} must preserve its service requirements and provisions`);
338
+ }
339
+ this.#validateReplacementProvisions(record.provisions);
340
+ candidates.push(record);
341
+ }
342
+ }
343
+ catch (error) {
344
+ const cleanupErrors = await disposeFacetRecords(staged.reverse());
345
+ if (cleanupErrors.length > 0) {
346
+ const abortErrors = await this.#abort();
347
+ throw new AggregateError([error, ...cleanupErrors, ...abortErrors], "Facet reload setup and cleanup failed");
348
+ }
349
+ this.#phase = "active";
350
+ throw error;
351
+ }
352
+ const replacements = new Map(candidates.map((record) => [record.facetId, record]));
353
+ const candidateOrder = this.#activationOrder.flatMap((id) => {
354
+ const candidate = replacements.get(id);
355
+ return candidate === undefined ? [] : [candidate];
356
+ });
357
+ try {
358
+ for (const candidate of candidateOrder)
359
+ await candidate.lifecycle.activate();
360
+ for (const candidate of candidateOrder)
361
+ this.#validateReplacementProvisions(candidate.provisions);
362
+ }
363
+ catch (error) {
364
+ const cleanupErrors = await disposeFacetRecords([...candidateOrder].reverse());
365
+ if (cleanupErrors.length > 0) {
366
+ const abortErrors = await this.#abort();
367
+ throw new AggregateError([error, ...cleanupErrors, ...abortErrors], "Facet reload activation and cleanup failed");
368
+ }
369
+ this.#phase = "active";
370
+ throw error;
371
+ }
372
+ const previous = candidateOrder.map(({ facetId }) => this.#facets.get(facetId));
373
+ for (const candidate of candidateOrder)
374
+ this.#facets.set(candidate.facetId, candidate);
375
+ try {
376
+ for (const candidate of candidateOrder) {
377
+ for (const provision of candidate.provisions) {
378
+ if (provision.kind !== "singleton")
379
+ continue;
380
+ if (provision.service.local) {
381
+ this.#serviceSlots.bindSingleton(provision.service.id, provision.implementation);
382
+ }
383
+ else {
384
+ provision.replace(this.provider);
385
+ }
386
+ }
387
+ }
388
+ const retirementErrors = await disposeFacetRecords(previous.reverse());
389
+ if (retirementErrors.length === 1)
390
+ throw retirementErrors[0];
391
+ if (retirementErrors.length > 1) {
392
+ throw new AggregateError(retirementErrors, "Failed to retire replaced facets");
393
+ }
394
+ for (const candidate of candidateOrder) {
395
+ for (const provision of candidate.provisions) {
396
+ if (provision.kind !== "keyed")
397
+ continue;
398
+ if (provision.service.local)
399
+ provision.connectLocal(this.#localKeyedRegistry);
400
+ else
401
+ provision.connectRemote(this.provider);
402
+ }
403
+ }
404
+ }
405
+ catch (error) {
406
+ const abortErrors = await this.#abort(previous);
407
+ throw new AggregateError([error, ...abortErrors], "Facet reload failed after cutover");
408
+ }
409
+ this.#phase = "active";
410
+ }
411
+ async dispose() {
412
+ if (this.#phase === "dead")
413
+ return;
414
+ if (this.#phase !== "active")
415
+ throw new Error(`Facet host cannot be disposed while ${this.#phase}`);
416
+ const errors = await this.#terminate();
417
+ if (errors.length === 1)
418
+ throw errors[0];
419
+ if (errors.length > 1)
420
+ throw new AggregateError(errors, "Failed to dispose facet generation");
421
+ }
422
+ #validateReplacementProvisions(provisions) {
423
+ for (const provision of provisions) {
424
+ if (provision.kind !== "singleton" || provision.service.local)
425
+ continue;
426
+ provision.validateReplacement(this.provider);
427
+ }
428
+ }
429
+ #environment(runtime) {
430
+ const { lifecycle, provisions } = runtime;
431
+ return {
432
+ provide: (service, implementation) => {
433
+ lifecycle.assertSettingUp("provide services");
434
+ if (typeof implementation !== "object" || implementation === null || Array.isArray(implementation)) {
435
+ throw new TypeError(`Service ${service.id} implementation must be an object`);
436
+ }
437
+ recordServiceReference(runtime.provides, service, "singleton");
438
+ provisions.push({
439
+ kind: "singleton",
440
+ service,
441
+ implementation,
442
+ install: (provider) => provider.provide(service, implementation),
443
+ validateReplacement: (provider) => provider.validateReplacement(service, implementation),
444
+ replace: (provider) => provider.replace(service, implementation),
445
+ });
446
+ },
447
+ provideMany: (service) => {
448
+ lifecycle.assertSettingUp("provide service instances");
449
+ recordServiceReference(runtime.provides, service, "keyed");
450
+ const instances = new StagedServiceSpawner(lifecycle, (key, implementation) => {
451
+ if (key.length === 0)
452
+ throw new TypeError("Facet service instance key must not be empty");
453
+ if (typeof implementation !== "object" || implementation === null || Array.isArray(implementation)) {
454
+ throw new TypeError(`Facet service ${service.id} implementation must be an object`);
455
+ }
456
+ if (!service.local)
457
+ validateRemoteServiceImplementation(service.id, implementation);
458
+ });
459
+ provisions.push({
460
+ kind: "keyed",
461
+ service,
462
+ connectLocal: (registry) => instances.connect((key, implementation) => registry.spawn(service, key, implementation)),
463
+ connectRemote: (provider) => instances.connect((key, implementation) => provider.spawn(service, key, implementation)),
464
+ });
465
+ return instances;
466
+ },
467
+ use: (service) => {
468
+ lifecycle.assertSettingUp("acquire services");
469
+ recordServiceReference(runtime.requires, service, "singleton");
470
+ let view = runtime.singletonViews.get(service.id);
471
+ if (view === undefined) {
472
+ view = this.#serviceSlots.getSingleton(service, () => lifecycle.assertServiceAccess());
473
+ runtime.singletonViews.set(service.id, view);
474
+ }
475
+ return view;
476
+ },
477
+ observe: (service, handler) => {
478
+ lifecycle.assertSettingUp("observe services");
479
+ recordServiceReference(runtime.requires, service, "keyed");
480
+ lifecycle.observe(() => this.#serviceSlots.observe(service, () => lifecycle.assertServiceAccess(), handler));
481
+ },
482
+ replicatedState: (initial) => {
483
+ lifecycle.assertRunning("create replicated state");
484
+ return new MutableReplicatedStateImpl(initial);
485
+ },
486
+ own: (disposal) => lifecycle.own(disposal),
487
+ onActivate: (callback) => lifecycle.onActivate(callback),
488
+ onDeactivate: (callback) => lifecycle.own(callback),
489
+ };
490
+ }
491
+ async #resolveExternalServices(records) {
492
+ const catalogues = await Promise.all(this.#serviceSources.map(async (source) => ({
493
+ source,
494
+ entries: await source.catalogue(BACKGROUND_CONTEXT),
495
+ })));
496
+ const offered = new Map();
497
+ for (const { source, entries } of catalogues) {
498
+ for (const { serviceId, mode } of entries) {
499
+ if (offered.has(serviceId)) {
500
+ throw new Error(`Facet host service ${serviceId} is offered by more than one source`);
501
+ }
502
+ offered.set(serviceId, { mode, source });
503
+ }
504
+ }
505
+ const local = new Set(records.flatMap(({ provides }) => provides.map(({ serviceId }) => serviceId)));
506
+ const external = new Map();
507
+ for (const { requires } of records) {
508
+ for (const requirement of requires) {
509
+ if (local.has(requirement.serviceId) || external.has(requirement.serviceId))
510
+ continue;
511
+ let source = offered.get(requirement.serviceId);
512
+ if (source === undefined) {
513
+ const deferred = this.#serviceSources.filter(({ acceptsUnavailableServices }) => acceptsUnavailableServices);
514
+ if (deferred.length > 1) {
515
+ throw new Error(`Facet host service ${requirement.serviceId} has more than one deferred source`);
516
+ }
517
+ if (deferred.length === 1)
518
+ source = { mode: requirement.mode, source: deferred[0] };
519
+ }
520
+ if (source !== undefined) {
521
+ external.set(requirement.serviceId, { ...source, service: requirement.service });
522
+ }
523
+ }
524
+ }
525
+ const serviceIdsBySource = new Map();
526
+ for (const [serviceId, { source }] of external) {
527
+ let serviceIds = serviceIdsBySource.get(source);
528
+ if (serviceIds === undefined) {
529
+ serviceIds = [];
530
+ serviceIdsBySource.set(source, serviceIds);
531
+ }
532
+ serviceIds.push(serviceId);
533
+ }
534
+ for (const [source, serviceIds] of serviceIdsBySource) {
535
+ this.#sourceBindings.set(source, source.open({
536
+ services: serviceIds.map((id) => ({ id })),
537
+ assertAccess: () => this.#assertServiceTargetAccess(),
538
+ onError: this.#onError,
539
+ }));
540
+ }
541
+ return external;
542
+ }
543
+ #assembleProviders() {
544
+ const provisions = this.#provisions();
545
+ const remoteProvisions = provisions.filter(({ service }) => !service.local);
546
+ const provider = new RemoteServiceProvider(remoteProvisions.map(({ service, kind }) => ({ service, mode: kind })));
547
+ const internalServices = new RemoteServiceBindingImpl({
548
+ services: remoteProvisions.map(({ service }) => service),
549
+ transport: createLoopbackServiceTransport(provider),
550
+ assertAccess: () => this.#assertServiceTargetAccess(),
551
+ onError: this.#onError,
552
+ });
553
+ const localKeyedServices = new LocalKeyedServiceRegistry(provisions.flatMap((provision) => provision.kind === "keyed" && provision.service.local ? [provision.service] : []), this.#onError);
554
+ this.#provider = provider;
555
+ this.#internalServices = internalServices;
556
+ this.#localKeyedServices = localKeyedServices;
557
+ for (const provision of provisions) {
558
+ if (provision.kind === "singleton") {
559
+ if (!provision.service.local)
560
+ provision.install(provider);
561
+ }
562
+ else if (provision.service.local) {
563
+ provision.connectLocal(localKeyedServices);
564
+ }
565
+ else {
566
+ provision.connectRemote(provider);
567
+ }
568
+ }
569
+ }
570
+ #bindServices(externalServices) {
571
+ for (const provision of this.#provisions()) {
572
+ if (provision.kind === "singleton") {
573
+ if (!this.#serviceSlots.hasSingleton(provision.service.id))
574
+ continue;
575
+ const target = provision.service.local
576
+ ? provision.implementation
577
+ : this.#internalServiceBinding.use(provision.service);
578
+ this.#serviceSlots.bindSingleton(provision.service.id, target);
579
+ }
580
+ else {
581
+ this.#serviceSlots.bindKeyed(provision.service.id, provision.service.local ? this.#localKeyedRegistry : this.#internalServiceBinding);
582
+ }
583
+ }
584
+ for (const [serviceId, { service, mode, source }] of externalServices) {
585
+ const services = this.#sourceBindings.get(source);
586
+ if (services === undefined)
587
+ throw new Error(`Service source for ${serviceId} is not open`);
588
+ if (mode === "singleton") {
589
+ this.#serviceSlots.bindSingleton(serviceId, services.use(service));
590
+ }
591
+ else {
592
+ this.#serviceSlots.bindKeyed(serviceId, services);
593
+ }
594
+ }
595
+ }
596
+ #provisions() {
597
+ return [...this.#facets.values()].flatMap(({ provisions }) => provisions);
598
+ }
599
+ get #localKeyedRegistry() {
600
+ if (this.#localKeyedServices === undefined)
601
+ throw new Error("Facet keyed services are not assembled");
602
+ return this.#localKeyedServices;
603
+ }
604
+ get #internalServiceBinding() {
605
+ if (this.#internalServices === undefined)
606
+ throw new Error("Facet remote services are not assembled");
607
+ return this.#internalServices;
608
+ }
609
+ async #disposeServiceBindings() {
610
+ const bindings = [...this.#sourceBindings.values()];
611
+ this.#sourceBindings.clear();
612
+ if (this.#internalServices !== undefined)
613
+ bindings.push(this.#internalServices);
614
+ this.#internalServices = undefined;
615
+ const results = await Promise.allSettled(bindings.map((services) => services.dispose(BACKGROUND_CONTEXT)));
616
+ return results.flatMap((result) => (result.status === "rejected" ? [result.reason] : []));
617
+ }
618
+ #assertServiceTargetAccess() {
619
+ if (this.#phase !== "activating" &&
620
+ this.#phase !== "active" &&
621
+ this.#phase !== "reloading" &&
622
+ this.#phase !== "disposing") {
623
+ throw new Error(`Facet service targets cannot be used during ${this.#phase}`);
624
+ }
625
+ }
626
+ async #abort(extraRecords = []) {
627
+ for (const record of this.#facets.values())
628
+ record.lifecycle.revoke();
629
+ for (const record of extraRecords)
630
+ record.lifecycle.revoke();
631
+ return this.#terminate(extraRecords);
632
+ }
633
+ async #terminate(extraRecords = []) {
634
+ this.#phase = "disposing";
635
+ const errors = await this.#disposeLifecycles();
636
+ errors.push(...(await disposeFacetRecords([...extraRecords].reverse())));
637
+ try {
638
+ this.#localKeyedServices?.dispose();
639
+ }
640
+ catch (error) {
641
+ errors.push(error);
642
+ }
643
+ this.#localKeyedServices = undefined;
644
+ errors.push(...(await this.#disposeServiceBindings()));
645
+ try {
646
+ this.#serviceSlots.dispose();
647
+ }
648
+ catch (error) {
649
+ errors.push(error);
650
+ }
651
+ try {
652
+ this.#provider?.dispose();
653
+ }
654
+ catch (error) {
655
+ errors.push(error);
656
+ }
657
+ this.#phase = "dead";
658
+ return errors;
659
+ }
660
+ async #disposeLifecycles() {
661
+ const errors = [];
662
+ const order = this.#activationOrder.length > 0 ? [...this.#activationOrder].reverse() : [...this.#facets.keys()].reverse();
663
+ for (const id of order) {
664
+ const record = this.#facets.get(id);
665
+ if (record === undefined)
666
+ continue;
667
+ this.#facets.delete(id);
668
+ try {
669
+ await record.lifecycle.dispose();
670
+ }
671
+ catch (error) {
672
+ errors.push(error);
673
+ }
674
+ }
675
+ return errors;
676
+ }
677
+ }
678
+ async function disposeFacetRecords(records) {
679
+ const errors = [];
680
+ for (const record of records) {
681
+ try {
682
+ await record.lifecycle.dispose();
683
+ }
684
+ catch (error) {
685
+ errors.push(error);
686
+ }
687
+ }
688
+ return errors;
689
+ }
690
+ function validateFacets(records, externalServices) {
691
+ const providers = new Map();
692
+ for (const [serviceId, { mode }] of externalServices)
693
+ providers.set(serviceId, { facetId: undefined, mode });
694
+ for (const record of records) {
695
+ for (const provision of record.provides) {
696
+ const existing = providers.get(provision.serviceId);
697
+ if (existing !== undefined) {
698
+ if (existing.mode !== undefined && existing.mode !== provision.mode) {
699
+ throw new Error(`Service ${provision.serviceId} is provided as both singleton and keyed`);
700
+ }
701
+ if (existing.facetId === undefined) {
702
+ throw new Error(`Service ${provision.serviceId} is provided by both the host and ${record.facetId}`);
703
+ }
704
+ throw new Error(`Service ${provision.serviceId} is provided by both ${existing.facetId} and ${record.facetId}`);
705
+ }
706
+ providers.set(provision.serviceId, { facetId: record.facetId, mode: provision.mode });
707
+ }
708
+ }
709
+ const dependencies = new Map(records.map((record) => [record.facetId, new Set()]));
710
+ const dependents = new Map(records.map((record) => [record.facetId, new Set()]));
711
+ for (const record of records) {
712
+ for (const requirement of record.requires) {
713
+ const provider = providers.get(requirement.serviceId);
714
+ if (provider === undefined) {
715
+ throw new Error(`Facet ${record.facetId} requires local/${requirement.serviceId}/${requirement.mode}, but no facet provides it`);
716
+ }
717
+ if (provider.mode !== undefined && provider.mode !== requirement.mode) {
718
+ throw new Error(`Facet ${record.facetId} requires ${requirement.serviceId} as ${requirement.mode}, but ${provider.facetId ?? "the host"} provides it as ${provider.mode}`);
719
+ }
720
+ if (provider.facetId === undefined || provider.facetId === record.facetId)
721
+ continue;
722
+ dependencies.get(record.facetId).add(provider.facetId);
723
+ dependents.get(provider.facetId).add(record.facetId);
724
+ }
725
+ }
726
+ return topologicalOrder(records, dependencies, dependents);
727
+ }
728
+ function topologicalOrder(records, dependencies, dependents) {
729
+ const remaining = new Map([...dependencies].map(([id, values]) => [id, values.size]));
730
+ const ready = records.map((record) => record.facetId).filter((id) => remaining.get(id) === 0);
731
+ const order = [];
732
+ while (ready.length > 0) {
733
+ const id = ready.shift();
734
+ order.push(id);
735
+ for (const dependent of dependents.get(id) ?? []) {
736
+ const count = remaining.get(dependent) - 1;
737
+ remaining.set(dependent, count);
738
+ if (count === 0)
739
+ ready.push(dependent);
740
+ }
741
+ }
742
+ if (order.length !== records.length) {
743
+ const cycle = records.map((record) => record.facetId).filter((id) => remaining.get(id) > 0);
744
+ throw new Error(`Facet dependency cycle: ${cycle.join(", ")}`);
745
+ }
746
+ return order;
747
+ }
748
+ function recordServiceReference(target, service, mode) {
749
+ if (target.some((reference) => reference.serviceId === service.id && reference.mode === mode))
750
+ return;
751
+ target.push({ serviceId: service.id, service: service, mode });
752
+ }
753
+ function sameFacetShape(left, right) {
754
+ return sameReferences(left.requires, right.requires) && sameReferences(left.provides, right.provides);
755
+ }
756
+ function sameReferences(left, right) {
757
+ return (left.length === right.length &&
758
+ left.every((reference) => right.some((other) => other.serviceId === reference.serviceId && other.mode === reference.mode)));
759
+ }
760
+ function isPromiseLike(value) {
761
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
762
+ }
763
+ //# sourceMappingURL=host.js.map