@bleedingdev/modern-js-renderer-core 3.9.3-ultramodern.1 → 3.9.3-ultramodern.10

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 (53) hide show
  1. package/dist/cjs/adapter.js +1 -1
  2. package/dist/cjs/data/codec.js +1 -1
  3. package/dist/cjs/entry-client.js +5 -0
  4. package/dist/cjs/federation.js +14 -5
  5. package/dist/cjs/server/worker.js +2 -1
  6. package/dist/cjs/worker-federation/contract.js +298 -0
  7. package/dist/cjs/worker-federation/handler.js +197 -0
  8. package/dist/cjs/worker-federation/index.js +63 -0
  9. package/dist/cjs/worker-federation/lease.js +167 -0
  10. package/dist/cjs/worker-federation/transport.js +272 -0
  11. package/dist/esm/adapter.mjs +1 -1
  12. package/dist/esm/data/codec.mjs +1 -1
  13. package/dist/esm/entry-client.mjs +5 -0
  14. package/dist/esm/federation.mjs +14 -5
  15. package/dist/esm/server/worker.mjs +2 -1
  16. package/dist/esm/worker-federation/contract.mjs +227 -0
  17. package/dist/esm/worker-federation/handler.mjs +159 -0
  18. package/dist/esm/worker-federation/index.mjs +3 -0
  19. package/dist/esm/worker-federation/lease.mjs +107 -0
  20. package/dist/esm/worker-federation/transport.mjs +234 -0
  21. package/dist/esm-node/adapter.mjs +1 -1
  22. package/dist/esm-node/data/codec.mjs +1 -1
  23. package/dist/esm-node/entry-client.mjs +5 -0
  24. package/dist/esm-node/federation.mjs +14 -5
  25. package/dist/esm-node/server/worker.mjs +2 -1
  26. package/dist/esm-node/worker-federation/contract.mjs +228 -0
  27. package/dist/esm-node/worker-federation/handler.mjs +160 -0
  28. package/dist/esm-node/worker-federation/index.mjs +4 -0
  29. package/dist/esm-node/worker-federation/lease.mjs +108 -0
  30. package/dist/esm-node/worker-federation/transport.mjs +235 -0
  31. package/dist/types/adapter.d.ts +1 -1
  32. package/dist/types/data/codec.d.ts +1 -1
  33. package/dist/types/entry-client.d.ts +2 -0
  34. package/dist/types/federation.d.ts +15 -2
  35. package/dist/types/server/types.d.ts +3 -0
  36. package/dist/types/server/worker.d.ts +19 -0
  37. package/dist/types/worker-federation/contract.d.ts +70 -0
  38. package/dist/types/worker-federation/handler.d.ts +21 -0
  39. package/dist/types/worker-federation/index.d.ts +6 -0
  40. package/dist/types/worker-federation/lease.d.ts +11 -0
  41. package/dist/types/worker-federation/transport.d.ts +47 -0
  42. package/package.json +17 -7
  43. package/src/adapter.ts +4 -2
  44. package/src/data/codec.ts +1 -1
  45. package/src/entry-client.ts +5 -0
  46. package/src/federation.ts +36 -8
  47. package/src/server/types.ts +3 -0
  48. package/src/server/worker.ts +23 -0
  49. package/src/worker-federation/contract.ts +449 -0
  50. package/src/worker-federation/handler.ts +274 -0
  51. package/src/worker-federation/index.ts +23 -0
  52. package/src/worker-federation/lease.ts +143 -0
  53. package/src/worker-federation/transport.ts +430 -0
