@forgeax/engine-host 0.1.27

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.
@@ -0,0 +1,715 @@
1
+ import { Context, inspectCatalogPlugins } from '@forgeax/engine-plugin';
2
+ import { installCatalogLoader, projectPluginEntries } from '@forgeax/engine-plugin/loader';
3
+
4
+ // src/frontend.ts
5
+
6
+ // src/protocol.ts
7
+ var HOST_ASSEMBLY_SCHEMA_VERSION = 1;
8
+ var HostAssemblyError = class extends Error {
9
+ code;
10
+ expected;
11
+ hint;
12
+ detail;
13
+ constructor(code, expected, hint, detail) {
14
+ super(`${code}: ${expected}`);
15
+ this.name = "HostAssemblyError";
16
+ this.code = code;
17
+ this.expected = expected;
18
+ this.hint = hint;
19
+ this.detail = detail;
20
+ }
21
+ };
22
+ function assertHostModuleCatalogIdentity(module, record) {
23
+ if (record.version !== void 0 && record.version !== module.version) {
24
+ throw new HostAssemblyError(
25
+ "host-assembly-module-version-mismatch",
26
+ `module ${module.name} to load catalog version ${module.version}`,
27
+ "Regenerate the static Catalog from the same backend package revision.",
28
+ { name: module.name, actual: record.version, expected: module.version }
29
+ );
30
+ }
31
+ if (record.digest !== void 0 && module.digest !== void 0 && record.digest !== module.digest) {
32
+ throw new HostAssemblyError(
33
+ "host-assembly-module-version-mismatch",
34
+ `module ${module.name} to load catalog digest ${module.digest ?? "none"}`,
35
+ "Regenerate the static Catalog from the same backend package bytes.",
36
+ { name: module.name, actual: record.digest, expected: module.digest ?? "none" }
37
+ );
38
+ }
39
+ const versionMatches = record.version !== void 0 && record.version === module.version;
40
+ const digestMatches = module.digest !== void 0 && record.digest !== void 0 && record.digest === module.digest;
41
+ const staticWithoutIdentity = module.version === "static" && module.digest === void 0 && record.version === void 0 && record.digest === void 0;
42
+ if (!versionMatches && !digestMatches && !staticWithoutIdentity) {
43
+ throw new HostAssemblyError(
44
+ "host-assembly-module-version-mismatch",
45
+ `module ${module.name} to have a matching catalog code identity for ${module.version}`,
46
+ "Add the generated module version or digest to the static Catalog.",
47
+ {
48
+ name: module.name,
49
+ actual: record.version ?? record.digest ?? "unknown",
50
+ expected: module.version
51
+ }
52
+ );
53
+ }
54
+ }
55
+ function canonicalHostJson(value) {
56
+ if (value === void 0) return "undefined";
57
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
58
+ if (Array.isArray(value)) return `[${value.map(canonicalHostJson).join(",")}]`;
59
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${canonicalHostJson(item)}`).join(",")}}`;
60
+ }
61
+ function hostRevision(value) {
62
+ let hash = 2166136261;
63
+ for (const char of canonicalHostJson(value)) {
64
+ hash ^= char.codePointAt(0) ?? 0;
65
+ hash = Math.imul(hash, 16777619);
66
+ }
67
+ return `fnv1a:${(hash >>> 0).toString(16).padStart(8, "0")}`;
68
+ }
69
+ function cloneEntry(entry) {
70
+ const config = entry.group ? entry.config?.map(cloneEntry) : entry.config;
71
+ return {
72
+ id: entry.id,
73
+ name: entry.name,
74
+ ...config === void 0 ? {} : { config },
75
+ ...entry.group === void 0 ? {} : { group: entry.group },
76
+ ...entry.disabled === void 0 ? {} : { disabled: entry.disabled },
77
+ ...entry.inject === void 0 ? {} : { inject: entry.inject },
78
+ ...entry.realm === void 0 ? {} : { realm: entry.realm }
79
+ };
80
+ }
81
+ function projectPairs(pairs) {
82
+ const entries = [];
83
+ const modules = [];
84
+ const projections = [];
85
+ for (const pair of pairs) {
86
+ if (pair.backend !== void 0 && pair.frontend !== void 0 && pair.backend.module.version !== pair.frontend.module.version) {
87
+ throw new HostAssemblyError(
88
+ "host-assembly-module-version-mismatch",
89
+ `paired module ${pair.id} to use one code version on both hosts`,
90
+ "Resolve backend and frontend package entries from the same locked package revision.",
91
+ {
92
+ name: pair.id,
93
+ actual: pair.backend.module.version,
94
+ expected: pair.frontend.module.version
95
+ }
96
+ );
97
+ }
98
+ if (pair.frontend === void 0) continue;
99
+ entries.push(pair.frontend.entry);
100
+ modules.push(pair.frontend.module);
101
+ projections.push({
102
+ id: pair.id,
103
+ entryId: pair.frontend.entry.id,
104
+ module: pair.frontend.module
105
+ });
106
+ }
107
+ return { entries, modules, projections };
108
+ }
109
+ function assemblyPairsFromInput(input) {
110
+ if (input.pairs !== void 0) {
111
+ const projected = projectPairs(input.pairs);
112
+ return {
113
+ entries: input.entries ?? projected.entries,
114
+ modules: input.modules ?? projected.modules,
115
+ pairs: projected.projections
116
+ };
117
+ }
118
+ const entries = input.entries ?? [];
119
+ const modules = [...input.modules ?? []];
120
+ for (const [index, entry] of entries.entries()) {
121
+ if (modules[index] !== void 0 || modules.some((module) => module.name === entry.name))
122
+ continue;
123
+ modules.push({
124
+ name: entry.name,
125
+ realm: entry.realm ?? "engine",
126
+ version: "unknown"
127
+ });
128
+ }
129
+ return {
130
+ entries,
131
+ modules,
132
+ pairs: entries.map((entry, index) => ({
133
+ id: entry.id,
134
+ entryId: entry.id,
135
+ module: modules[index]?.name === entry.name ? modules[index] : modules.find((module) => module.name === entry.name) ?? {
136
+ name: entry.name,
137
+ realm: entry.realm ?? "engine",
138
+ version: "unknown"
139
+ }
140
+ }))
141
+ };
142
+ }
143
+ function createHostAssembly(input) {
144
+ const projected = assemblyPairsFromInput(input);
145
+ const entries = projected.entries.map(cloneEntry);
146
+ const modules = projected.modules.map((module) => ({ ...module }));
147
+ const pairs = projected.pairs.map((pair) => ({ ...pair, module: { ...pair.module } }));
148
+ const identity = {
149
+ schemaVersion: HOST_ASSEMBLY_SCHEMA_VERSION,
150
+ entries,
151
+ modules,
152
+ pairs,
153
+ ...input.config === void 0 ? {} : { config: input.config }
154
+ };
155
+ return {
156
+ ...identity,
157
+ revision: hostRevision(identity)
158
+ };
159
+ }
160
+ function validateEntry(entry, path) {
161
+ if (entry === null || typeof entry !== "object") return `${path} must be an Entry object`;
162
+ if (typeof entry.id !== "string" || entry.id.length === 0) return `${path}.id must be non-empty`;
163
+ if (typeof entry.name !== "string" || entry.name.length === 0)
164
+ return `${path}.name must be non-empty`;
165
+ if (entry.group === true) {
166
+ if (!Array.isArray(entry.config)) return `${path}.config must be an Entry array for a Group`;
167
+ for (const [index, child] of entry.config.entries()) {
168
+ const reason = validateEntry(child, `${path}.config[${index}]`);
169
+ if (reason !== void 0) return reason;
170
+ }
171
+ }
172
+ return void 0;
173
+ }
174
+ function validateHostAssembly(assembly) {
175
+ if (assembly === null || typeof assembly !== "object") {
176
+ return {
177
+ ok: false,
178
+ error: new HostAssemblyError(
179
+ "host-assembly-invalid",
180
+ "assembly to be an object",
181
+ "Regenerate the frontend assembly from the active backend authority.",
182
+ { reason: "assembly is not an object" }
183
+ )
184
+ };
185
+ }
186
+ if (assembly.schemaVersion !== HOST_ASSEMBLY_SCHEMA_VERSION) {
187
+ return {
188
+ ok: false,
189
+ error: new HostAssemblyError(
190
+ "host-assembly-invalid",
191
+ `assembly schema ${HOST_ASSEMBLY_SCHEMA_VERSION}`,
192
+ "Regenerate the frontend assembly with the matching Engine host package.",
193
+ { reason: `unsupported schema ${String(assembly.schemaVersion)}` }
194
+ )
195
+ };
196
+ }
197
+ if (!Array.isArray(assembly.entries) || !Array.isArray(assembly.modules)) {
198
+ return {
199
+ ok: false,
200
+ error: new HostAssemblyError(
201
+ "host-assembly-invalid",
202
+ "assembly entries and modules to be arrays",
203
+ "Regenerate the frontend assembly from the active backend authority.",
204
+ { reason: "entries or modules is not an array" }
205
+ )
206
+ };
207
+ }
208
+ if (!Array.isArray(assembly.pairs)) {
209
+ return {
210
+ ok: false,
211
+ error: new HostAssemblyError(
212
+ "host-assembly-invalid",
213
+ "assembly pairs to be an array",
214
+ "Regenerate the assembly from the backend host using the matching host package.",
215
+ { reason: "pairs is not an array" }
216
+ )
217
+ };
218
+ }
219
+ const seen = /* @__PURE__ */ new Set();
220
+ for (const [index, entry] of assembly.entries.entries()) {
221
+ const reason = validateEntry(entry, `entries[${index}]`);
222
+ if (reason !== void 0) {
223
+ return {
224
+ ok: false,
225
+ error: new HostAssemblyError(
226
+ "host-assembly-invalid",
227
+ "all assembly entries to be valid native EntryOptions",
228
+ "Repair the backend Entry projection before publishing it to a browser.",
229
+ { reason }
230
+ )
231
+ };
232
+ }
233
+ if (seen.has(entry.id)) {
234
+ return {
235
+ ok: false,
236
+ error: new HostAssemblyError(
237
+ "host-assembly-invalid",
238
+ "assembly Entry ids to be unique",
239
+ "Give repeated plugin instances independent stable ids.",
240
+ { reason: `duplicate Entry id ${entry.id}` }
241
+ )
242
+ };
243
+ }
244
+ seen.add(entry.id);
245
+ }
246
+ const seenModules = /* @__PURE__ */ new Map();
247
+ for (const [index, module] of assembly.modules.entries()) {
248
+ if (module === null || typeof module !== "object" || typeof module.name !== "string" || typeof module.realm !== "string" || typeof module.version !== "string" || module.name.length === 0 || module.version.length === 0 || module.url !== void 0 && typeof module.url !== "string" || module.digest !== void 0 && (typeof module.digest !== "string" || module.digest.length === 0)) {
249
+ return {
250
+ ok: false,
251
+ error: new HostAssemblyError(
252
+ "host-assembly-invalid",
253
+ "module names and versions to be non-empty",
254
+ "Regenerate the module projection from the resolved package metadata.",
255
+ { reason: `invalid module at index ${index}` }
256
+ )
257
+ };
258
+ }
259
+ const previousModule = seenModules.get(module.name);
260
+ if (previousModule !== void 0 && (previousModule.realm !== module.realm || previousModule.version !== module.version || previousModule.url !== module.url || previousModule.digest !== module.digest)) {
261
+ return {
262
+ ok: false,
263
+ error: new HostAssemblyError(
264
+ "host-assembly-invalid",
265
+ "assembly module identity to be consistent for repeated Entries",
266
+ "Reuse one resolved module identity when a package has multiple Entry instances.",
267
+ { reason: `module ${module.name} has conflicting identities` }
268
+ )
269
+ };
270
+ }
271
+ seenModules.set(module.name, module);
272
+ }
273
+ const seenPairIds = /* @__PURE__ */ new Set();
274
+ for (const [index, pair] of assembly.pairs.entries()) {
275
+ const pairModule = pair?.module;
276
+ if (pair === null || typeof pair !== "object" || typeof pair.id !== "string" || pair.id.length === 0 || typeof pair.entryId !== "string" || pair.entryId.length === 0 || pairModule === null || pairModule === void 0 || typeof pairModule !== "object" || typeof pairModule.name !== "string" || typeof pairModule.realm !== "string" || typeof pairModule.version !== "string" || pairModule.name.length === 0 || pairModule.version.length === 0 || pairModule.url !== void 0 && typeof pairModule.url !== "string" || pairModule.digest !== void 0 && (typeof pairModule.digest !== "string" || pairModule.digest.length === 0)) {
277
+ return {
278
+ ok: false,
279
+ error: new HostAssemblyError(
280
+ "host-assembly-invalid",
281
+ "assembly pair identities and modules to be valid",
282
+ "Regenerate paired frontend entries from the backend package manifest.",
283
+ { reason: `invalid pair at index ${index}` }
284
+ )
285
+ };
286
+ }
287
+ if (seenPairIds.has(pair.id)) {
288
+ return {
289
+ ok: false,
290
+ error: new HostAssemblyError(
291
+ "host-assembly-invalid",
292
+ "assembly pair ids to be unique",
293
+ "Give each paired package instance an independent stable id.",
294
+ { reason: `duplicate pair ${pair.id}` }
295
+ )
296
+ };
297
+ }
298
+ seenPairIds.add(pair.id);
299
+ if (!assembly.entries.some((entry) => entry.id === pair.entryId)) {
300
+ return {
301
+ ok: false,
302
+ error: new HostAssemblyError(
303
+ "host-assembly-invalid",
304
+ `pair ${pair.id} to reference an assembly Entry`,
305
+ "Keep paired identity and frontend Entry projection under one backend authority.",
306
+ { reason: `missing frontend Entry ${pair.entryId}` }
307
+ )
308
+ };
309
+ }
310
+ const module = assembly.modules.find(
311
+ (candidate) => candidate.name === pairModule.name && candidate.realm === pairModule.realm && candidate.version === pairModule.version && candidate.url === pairModule.url && candidate.digest === pairModule.digest
312
+ );
313
+ if (module === void 0) {
314
+ return {
315
+ ok: false,
316
+ error: new HostAssemblyError(
317
+ "host-assembly-invalid",
318
+ `pair ${pair.id} to reference a resolved frontend module`,
319
+ "Keep module/code identity in the backend-derived assembly projection.",
320
+ { reason: `missing frontend module ${pairModule.name}` }
321
+ )
322
+ };
323
+ }
324
+ }
325
+ const expected = hostRevision({
326
+ schemaVersion: assembly.schemaVersion,
327
+ entries: assembly.entries,
328
+ modules: assembly.modules,
329
+ pairs: assembly.pairs,
330
+ ...assembly.config === void 0 ? {} : { config: assembly.config }
331
+ });
332
+ if (expected !== assembly.revision) {
333
+ return {
334
+ ok: false,
335
+ error: new HostAssemblyError(
336
+ "host-assembly-revision-mismatch",
337
+ "assembly revision to match its entries and modules",
338
+ "Discard the stale response and request the current backend assembly again.",
339
+ { actual: assembly.revision, expected }
340
+ )
341
+ };
342
+ }
343
+ return { ok: true, value: assembly };
344
+ }
345
+ function modulesFromCatalog(catalog, realm, version = "static") {
346
+ return [...catalog.entries()].filter(([, record]) => record.realm === realm).map(([name, record]) => ({
347
+ name,
348
+ realm,
349
+ version: record.version ?? version,
350
+ ...record.digest === void 0 ? {} : { digest: record.digest }
351
+ }));
352
+ }
353
+
354
+ // src/transport.ts
355
+ var HOST_ASSEMBLY_SERVICE = "host/assembly.get";
356
+ var HOST_ACTIVATION_REPORT_SERVICE = "host/assembly.report";
357
+
358
+ // src/frontend.ts
359
+ function hostFoundationPlugin(assembly, transport) {
360
+ return {
361
+ name: "forgeax:frontend-host-foundation",
362
+ provide: ["hostAssembly", "hostTransport"],
363
+ apply(ctx) {
364
+ ctx.provide("hostAssembly", assembly);
365
+ if (transport !== void 0) ctx.provide("hostTransport", transport);
366
+ }
367
+ };
368
+ }
369
+ function dynamicModuleRecord(name, realm, version, url, digest) {
370
+ if (url === void 0) {
371
+ return {
372
+ realm,
373
+ version,
374
+ ...digest === void 0 ? {} : { digest },
375
+ load: async () => {
376
+ throw new HostAssemblyError(
377
+ "host-assembly-module-missing",
378
+ `module ${name} to have a browser URL or static Catalog record`,
379
+ "Add the module to the frozen Catalog or publish its browser entry from the backend.",
380
+ { name }
381
+ );
382
+ }
383
+ };
384
+ }
385
+ return {
386
+ realm,
387
+ version,
388
+ ...digest === void 0 ? {} : { digest },
389
+ load: () => import(
390
+ /* @vite-ignore */
391
+ url
392
+ )
393
+ };
394
+ }
395
+ function catalogForAssembly(assembly, staticCatalog) {
396
+ const catalog = /* @__PURE__ */ new Map();
397
+ for (const module of assembly.modules) {
398
+ const existing = staticCatalog?.get(module.name);
399
+ if (existing !== void 0) {
400
+ assertHostModuleCatalogIdentity(module, existing);
401
+ catalog.set(module.name, existing);
402
+ continue;
403
+ }
404
+ catalog.set(
405
+ module.name,
406
+ dynamicModuleRecord(module.name, module.realm, module.version, module.url, module.digest)
407
+ );
408
+ }
409
+ for (const [name, record] of staticCatalog ?? []) {
410
+ if (!catalog.has(name)) catalog.set(name, record);
411
+ }
412
+ return catalog;
413
+ }
414
+ function assertModuleVersions(assembly, versions) {
415
+ if (versions === void 0) return;
416
+ for (const module of assembly.modules) {
417
+ const actual = versions.get(module.name);
418
+ if (actual === void 0 || actual === module.version) continue;
419
+ throw new HostAssemblyError(
420
+ "host-assembly-module-version-mismatch",
421
+ `module ${module.name} to use version ${module.version}`,
422
+ "Refresh the browser module graph from the same backend assembly revision.",
423
+ { name: module.name, actual, expected: module.version }
424
+ );
425
+ }
426
+ }
427
+ function moduleIdentity(module) {
428
+ return `${module.version}@${module.digest ?? module.url ?? "<catalog>"}`;
429
+ }
430
+ function assertModuleReloadBoundary(previous, next) {
431
+ const previousModules = new Map(previous.modules.map((module) => [module.name, module]));
432
+ const nextModules = new Map(next.modules.map((module) => [module.name, module]));
433
+ const names = /* @__PURE__ */ new Set([...previousModules.keys(), ...nextModules.keys()]);
434
+ for (const name of names) {
435
+ const before = previousModules.get(name);
436
+ const after = nextModules.get(name);
437
+ const beforeIdentity = before === void 0 ? "<absent>" : moduleIdentity(before);
438
+ const afterIdentity = after === void 0 ? "<absent>" : moduleIdentity(after);
439
+ if (before === void 0 || after === void 0 || before.realm !== after.realm || beforeIdentity !== afterIdentity) {
440
+ throw new HostAssemblyError(
441
+ "host-assembly-reload-required",
442
+ `module ${name} to keep its loaded code identity ${beforeIdentity}`,
443
+ "Reload the frontend host to install the new module graph before activating this assembly.",
444
+ { module: name, actual: beforeIdentity, expected: afterIdentity }
445
+ );
446
+ }
447
+ }
448
+ }
449
+ function staticAssembly(options, realm) {
450
+ const entries = options.entries ?? [];
451
+ const modules = options.catalog === void 0 ? [] : modulesFromCatalog(options.catalog, realm, "static");
452
+ return createHostAssembly({
453
+ entries,
454
+ modules,
455
+ ...options.config === void 0 ? {} : { config: options.config }
456
+ });
457
+ }
458
+ function errorSummary(error) {
459
+ if (error === null || typeof error !== "object" || typeof error.code !== "string" || typeof error.expected !== "string" || typeof error.hint !== "string")
460
+ return void 0;
461
+ const detail = error.detail;
462
+ return {
463
+ code: error.code,
464
+ expected: error.expected,
465
+ hint: error.hint,
466
+ detail: detail !== null && typeof detail === "object" ? detail : { reason: String(detail ?? "unknown failure") }
467
+ };
468
+ }
469
+ async function fetchInitialAssembly(options, realm) {
470
+ if (options.assembly !== void 0) return options.assembly;
471
+ if (options.transport !== void 0)
472
+ return options.transport.request(HOST_ASSEMBLY_SERVICE, void 0);
473
+ if (options.entries !== void 0 || options.catalog !== void 0 || options.config !== void 0)
474
+ return staticAssembly(options, realm);
475
+ throw new HostAssemblyError(
476
+ "host-assembly-service-unavailable",
477
+ "a static assembly or backend transport to be provided",
478
+ "Pass the frozen assembly for a static player or connect the frontend host to a backend host.",
479
+ { service: HOST_ASSEMBLY_SERVICE }
480
+ );
481
+ }
482
+ function readiness(loader) {
483
+ return inspectCatalogPlugins(loader).live.map((entry) => ({
484
+ entryId: entry.entryId,
485
+ fiberState: entry.fiberState,
486
+ ...entry.failure === void 0 ? {} : {
487
+ failure: {
488
+ code: entry.failure.code,
489
+ expected: entry.failure.expected,
490
+ hint: entry.failure.hint,
491
+ detail: entry.failure.detail
492
+ }
493
+ }
494
+ }));
495
+ }
496
+ function assertReady(entries) {
497
+ const failed = entries.find(
498
+ (entry) => entry.fiberState !== "active" && entry.fiberState !== "disabled"
499
+ );
500
+ if (failed === void 0) return;
501
+ throw new HostAssemblyError(
502
+ "host-assembly-not-ready",
503
+ `Entry ${failed.entryId} to reach active or disabled Fiber state`,
504
+ "Inspect the Entry failure or waiting dependency and repair the frontend package graph.",
505
+ {
506
+ entryId: failed.entryId,
507
+ fiberState: failed.fiberState,
508
+ ...failed.failure === void 0 ? {} : { failure: failed.failure }
509
+ }
510
+ );
511
+ }
512
+ async function createFrontendHost(options = {}) {
513
+ const realm = options.realm ?? "engine";
514
+ const initial = await fetchInitialAssembly(options, realm);
515
+ const context = options.context ?? new Context();
516
+ const ownedContext = options.context === void 0;
517
+ const checked = validateHostAssembly(initial);
518
+ if (!checked.ok) throw checked.error;
519
+ let current = checked.value;
520
+ let status = {
521
+ state: "created",
522
+ revision: current.revision
523
+ };
524
+ const state = {
525
+ get current() {
526
+ return current;
527
+ },
528
+ get status() {
529
+ return status;
530
+ }
531
+ };
532
+ const startupFibers = [];
533
+ let foundationFiber;
534
+ let loaderFiber;
535
+ let loader;
536
+ let disposed = false;
537
+ let removeTransportDisconnect;
538
+ const report = async (next) => {
539
+ status = next;
540
+ await options.reportStatus?.(next);
541
+ if (options.transport !== void 0) {
542
+ const report2 = {
543
+ state: next.state,
544
+ revision: next.revision
545
+ };
546
+ if (next.entries !== void 0) report2.entries = next.entries;
547
+ const failure = errorSummary(next.error);
548
+ if (failure !== void 0) report2.error = failure;
549
+ try {
550
+ await options.transport.request(HOST_ACTIVATION_REPORT_SERVICE, report2);
551
+ } catch (error) {
552
+ const failed = {
553
+ ...next,
554
+ state: next.state === "active" ? "failed" : next.state,
555
+ error
556
+ };
557
+ status = failed;
558
+ await options.reportStatus?.(failed);
559
+ throw error;
560
+ }
561
+ }
562
+ };
563
+ if (options.transport !== void 0) {
564
+ removeTransportDisconnect = options.transport.onDisconnect((error) => {
565
+ if (disposed) return;
566
+ const failed = {
567
+ state: "failed",
568
+ revision: current.revision,
569
+ error
570
+ };
571
+ status = failed;
572
+ void Promise.resolve(options.reportStatus?.(failed)).catch(() => {
573
+ });
574
+ });
575
+ }
576
+ try {
577
+ foundationFiber = await context.plugin(hostFoundationPlugin(state, options.transport));
578
+ for (const plugin of options.startupPlugins ?? [])
579
+ startupFibers.push(await context.plugin(plugin));
580
+ if (options.autoActivate !== false) {
581
+ const host2 = {
582
+ context,
583
+ ...options.transport === void 0 ? {} : { transport: options.transport },
584
+ assembly: state,
585
+ ...ownedContext ? { ownedContext: true } : { ownedContext: false },
586
+ get status() {
587
+ return status;
588
+ },
589
+ activate: async (_next) => {
590
+ },
591
+ update: async (_next) => {
592
+ },
593
+ dispose: async () => {
594
+ }
595
+ };
596
+ await activateFrontendHost(
597
+ host2,
598
+ initial,
599
+ options,
600
+ realm,
601
+ () => loader,
602
+ (value) => {
603
+ loader = value.loader;
604
+ loaderFiber = value.fiber;
605
+ },
606
+ report,
607
+ () => {
608
+ current = initial;
609
+ }
610
+ );
611
+ }
612
+ } catch (error) {
613
+ removeTransportDisconnect?.();
614
+ await loaderFiber?.dispose();
615
+ for (const fiber of startupFibers.reverse()) await fiber.dispose();
616
+ await foundationFiber?.dispose();
617
+ if (ownedContext) await context.fiber.dispose();
618
+ throw error;
619
+ }
620
+ const host = {
621
+ context,
622
+ ...loader === void 0 ? {} : { loader },
623
+ ...options.transport === void 0 ? {} : { transport: options.transport },
624
+ assembly: state,
625
+ ownedContext,
626
+ get status() {
627
+ return status;
628
+ },
629
+ async activate(next = current) {
630
+ await activateFrontendHost(
631
+ host,
632
+ next,
633
+ options,
634
+ realm,
635
+ () => loader,
636
+ (value) => {
637
+ loader = value.loader;
638
+ loaderFiber = value.fiber;
639
+ host.loader = value.loader;
640
+ },
641
+ report,
642
+ () => {
643
+ current = next;
644
+ }
645
+ );
646
+ },
647
+ async update(next) {
648
+ await host.activate(next);
649
+ },
650
+ async dispose() {
651
+ if (disposed) return;
652
+ disposed = true;
653
+ removeTransportDisconnect?.();
654
+ await loaderFiber?.dispose();
655
+ for (const fiber of startupFibers.reverse()) await fiber.dispose();
656
+ await foundationFiber?.dispose();
657
+ if (ownedContext) await context.fiber.dispose();
658
+ status = { state: "disposed", revision: current.revision };
659
+ }
660
+ };
661
+ return host;
662
+ }
663
+ async function activateFrontendHost(host, next, options, realm, getLoader, setLoader, report, commit) {
664
+ if (host.status.state === "disposed") {
665
+ throw new HostAssemblyError(
666
+ "host-assembly-service-unavailable",
667
+ "frontend host to remain active while activating an assembly",
668
+ "Create a new frontend host for the next browser connection.",
669
+ { service: "host-assembly" }
670
+ );
671
+ }
672
+ const checked = validateHostAssembly(next);
673
+ if (!checked.ok) {
674
+ await report({ state: "failed", revision: next.revision, error: checked.error });
675
+ throw checked.error;
676
+ }
677
+ const activeLoader = getLoader();
678
+ try {
679
+ if (activeLoader !== void 0)
680
+ assertModuleReloadBoundary(host.assembly.current, checked.value);
681
+ await report({ state: "loading", revision: next.revision });
682
+ assertModuleVersions(checked.value, options.moduleVersions);
683
+ let loader = activeLoader;
684
+ if (loader === void 0) {
685
+ const catalog = catalogForAssembly(checked.value, options.catalog);
686
+ const installed = await installCatalogLoader(host.context, catalog, realm);
687
+ loader = installed.loader;
688
+ setLoader(installed);
689
+ }
690
+ const entries = projectPluginEntries(checked.value.entries, realm, realm);
691
+ await loader.root.update(entries);
692
+ await loader.await();
693
+ const actualEntries = readiness(loader);
694
+ assertReady(actualEntries);
695
+ commit();
696
+ await report({ state: "active", revision: checked.value.revision, entries: actualEntries });
697
+ } catch (error) {
698
+ if (error instanceof HostAssemblyError && error.code === "host-assembly-reload-required") {
699
+ throw error;
700
+ }
701
+ const activeLoader2 = getLoader();
702
+ const actualEntries = activeLoader2 === void 0 ? void 0 : readiness(activeLoader2);
703
+ await report({
704
+ state: "failed",
705
+ revision: next.revision,
706
+ ...actualEntries === void 0 ? {} : { entries: actualEntries },
707
+ error
708
+ });
709
+ throw error;
710
+ }
711
+ }
712
+
713
+ export { HostAssemblyError, createFrontendHost };
714
+ //# sourceMappingURL=frontend.mjs.map
715
+ //# sourceMappingURL=frontend.mjs.map