@@ -0,0 +1,234 @@
1
+ import { admitWorkerFederationExpose, readWorkerFederationEnvelope, readWorkerFederationManifestUrl, readWorkerFederationPath, readWorkerFederationStyles, workerFederationEnvelope, workerFederationWireJson } from "./contract.mjs";
2
+ import { assertWorkerFederationBodySize, checkWorkerFederationDeadline, readWorkerFederationJsonBody, workerAbortReason, workerAbortable } from "./lease.mjs";
3
+ async function readResponseJson(response, expectedUrl, signal, checkDeadline) {
4
+ const cancelBody = ()=>{
5
+ response.body?.cancel().catch(()=>void 0);
6
+ };
7
+ if (signal.aborted) {
8
+ cancelBody();
9
+ throw workerAbortReason(signal);
10
+ }
11
+ try {
12
+ checkDeadline();
13
+ } catch (error) {
14
+ cancelBody();
15
+ throw error;
16
+ }
17
+ if (!response.ok || response.redirected || response.url && response.url !== expectedUrl) {
18
+ cancelBody();
19
+ throw new Error(`Worker federation publication request failed (${response.status}).`);
20
+ }
21
+ if (!response.body) throw new Error('Worker federation response has no body.');
22
+ return readWorkerFederationJsonBody(response.body, signal, response.headers.get('content-length'), checkDeadline);
23
+ }
24
+ async function fetchJson(fetchResponse, request, signal, checkDeadline) {
25
+ checkDeadline();
26
+ if (signal.aborted) throw workerAbortReason(signal);
27
+ const pending = fetchResponse(request);
28
+ pending.then((response)=>{
29
+ if (signal.aborted) response.body?.cancel().catch(()=>void 0);
30
+ }, ()=>void 0);
31
+ return readResponseJson(await workerAbortable(pending, signal), request.url, signal, checkDeadline);
32
+ }
33
+ function createWorkerFederationTransport(options) {
34
+ const timeout = options.requestTimeout ?? 3000;
35
+ const admitRendererContract = options.admitRendererContract;
36
+ if ('function' != typeof admitRendererContract) throw new Error('Worker federation requires canonical renderer admission.');
37
+ if (!Number.isFinite(timeout) || timeout <= 0) throw new Error('Worker federation requires a positive request deadline.');
38
+ const services = new Map();
39
+ for (const service of options.services){
40
+ if (!service.remote || services.has(service.remote)) throw new Error('Worker federation service remotes must be unique.');
41
+ const exposes = new Set();
42
+ for (const fragment of service.fragments){
43
+ Object.values(fragment.paths).forEach(readWorkerFederationPath);
44
+ if (!fragment.boundaryId || exposes.has(fragment.expose)) throw new Error('Worker federation service exposes must be explicit and unique.');
45
+ exposes.add(fragment.expose);
46
+ }
47
+ const captured = {
48
+ ...service,
49
+ manifestUrl: readWorkerFederationManifestUrl(service.manifestUrl).href,
50
+ fetch: service.fetch.bind(service),
51
+ fragments: service.fragments.map((fragment)=>Object.freeze({
52
+ ...fragment,
53
+ paths: Object.freeze({
54
+ ...fragment.paths
55
+ })
56
+ }))
57
+ };
58
+ services.set(service.remote, captured);
59
+ if (service.alias && service.alias !== service.remote) {
60
+ if (services.has(service.alias)) throw new Error('Worker federation service aliases must be unique.');
61
+ services.set(service.alias, captured);
62
+ }
63
+ }
64
+ const snapshots = new Map();
65
+ const admissions = new Map();
66
+ const fetchManifest = options.fetchManifest;
67
+ let retired;
68
+ const onOwnerAbort = ()=>dispose(workerAbortReason(options.signal));
69
+ function checkDocumentLiveness() {
70
+ if (retired) throw retired.reason;
71
+ }
72
+ function dispose(reason = new DOMException('The Worker federation document was disposed', 'AbortError')) {
73
+ if (retired) return;
74
+ retired = {
75
+ reason
76
+ };
77
+ options.signal?.removeEventListener('abort', onOwnerAbort);
78
+ for (const pending of [
79
+ ...snapshots.values(),
80
+ ...admissions.values()
81
+ ])pending.controller.abort(reason);
82
+ snapshots.clear();
83
+ admissions.clear();
84
+ }
85
+ if (options.signal?.aborted) onOwnerAbort();
86
+ else options.signal?.addEventListener('abort', onOwnerAbort, {
87
+ once: true
88
+ });
89
+ function serviceCoordinate(remote, expose, locale) {
90
+ const service = services.get(remote);
91
+ const fragment = service?.fragments.find((value)=>value.expose === expose);
92
+ if (!service || !fragment || !Object.hasOwn(fragment.paths, locale)) throw new Error('Worker federation remote/expose has no configured service binding.');
93
+ return {
94
+ service,
95
+ fragment
96
+ };
97
+ }
98
+ async function shared(cache, key, operation, signal, deadlineAt = performance.now() + timeout, retainFulfilled = true) {
99
+ checkDocumentLiveness();
100
+ if (signal?.aborted) throw workerAbortReason(signal);
101
+ let pending = cache.get(key);
102
+ if (!pending) {
103
+ const controller = new AbortController();
104
+ const checkDeadline = ()=>checkWorkerFederationDeadline(deadlineAt, controller.signal, controller);
105
+ const deadline = setTimeout(()=>controller.abort(new Error('Worker federation request deadline exceeded.')), Math.max(0, deadlineAt - performance.now()));
106
+ const promise = operation(controller.signal, checkDeadline);
107
+ pending = {
108
+ controller,
109
+ promise,
110
+ waiters: 0,
111
+ settled: false
112
+ };
113
+ const captured = pending;
114
+ cache.set(key, captured);
115
+ promise.then(()=>{
116
+ captured.settled = true;
117
+ clearTimeout(deadline);
118
+ if (!retainFulfilled && cache.get(key) === captured) cache.delete(key);
119
+ }, ()=>{
120
+ captured.settled = true;
121
+ clearTimeout(deadline);
122
+ if (cache.get(key) === captured) cache.delete(key);
123
+ });
124
+ }
125
+ const captured = pending;
126
+ captured.waiters++;
127
+ try {
128
+ const value = await (signal ? workerAbortable(captured.promise, signal) : captured.promise);
129
+ checkDocumentLiveness();
130
+ checkWorkerFederationDeadline(deadlineAt, signal);
131
+ return value;
132
+ } finally{
133
+ captured.waiters--;
134
+ if (!captured.settled && 0 === captured.waiters) {
135
+ captured.controller.abort(new Error('Worker federation request has no active consumers.'));
136
+ if (cache.get(key) === captured) cache.delete(key);
137
+ }
138
+ }
139
+ }
140
+ function admitWithin({ remote, expose, locale, signal }, deadlineAt = performance.now() + timeout) {
141
+ const { service, fragment } = serviceCoordinate(remote, expose, locale);
142
+ const key = workerFederationWireJson([
143
+ service.remote,
144
+ expose,
145
+ locale
146
+ ]);
147
+ const promise = shared(admissions, key, async (signal, checkDeadline)=>{
148
+ checkDeadline();
149
+ const request = new Request(service.manifestUrl, {
150
+ redirect: 'manual',
151
+ signal
152
+ });
153
+ const manifest = await fetchJson(fetchManifest ?? service.fetch, request, signal, checkDeadline);
154
+ const styles = readWorkerFederationStyles({
155
+ manifest,
156
+ manifestUrl: request.url,
157
+ expose
158
+ });
159
+ const admitted = admitWorkerFederationExpose({
160
+ manifest,
161
+ manifestUrl: request.url,
162
+ remote: service.remote,
163
+ expose,
164
+ locale,
165
+ admitRendererContract
166
+ });
167
+ checkDeadline();
168
+ if (admitted.coordinate.boundaryId !== fragment.boundaryId || admitted.path !== fragment.paths[locale]) throw new Error('Worker federation publication conflicts with its service coordinate.');
169
+ return Object.freeze({
170
+ ...admitted,
171
+ styles
172
+ });
173
+ }, signal, deadlineAt);
174
+ return {
175
+ promise,
176
+ key,
177
+ pending: admissions.get(key)
178
+ };
179
+ }
180
+ return {
181
+ dispose,
182
+ admit: (options)=>admitWithin(options).promise,
183
+ async request ({ remote, expose, locale, payload, signal }) {
184
+ const deadlineAt = performance.now() + timeout;
185
+ checkDocumentLiveness();
186
+ if (signal?.aborted) throw workerAbortReason(signal);
187
+ const { service } = serviceCoordinate(remote, expose, locale);
188
+ if ('string' == typeof payload) assertWorkerFederationBodySize(payload);
189
+ const encodedPayload = assertWorkerFederationBodySize(workerFederationWireJson(payload));
190
+ const key = workerFederationWireJson([
191
+ service.remote,
192
+ expose,
193
+ locale,
194
+ encodedPayload
195
+ ]);
196
+ checkWorkerFederationDeadline(deadlineAt, signal);
197
+ return shared(snapshots, key, async (signal, checkDeadline)=>{
198
+ checkDeadline();
199
+ const admission = admitWithin({
200
+ remote,
201
+ expose,
202
+ locale,
203
+ signal
204
+ }, deadlineAt);
205
+ const admitted = await admission.promise;
206
+ checkDeadline();
207
+ try {
208
+ const body = assertWorkerFederationBodySize(workerFederationWireJson(workerFederationEnvelope('request', admitted, JSON.parse(encodedPayload))));
209
+ checkDeadline();
210
+ const response = await fetchJson(service.fetch, new Request(admitted.endpoint, {
211
+ method: 'POST',
212
+ headers: {
213
+ 'content-type': 'application/json'
214
+ },
215
+ body,
216
+ redirect: 'manual',
217
+ signal
218
+ }), signal, checkDeadline);
219
+ checkDeadline();
220
+ const decodedPayload = readWorkerFederationEnvelope(response, 'response', admitted);
221
+ checkDeadline();
222
+ return {
223
+ admitted,
224
+ payload: decodedPayload
225
+ };
226
+ } catch (error) {
227
+ if (!signal.aborted && admissions.get(admission.key) === admission.pending) admissions.delete(admission.key);
228
+ throw error;
229
+ }
230
+ }, signal, deadlineAt, false);
231
+ }
232
+ };
233
+ }
234
+ export { createWorkerFederationTransport };
@@ -16,7 +16,7 @@ function defineRendererAdapter(adapter) {
16
16
  function nativeRendererDependencies(profile) {
17
17
  return {
18
18
  ...Object.fromEntries(Object.entries(profile.dependencies).filter(([name])=>!name.startsWith('@modern-js/'))),
19
- [profile.router.coreName]: profile.router.coreVersion
19
+ [profile.router.coreName]: profile.dependencies[profile.router.coreName] ?? profile.router.coreVersion
20
20
  };
21
21
  }
22
22
  export { defineRendererAdapter, nativeRendererDependencies };
@@ -1,7 +1,7 @@
1
1
  import "node:module";
2
2
  import { fromJSON, toJSON } from "seroval";
3
3
  import { validateSerializedData } from "./tree.mjs";
4
- const DATA_CODEC = 'seroval-json@1.6.8';
4
+ const DATA_CODEC = 'seroval-json@1.6.9';
5
5
  const MAX_DATA_BYTES = 1048576;
6
6
  const MAX_DEPTH = 100;
7
7
  const MAX_NODES = 20000;
@@ -70,6 +70,11 @@ function startNativeClientEntry(options, adapter) {
70
70
  rewrite: i18n.rewrite(()=>current.language)
71
71
  } : {}
72
72
  });
73
+ const releaseRouter = ()=>adapter.disposeRouter(router);
74
+ if (signal.aborted) releaseRouter();
75
+ else signal.addEventListener('abort', releaseRouter, {
76
+ once: true
77
+ });
73
78
  const stopSync = i18n && current ? i18n.syncWithRouter(router, current) : void 0;
74
79
  if (stopSync) signal.addEventListener('abort', stopSync, {
75
80
  once: true
@@ -1,4 +1,5 @@
1
1
  import "node:module";
2
+ const LOAD_WITH_CAPTURE = Symbol.for('ultramodern.federation.load-with-capture');
2
3
  function getFederationHost(binding) {
3
4
  if (void 0 === binding) return;
4
5
  if ('function' != typeof binding?.instance) throw new TypeError('The native federation host binding is invalid.');
@@ -8,16 +9,24 @@ function getFederationHost(binding) {
8
9
  }
9
10
  async function loadFederatedModule(instance, id) {
10
11
  if (!instance) throw new Error(`Cannot load ${id}: this application has no Module Federation runtime. Add module-federation.config.ts with its remotes.`);
11
- const module = await instance.loadRemote(id);
12
+ const coupledLoad = instance[LOAD_WITH_CAPTURE];
13
+ const result = coupledLoad ? await coupledLoad(id, (_module, resolution)=>remoteBrowserAssets(instance, id, resolution)) : {
14
+ module: await instance.loadRemote(id),
15
+ captured: remoteBrowserAssets(instance, id)
16
+ };
17
+ const module = result.module;
12
18
  if (!module) throw new Error(`Remote module ${id} is unavailable`);
13
- return module;
19
+ return {
20
+ module,
21
+ assets: result.captured
22
+ };
14
23
  }
15
- function remoteBrowserAssets(instance, id) {
24
+ function remoteBrowserAssets(instance, id, resolution) {
16
25
  const target = instance.remoteHandler?.idToRemoteMap?.[id];
17
26
  const loaded = target && instance.moduleCache?.get(target.name);
18
- const snapshot = loaded && instance.snapshotHandler?.getGlobalRemoteInfo(loaded.remoteInfo).remoteSnapshot;
27
+ const snapshot = resolution ? resolution.remoteSnapshot : loaded && instance.snapshotHandler?.getGlobalRemoteInfo(loaded.remoteInfo).remoteSnapshot;
19
28
  if (!snapshot || 'string' != typeof snapshot.publicPath || !snapshot.publicPath || 'string' != typeof snapshot.remoteEntry || !snapshot.remoteEntry) return;
20
- const expose = snapshot.modules?.find((module)=>module.modulePath === target.expose);
29
+ const expose = snapshot.modules?.find((module)=>module.modulePath === (resolution?.expose ?? target?.expose));
21
30
  if (!expose?.assets) return;
22
31
  const publicPath = snapshot.publicPath;
23
32
  const url = (file)=>/^(?:https?:)?\/\//iu.test(file) ? file : `${publicPath.replace(/\/$/u, '')}/${file.replace(/^\//u, '')}`;
@@ -29,7 +29,8 @@ function dispatchNativeWorkerRequest(request, options) {
29
29
  ...resources.serverConfig,
30
30
  forceCSR
31
31
  },
32
- nonce: resources.nonce
32
+ nonce: resources.nonce,
33
+ workerFederation: options.workerFederation
33
34
  }
34
35
  });
35
36
  }
@@ -0,0 +1,228 @@
1
+ import "node:module";
2
+ import { identityCacheKey, readRendererIdentity } from "../identity.mjs";
3
+ const WORKER_FEDERATION_METADATA_KEY = 'ultramodernWorkerFederation';
4
+ const WORKER_FEDERATION_SCHEMA = 'ultramodern.worker-federation';
5
+ const WORKER_FEDERATION_VERSION = 1;
6
+ function workerFederationRecord(value, label) {
7
+ if (!value || 'object' != typeof value || ![
8
+ Object.prototype,
9
+ null
10
+ ].includes(Object.getPrototypeOf(value))) throw new Error(`Worker federation ${label} must be a plain record.`);
11
+ return value;
12
+ }
13
+ function exactFields(value, fields, label) {
14
+ if (Object.keys(value).length !== fields.length || Object.keys(value).some((key)=>!fields.includes(key))) throw new Error(`Worker federation ${label} has unsupported fields.`);
15
+ }
16
+ function requiredString(value, label) {
17
+ if ('string' != typeof value || 0 === value.length || value.trim() !== value || /[\u0000-\u001f\u007f]/u.test(value)) throw new Error(`Worker federation requires ${label}.`);
18
+ return value;
19
+ }
20
+ function readWorkerFederationPath(value) {
21
+ const path = requiredString(value, 'an endpoint path');
22
+ let decoded;
23
+ try {
24
+ decoded = decodeURIComponent(path);
25
+ } catch {
26
+ throw new Error('Worker federation endpoint path has invalid encoding.');
27
+ }
28
+ if (!path.startsWith('/') || path.startsWith('//') || /[\\?#\s\u0000-\u001f\u007f]/u.test(decoded) || /%[0-9a-f]{2}/iu.test(decoded) || decoded.split('/').some((segment)=>'.' === segment || '..' === segment) || new URL(path, 'https://worker.invalid').pathname !== path) throw new Error('Worker federation endpoint must be an exact root path.');
29
+ return path;
30
+ }
31
+ function readWorkerFederationManifestUrl(value) {
32
+ const url = new URL(value);
33
+ if (![
34
+ 'http:',
35
+ 'https:'
36
+ ].includes(url.protocol) || url.username || url.password || url.hash) throw new Error('Worker federation manifest requires an HTTP(S) publication URL.');
37
+ return url;
38
+ }
39
+ function readWorkerFederationPublication(value, identities) {
40
+ const record = workerFederationRecord(value, 'publication');
41
+ exactFields(record, [
42
+ 'schema',
43
+ 'schemaVersion',
44
+ 'remote',
45
+ 'exposes'
46
+ ], 'publication');
47
+ if (record.schema !== WORKER_FEDERATION_SCHEMA || record.schemaVersion !== WORKER_FEDERATION_VERSION) throw new Error('Unsupported Worker federation publication protocol.');
48
+ const remote = requiredString(record.remote, 'a remote');
49
+ const entries = workerFederationRecord(record.exposes, 'exposes');
50
+ if (0 === Object.keys(entries).length) throw new Error('Worker federation publication has no exposes.');
51
+ const paths = new Set();
52
+ const exposes = Object.fromEntries(Object.entries(entries).map(([expose, raw])=>{
53
+ if (!/^\.\/[A-Za-z0-9_$@.-]+(?:\/[A-Za-z0-9_$@.-]+)*$/u.test(expose) || expose.slice(2).split('/').some((segment)=>'.' === segment || '..' === segment)) throw new Error(`Unsupported Worker federation expose ${expose}.`);
54
+ const coordinate = workerFederationRecord(raw, 'expose coordinate');
55
+ exactFields(coordinate, [
56
+ 'entryName',
57
+ 'boundaryId',
58
+ 'paths',
59
+ 'native'
60
+ ], 'expose coordinate');
61
+ const entryName = requiredString(coordinate.entryName, 'an entry name');
62
+ if (!Object.hasOwn(identities, entryName)) throw new Error(`Worker federation expose ${expose} has no built entry.`);
63
+ const identity = identities[entryName];
64
+ identityCacheKey(identity);
65
+ if (identity.entryName !== entryName) throw new Error('Worker federation entry identity conflicts with its key.');
66
+ const rawPaths = workerFederationRecord(coordinate.paths, 'locale paths');
67
+ const native = workerFederationRecord(coordinate.native, 'native authority');
68
+ exactFields(native, [
69
+ 'publisherBuildId',
70
+ 'runtimeABI'
71
+ ], 'native authority');
72
+ if (1 !== native.runtimeABI) throw new Error('Unsupported native Worker federation runtime ABI.');
73
+ if (0 === Object.keys(rawPaths).length) throw new Error('Worker federation expose has no locale endpoints.');
74
+ const locales = Object.keys(rawPaths);
75
+ if (new Set(locales.map((locale)=>locale.toLowerCase())).size !== locales.length || locales.includes('default') && 1 !== locales.length) throw new Error('Worker federation requires unique locales or only default.');
76
+ const localePaths = Object.fromEntries(Object.entries(rawPaths).map(([locale, rawPath])=>{
77
+ if (!/^[a-zA-Z0-9]+(?:[-_][a-zA-Z0-9]+)*$/u.test(locale)) throw new Error('Worker federation endpoint has an invalid locale.');
78
+ const path = readWorkerFederationPath(rawPath);
79
+ if (paths.has(path)) throw new Error('Worker federation exposes have conflicting endpoint paths.');
80
+ paths.add(path);
81
+ return [
82
+ locale,
83
+ path
84
+ ];
85
+ }));
86
+ return [
87
+ expose,
88
+ Object.freeze({
89
+ entryName,
90
+ boundaryId: requiredString(coordinate.boundaryId, 'a boundary ID'),
91
+ paths: Object.freeze(localePaths),
92
+ native: Object.freeze({
93
+ publisherBuildId: requiredString(native.publisherBuildId, 'a native publisher build ID'),
94
+ runtimeABI: 1
95
+ })
96
+ })
97
+ ];
98
+ }));
99
+ return Object.freeze({
100
+ schema: WORKER_FEDERATION_SCHEMA,
101
+ schemaVersion: WORKER_FEDERATION_VERSION,
102
+ remote,
103
+ exposes: Object.freeze(exposes)
104
+ });
105
+ }
106
+ function workerFederationWireJson(value) {
107
+ const encode = (input)=>{
108
+ if (null === input || 'string' == typeof input || 'boolean' == typeof input) return input;
109
+ if ('number' == typeof input && Number.isFinite(input)) return input;
110
+ if (Array.isArray(input)) return input.map(encode);
111
+ const record = workerFederationRecord(input, 'wire value');
112
+ return Object.fromEntries(Object.keys(record).toSorted().map((key)=>[
113
+ key,
114
+ encode(record[key])
115
+ ]));
116
+ };
117
+ return JSON.stringify(encode(value));
118
+ }
119
+ function freezeWireValue(value) {
120
+ const copy = JSON.parse(workerFederationWireJson(value));
121
+ const freeze = (input)=>{
122
+ if (input && 'object' == typeof input) {
123
+ Object.values(input).forEach(freeze);
124
+ Object.freeze(input);
125
+ }
126
+ };
127
+ freeze(copy);
128
+ return copy;
129
+ }
130
+ function readWorkerFederationStyles(options) {
131
+ const manifest = workerFederationRecord(options.manifest, 'manifest');
132
+ const metadata = workerFederationRecord(manifest.metaData, 'manifest metadata');
133
+ const manifestUrl = readWorkerFederationManifestUrl(options.manifestUrl);
134
+ if (!Array.isArray(manifest.exposes)) throw new Error('Worker federation manifest exposes must be an array.');
135
+ const selected = manifest.exposes.map((value)=>workerFederationRecord(value, 'manifest expose')).filter((value)=>value.path === options.expose);
136
+ if (1 !== selected.length) throw new Error('Worker federation styles require one exact manifest expose.');
137
+ if (void 0 === selected[0].assets) return Object.freeze([]);
138
+ const assets = workerFederationRecord(selected[0].assets, 'expose assets');
139
+ if (void 0 === assets.css) return Object.freeze([]);
140
+ const css = workerFederationRecord(assets.css, 'expose CSS assets');
141
+ const files = [
142
+ 'sync',
143
+ 'async'
144
+ ].flatMap((kind)=>{
145
+ const values = css[kind];
146
+ if (void 0 === values) return [];
147
+ if (!Array.isArray(values)) throw new Error('Worker federation CSS assets must be string arrays.');
148
+ return values.map((value)=>requiredString(value, 'a CSS asset URL'));
149
+ });
150
+ if (0 === files.length) return Object.freeze([]);
151
+ if (void 0 === metadata.publicPath || 'auto' === metadata.publicPath) throw new Error('Worker federation CSS requires a completed static publicPath; auto and getPublicPath cannot resolve styles in a Worker.');
152
+ const publicPath = requiredString(metadata.publicPath, 'a completed public path');
153
+ const explicitScheme = /^[a-z][a-z0-9+.-]*:/iu;
154
+ const isHttpScheme = /^https?:/iu;
155
+ const assertScheme = (value)=>{
156
+ if (explicitScheme.test(value) && !isHttpScheme.test(value)) throw new Error('Worker federation styles require an HTTP(S) publication URL.');
157
+ };
158
+ assertScheme(publicPath);
159
+ const publicUrl = new URL(publicPath, manifestUrl);
160
+ if (![
161
+ 'http:',
162
+ 'https:'
163
+ ].includes(publicUrl.protocol) || publicUrl.username || publicUrl.password || publicUrl.hash) throw new Error('Worker federation styles require an HTTP(S) publication URL.');
164
+ const styles = files.map((file)=>{
165
+ assertScheme(file);
166
+ const resolved = /^(?:https?:)?\/\//iu.test(file) ? file : `${publicPath.replace(/\/$/u, '')}/${file.replace(/^\//u, '')}`;
167
+ const url = new URL(resolved, manifestUrl);
168
+ if (![
169
+ 'http:',
170
+ 'https:'
171
+ ].includes(url.protocol) || url.username || url.password || url.hash) throw new Error('Worker federation styles require an HTTP(S) publication URL.');
172
+ return resolved;
173
+ });
174
+ return Object.freeze([
175
+ ...new Set(styles)
176
+ ]);
177
+ }
178
+ function admitWorkerFederationExpose(options) {
179
+ const url = readWorkerFederationManifestUrl(options.manifestUrl);
180
+ const manifest = workerFederationRecord(options.manifest, 'manifest');
181
+ const metadata = workerFederationRecord(manifest.metaData, 'manifest metadata');
182
+ const renderer = freezeWireValue(options.admitRendererContract(metadata.ultramodernRenderer));
183
+ const worker = readWorkerFederationPublication(metadata[WORKER_FEDERATION_METADATA_KEY], renderer.identities);
184
+ if (worker.remote !== options.remote) throw new Error('Worker federation publication names another remote.');
185
+ if (!Object.hasOwn(worker.exposes, options.expose)) throw new Error(`Worker federation expose ${options.expose} is not published.`);
186
+ const coordinate = worker.exposes[options.expose];
187
+ if (!Object.hasOwn(coordinate.paths, options.locale)) throw new Error('Worker federation locale has no published endpoint.');
188
+ const path = coordinate.paths[options.locale];
189
+ const identity = readRendererIdentity(renderer.identities[coordinate.entryName], renderer.identities[coordinate.entryName]);
190
+ return Object.freeze({
191
+ authority: Object.freeze({
192
+ origin: url.origin,
193
+ renderer,
194
+ worker
195
+ }),
196
+ expose: options.expose,
197
+ locale: options.locale,
198
+ coordinate,
199
+ path,
200
+ identity,
201
+ endpoint: new URL(path, url.origin).href,
202
+ styles: Object.freeze([])
203
+ });
204
+ }
205
+ function workerFederationEnvelope(kind, admitted, payload) {
206
+ return {
207
+ schema: `ultramodern.worker-federation-${kind}`,
208
+ schemaVersion: 1,
209
+ authority: admitted.authority,
210
+ expose: admitted.expose,
211
+ locale: admitted.locale,
212
+ payload
213
+ };
214
+ }
215
+ function readWorkerFederationEnvelope(value, kind, admitted) {
216
+ const record = workerFederationRecord(value, `${kind} envelope`);
217
+ exactFields(record, [
218
+ 'schema',
219
+ 'schemaVersion',
220
+ 'authority',
221
+ 'expose',
222
+ 'locale',
223
+ 'payload'
224
+ ], `${kind} envelope`);
225
+ if (record.schema !== `ultramodern.worker-federation-${kind}` || 1 !== record.schemaVersion || record.expose !== admitted.expose || record.locale !== admitted.locale || workerFederationWireJson(record.authority) !== workerFederationWireJson(admitted.authority)) throw new Error(`Worker federation ${kind} conflicts with the admitted publication.`);
226
+ return record.payload;
227
+ }
228
+ export { WORKER_FEDERATION_METADATA_KEY, WORKER_FEDERATION_SCHEMA, WORKER_FEDERATION_VERSION, admitWorkerFederationExpose, readWorkerFederationEnvelope, readWorkerFederationManifestUrl, readWorkerFederationPath, readWorkerFederationPublication, readWorkerFederationStyles, workerFederationEnvelope, workerFederationRecord, workerFederationWireJson };