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

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,160 @@
1
+ import "node:module";
2
+ import { admitWorkerFederationExpose, readWorkerFederationEnvelope, readWorkerFederationPublication, workerFederationEnvelope, workerFederationRecord, workerFederationWireJson } from "./contract.mjs";
3
+ import { WorkerFederationBodyTooLargeError, assertWorkerFederationBodySize, checkWorkerFederationDeadline, readWorkerFederationJsonBody, workerAbortReason, workerAbortable, workerFederationErrorMessage } from "./lease.mjs";
4
+ function createWorkerFederationHandler(options) {
5
+ if ('function' != typeof options.admitRendererContract || 'function' != typeof options.admitNativeRequest || 'function' != typeof options.releaseNativeRequest || 'function' != typeof options.renderNativeSnapshot) throw new Error('Worker federation requires canonical and native admission callbacks.');
6
+ const timeout = options.requestTimeout ?? 3000;
7
+ if (!Number.isFinite(timeout) || timeout <= 0) throw new Error('Worker federation requires a positive request deadline.');
8
+ const renderer = options.admitRendererContract(options.publication.renderer);
9
+ const worker = readWorkerFederationPublication(options.publication.worker, renderer.identities);
10
+ const manifest = JSON.parse(workerFederationWireJson({
11
+ metaData: {
12
+ ultramodernRenderer: renderer,
13
+ ultramodernWorkerFederation: worker
14
+ }
15
+ }));
16
+ const loaders = workerFederationRecord(options.exposes, 'compiled exposes');
17
+ if (Object.keys(loaders).length !== Object.keys(worker.exposes).length || Object.keys(loaders).some((expose)=>!Object.hasOwn(worker.exposes, expose) || 'function' != typeof loaders[expose])) throw new Error('Worker federation registry conflicts with its own publication.');
18
+ const pinnedLoaders = {
19
+ ...options.exposes
20
+ };
21
+ const endpoints = new Map();
22
+ for (const [expose, coordinate] of Object.entries(worker.exposes))for (const locale of Object.keys(coordinate.paths)){
23
+ const admitted = admitWorkerFederationExpose({
24
+ manifest,
25
+ manifestUrl: options.manifestUrl,
26
+ remote: worker.remote,
27
+ expose,
28
+ locale,
29
+ admitRendererContract: options.admitRendererContract
30
+ });
31
+ endpoints.set(admitted.path, admitted);
32
+ }
33
+ const admitNativeRequest = options.admitNativeRequest;
34
+ const releaseNativeRequest = options.releaseNativeRequest;
35
+ const renderNativeSnapshot = options.renderNativeSnapshot;
36
+ return async (request)=>{
37
+ const deadlineAt = performance.now() + timeout;
38
+ const url = new URL(request.url);
39
+ const admitted = endpoints.get(url.pathname);
40
+ if (!admitted) return;
41
+ if ('POST' !== request.method || url.href !== admitted.endpoint || request.headers.get('content-type')?.split(';')[0].trim().toLowerCase() !== 'application/json') {
42
+ request.body?.cancel().catch(()=>void 0);
43
+ return new Response('Worker federation endpoint coordinate conflicts with its publication.', {
44
+ status: 400
45
+ });
46
+ }
47
+ if (request.signal.aborted) {
48
+ request.body?.cancel().catch(()=>void 0);
49
+ throw workerAbortReason(request.signal);
50
+ }
51
+ const controller = new AbortController();
52
+ const signal = AbortSignal.any([
53
+ request.signal,
54
+ controller.signal
55
+ ]);
56
+ const checkDeadline = ()=>checkWorkerFederationDeadline(deadlineAt, signal, controller);
57
+ const execution = Object.freeze({
58
+ admitted,
59
+ signal
60
+ });
61
+ const deadline = setTimeout(()=>{
62
+ controller.abort(new Error('Worker federation request deadline exceeded.'));
63
+ }, Math.max(0, deadlineAt - performance.now()));
64
+ let executing = false;
65
+ let admittedRequest;
66
+ let release;
67
+ let response;
68
+ let failedRequest;
69
+ const releaseOnce = (value)=>{
70
+ if (!release) try {
71
+ release = Promise.resolve(releaseNativeRequest(value));
72
+ } catch (error) {
73
+ release = Promise.reject(error);
74
+ }
75
+ return release ?? Promise.resolve();
76
+ };
77
+ try {
78
+ if (!request.body) throw new Error('Worker federation request has no native payload.');
79
+ const envelope = await readWorkerFederationJsonBody(request.body, signal, request.headers.get('content-length'), checkDeadline);
80
+ checkDeadline();
81
+ const payload = readWorkerFederationEnvelope(envelope, 'request', admitted);
82
+ checkDeadline();
83
+ const preparing = Promise.resolve().then(()=>{
84
+ checkDeadline();
85
+ return admitNativeRequest(payload, execution);
86
+ });
87
+ preparing.then((value)=>{
88
+ if (signal.aborted) releaseOnce(value).catch(()=>void 0);
89
+ }, ()=>void 0);
90
+ const nativeRequest = await workerAbortable(preparing, signal);
91
+ admittedRequest = {
92
+ value: nativeRequest
93
+ };
94
+ checkDeadline();
95
+ executing = true;
96
+ const module = await workerAbortable(pinnedLoaders[admitted.expose](), signal);
97
+ checkDeadline();
98
+ const snapshot = await workerAbortable(Promise.resolve(renderNativeSnapshot(module, nativeRequest, execution)), signal);
99
+ checkDeadline();
100
+ if ('string' == typeof snapshot) assertWorkerFederationBodySize(snapshot);
101
+ const body = assertWorkerFederationBodySize(workerFederationWireJson(workerFederationEnvelope('response', admitted, snapshot)));
102
+ checkDeadline();
103
+ response = new Response(body, {
104
+ headers: {
105
+ 'content-type': 'application/json'
106
+ }
107
+ });
108
+ } catch (error) {
109
+ let failure = error;
110
+ try {
111
+ checkDeadline();
112
+ } catch (deadlineError) {
113
+ failure = deadlineError;
114
+ }
115
+ failedRequest = {
116
+ error: failure
117
+ };
118
+ response = new Response(workerFederationErrorMessage(failure, 'Worker federation request failed.'), {
119
+ status: signal.aborted ? 504 : executing ? 502 : failure instanceof WorkerFederationBodyTooLargeError ? 413 : 400
120
+ });
121
+ } finally{
122
+ try {
123
+ if (admittedRequest) {
124
+ const releasing = releaseOnce(admittedRequest.value);
125
+ if (signal.aborted) releasing.catch(()=>void 0);
126
+ else try {
127
+ await workerAbortable(releasing, signal);
128
+ checkDeadline();
129
+ } catch (error) {
130
+ response?.body?.cancel().catch(()=>void 0);
131
+ let releaseFailure = error;
132
+ try {
133
+ checkDeadline();
134
+ } catch (deadlineError) {
135
+ releaseFailure = new AggregateError([
136
+ error,
137
+ deadlineError
138
+ ], `${workerFederationErrorMessage(error, 'Native request release failed.', 384)}; ${workerFederationErrorMessage(deadlineError, 'Worker federation deadline exceeded.', 384)}`);
139
+ }
140
+ const failure = failedRequest ? new AggregateError([
141
+ failedRequest.error,
142
+ releaseFailure
143
+ ], `${workerFederationErrorMessage(failedRequest.error, 'Worker federation request failed.', 384)}; native request release failed: ${workerFederationErrorMessage(releaseFailure, 'Native request release failed.', 384)}`) : releaseFailure;
144
+ response = new Response(workerFederationErrorMessage(failure, 'Worker federation native request release failed.'), {
145
+ status: signal.aborted ? 504 : 502
146
+ });
147
+ }
148
+ }
149
+ } finally{
150
+ clearTimeout(deadline);
151
+ }
152
+ }
153
+ if (request.signal.aborted) {
154
+ response?.body?.cancel().catch(()=>void 0);
155
+ throw workerAbortReason(request.signal);
156
+ }
157
+ return response;
158
+ };
159
+ }
160
+ export { createWorkerFederationHandler };
@@ -0,0 +1,4 @@
1
+ import "node:module";
2
+ export { WORKER_FEDERATION_METADATA_KEY, WORKER_FEDERATION_SCHEMA, WORKER_FEDERATION_VERSION, readWorkerFederationManifestUrl, readWorkerFederationPath, readWorkerFederationPublication } from "./contract.mjs";
3
+ export { createWorkerFederationHandler } from "./handler.mjs";
4
+ export { createWorkerFederationTransport } from "./transport.mjs";
@@ -0,0 +1,108 @@
1
+ import "node:module";
2
+ function checkWorkerFederationDeadline(deadlineAt, signal, controller) {
3
+ if (signal?.aborted) throw workerAbortReason(signal);
4
+ if (performance.now() >= deadlineAt) {
5
+ const error = new Error('Worker federation request deadline exceeded.');
6
+ controller?.abort(error);
7
+ throw error;
8
+ }
9
+ }
10
+ function workerFederationErrorMessage(error, fallback, maxLength = 1024) {
11
+ return error instanceof Error && error.message.length <= maxLength ? error.message : fallback;
12
+ }
13
+ function workerAbortReason(signal) {
14
+ return signal.reason ?? new DOMException('Worker federation request aborted.', 'AbortError');
15
+ }
16
+ function workerAbortable(operation, signal) {
17
+ if (signal.aborted) {
18
+ operation.catch(()=>void 0);
19
+ return Promise.reject(workerAbortReason(signal));
20
+ }
21
+ return new Promise((resolve, reject)=>{
22
+ const onAbort = ()=>reject(workerAbortReason(signal));
23
+ signal.addEventListener('abort', onAbort, {
24
+ once: true
25
+ });
26
+ operation.then(resolve, reject).finally(()=>signal.removeEventListener('abort', onAbort));
27
+ });
28
+ }
29
+ async function readWorkerFederationJsonBody(body, signal, contentLength, checkDeadline) {
30
+ if (signal.aborted) {
31
+ body.cancel().catch(()=>void 0);
32
+ throw workerAbortReason(signal);
33
+ }
34
+ try {
35
+ checkDeadline?.();
36
+ if (null != contentLength) {
37
+ if (!/^\d+$/u.test(contentLength)) throw new Error('Worker federation body has an invalid Content-Length.');
38
+ if (Number(contentLength) > WORKER_FEDERATION_MAX_BODY_BYTES) throw new WorkerFederationBodyTooLargeError();
39
+ }
40
+ } catch (error) {
41
+ body.cancel(error).catch(()=>void 0);
42
+ throw error;
43
+ }
44
+ const reader = body.getReader();
45
+ const onAbort = ()=>{
46
+ reader.cancel(workerAbortReason(signal)).catch(()=>void 0);
47
+ };
48
+ signal.addEventListener('abort', onAbort, {
49
+ once: true
50
+ });
51
+ try {
52
+ const chunks = [];
53
+ let length = 0;
54
+ while(true){
55
+ checkDeadline?.();
56
+ const chunk = await workerAbortable(reader.read(), signal);
57
+ checkDeadline?.();
58
+ if (chunk.done) break;
59
+ if (chunk.value.byteLength > WORKER_FEDERATION_MAX_BODY_BYTES - length) throw new WorkerFederationBodyTooLargeError();
60
+ chunks.push(new Uint8Array(chunk.value));
61
+ length += chunk.value.byteLength;
62
+ }
63
+ checkDeadline?.();
64
+ const bytes = new Uint8Array(length);
65
+ let offset = 0;
66
+ for (const chunk of chunks){
67
+ bytes.set(chunk, offset);
68
+ offset += chunk.byteLength;
69
+ }
70
+ if (signal.aborted) throw workerAbortReason(signal);
71
+ const decoded = new TextDecoder('utf-8', {
72
+ fatal: true
73
+ }).decode(bytes);
74
+ checkDeadline?.();
75
+ const result = JSON.parse(decoded);
76
+ checkDeadline?.();
77
+ return result;
78
+ } catch (error) {
79
+ reader.cancel(error).catch(()=>void 0);
80
+ throw error;
81
+ } finally{
82
+ signal.removeEventListener('abort', onAbort);
83
+ reader.releaseLock();
84
+ }
85
+ }
86
+ const WORKER_FEDERATION_MAX_BODY_BYTES = 8388608;
87
+ class WorkerFederationBodyTooLargeError extends Error {
88
+ constructor(){
89
+ super('Worker federation body exceeds 8 MiB.');
90
+ this.name = 'WorkerFederationBodyTooLargeError';
91
+ }
92
+ }
93
+ function assertWorkerFederationBodySize(value) {
94
+ if (value.length > WORKER_FEDERATION_MAX_BODY_BYTES) throw new WorkerFederationBodyTooLargeError();
95
+ let bytes = 0;
96
+ for(let index = 0; index < value.length; index++){
97
+ const code = value.charCodeAt(index);
98
+ if (code <= 0x7f) bytes++;
99
+ else if (code <= 0x7ff) bytes += 2;
100
+ else if (code >= 0xd800 && code <= 0xdbff && value.charCodeAt(index + 1) >= 0xdc00 && value.charCodeAt(index + 1) <= 0xdfff) {
101
+ bytes += 4;
102
+ index++;
103
+ } else bytes += 3;
104
+ if (bytes > WORKER_FEDERATION_MAX_BODY_BYTES) throw new WorkerFederationBodyTooLargeError();
105
+ }
106
+ return value;
107
+ }
108
+ export { WORKER_FEDERATION_MAX_BODY_BYTES, WorkerFederationBodyTooLargeError, assertWorkerFederationBodySize, checkWorkerFederationDeadline, readWorkerFederationJsonBody, workerAbortReason, workerAbortable, workerFederationErrorMessage };
@@ -0,0 +1,235 @@
1
+ import "node:module";
2
+ import { admitWorkerFederationExpose, readWorkerFederationEnvelope, readWorkerFederationManifestUrl, readWorkerFederationPath, readWorkerFederationStyles, workerFederationEnvelope, workerFederationWireJson } from "./contract.mjs";
3
+ import { assertWorkerFederationBodySize, checkWorkerFederationDeadline, readWorkerFederationJsonBody, workerAbortReason, workerAbortable } from "./lease.mjs";
4
+ async function readResponseJson(response, expectedUrl, signal, checkDeadline) {
5
+ const cancelBody = ()=>{
6
+ response.body?.cancel().catch(()=>void 0);
7
+ };
8
+ if (signal.aborted) {
9
+ cancelBody();
10
+ throw workerAbortReason(signal);
11
+ }
12
+ try {
13
+ checkDeadline();
14
+ } catch (error) {
15
+ cancelBody();
16
+ throw error;
17
+ }
18
+ if (!response.ok || response.redirected || response.url && response.url !== expectedUrl) {
19
+ cancelBody();
20
+ throw new Error(`Worker federation publication request failed (${response.status}).`);
21
+ }
22
+ if (!response.body) throw new Error('Worker federation response has no body.');
23
+ return readWorkerFederationJsonBody(response.body, signal, response.headers.get('content-length'), checkDeadline);
24
+ }
25
+ async function fetchJson(fetchResponse, request, signal, checkDeadline) {
26
+ checkDeadline();
27
+ if (signal.aborted) throw workerAbortReason(signal);
28
+ const pending = fetchResponse(request);
29
+ pending.then((response)=>{
30
+ if (signal.aborted) response.body?.cancel().catch(()=>void 0);
31
+ }, ()=>void 0);
32
+ return readResponseJson(await workerAbortable(pending, signal), request.url, signal, checkDeadline);
33
+ }
34
+ function createWorkerFederationTransport(options) {
35
+ const timeout = options.requestTimeout ?? 3000;
36
+ const admitRendererContract = options.admitRendererContract;
37
+ if ('function' != typeof admitRendererContract) throw new Error('Worker federation requires canonical renderer admission.');
38
+ if (!Number.isFinite(timeout) || timeout <= 0) throw new Error('Worker federation requires a positive request deadline.');
39
+ const services = new Map();
40
+ for (const service of options.services){
41
+ if (!service.remote || services.has(service.remote)) throw new Error('Worker federation service remotes must be unique.');
42
+ const exposes = new Set();
43
+ for (const fragment of service.fragments){
44
+ Object.values(fragment.paths).forEach(readWorkerFederationPath);
45
+ if (!fragment.boundaryId || exposes.has(fragment.expose)) throw new Error('Worker federation service exposes must be explicit and unique.');
46
+ exposes.add(fragment.expose);
47
+ }
48
+ const captured = {
49
+ ...service,
50
+ manifestUrl: readWorkerFederationManifestUrl(service.manifestUrl).href,
51
+ fetch: service.fetch.bind(service),
52
+ fragments: service.fragments.map((fragment)=>Object.freeze({
53
+ ...fragment,
54
+ paths: Object.freeze({
55
+ ...fragment.paths
56
+ })
57
+ }))
58
+ };
59
+ services.set(service.remote, captured);
60
+ if (service.alias && service.alias !== service.remote) {
61
+ if (services.has(service.alias)) throw new Error('Worker federation service aliases must be unique.');
62
+ services.set(service.alias, captured);
63
+ }
64
+ }
65
+ const snapshots = new Map();
66
+ const admissions = new Map();
67
+ const fetchManifest = options.fetchManifest;
68
+ let retired;
69
+ const onOwnerAbort = ()=>dispose(workerAbortReason(options.signal));
70
+ function checkDocumentLiveness() {
71
+ if (retired) throw retired.reason;
72
+ }
73
+ function dispose(reason = new DOMException('The Worker federation document was disposed', 'AbortError')) {
74
+ if (retired) return;
75
+ retired = {
76
+ reason
77
+ };
78
+ options.signal?.removeEventListener('abort', onOwnerAbort);
79
+ for (const pending of [
80
+ ...snapshots.values(),
81
+ ...admissions.values()
82
+ ])pending.controller.abort(reason);
83
+ snapshots.clear();
84
+ admissions.clear();
85
+ }
86
+ if (options.signal?.aborted) onOwnerAbort();
87
+ else options.signal?.addEventListener('abort', onOwnerAbort, {
88
+ once: true
89
+ });
90
+ function serviceCoordinate(remote, expose, locale) {
91
+ const service = services.get(remote);
92
+ const fragment = service?.fragments.find((value)=>value.expose === expose);
93
+ if (!service || !fragment || !Object.hasOwn(fragment.paths, locale)) throw new Error('Worker federation remote/expose has no configured service binding.');
94
+ return {
95
+ service,
96
+ fragment
97
+ };
98
+ }
99
+ async function shared(cache, key, operation, signal, deadlineAt = performance.now() + timeout, retainFulfilled = true) {
100
+ checkDocumentLiveness();
101
+ if (signal?.aborted) throw workerAbortReason(signal);
102
+ let pending = cache.get(key);
103
+ if (!pending) {
104
+ const controller = new AbortController();
105
+ const checkDeadline = ()=>checkWorkerFederationDeadline(deadlineAt, controller.signal, controller);
106
+ const deadline = setTimeout(()=>controller.abort(new Error('Worker federation request deadline exceeded.')), Math.max(0, deadlineAt - performance.now()));
107
+ const promise = operation(controller.signal, checkDeadline);
108
+ pending = {
109
+ controller,
110
+ promise,
111
+ waiters: 0,
112
+ settled: false
113
+ };
114
+ const captured = pending;
115
+ cache.set(key, captured);
116
+ promise.then(()=>{
117
+ captured.settled = true;
118
+ clearTimeout(deadline);
119
+ if (!retainFulfilled && cache.get(key) === captured) cache.delete(key);
120
+ }, ()=>{
121
+ captured.settled = true;
122
+ clearTimeout(deadline);
123
+ if (cache.get(key) === captured) cache.delete(key);
124
+ });
125
+ }
126
+ const captured = pending;
127
+ captured.waiters++;
128
+ try {
129
+ const value = await (signal ? workerAbortable(captured.promise, signal) : captured.promise);
130
+ checkDocumentLiveness();
131
+ checkWorkerFederationDeadline(deadlineAt, signal);
132
+ return value;
133
+ } finally{
134
+ captured.waiters--;
135
+ if (!captured.settled && 0 === captured.waiters) {
136
+ captured.controller.abort(new Error('Worker federation request has no active consumers.'));
137
+ if (cache.get(key) === captured) cache.delete(key);
138
+ }
139
+ }
140
+ }
141
+ function admitWithin({ remote, expose, locale, signal }, deadlineAt = performance.now() + timeout) {
142
+ const { service, fragment } = serviceCoordinate(remote, expose, locale);
143
+ const key = workerFederationWireJson([
144
+ service.remote,
145
+ expose,
146
+ locale
147
+ ]);
148
+ const promise = shared(admissions, key, async (signal, checkDeadline)=>{
149
+ checkDeadline();
150
+ const request = new Request(service.manifestUrl, {
151
+ redirect: 'manual',
152
+ signal
153
+ });
154
+ const manifest = await fetchJson(fetchManifest ?? service.fetch, request, signal, checkDeadline);
155
+ const styles = readWorkerFederationStyles({
156
+ manifest,
157
+ manifestUrl: request.url,
158
+ expose
159
+ });
160
+ const admitted = admitWorkerFederationExpose({
161
+ manifest,
162
+ manifestUrl: request.url,
163
+ remote: service.remote,
164
+ expose,
165
+ locale,
166
+ admitRendererContract
167
+ });
168
+ checkDeadline();
169
+ if (admitted.coordinate.boundaryId !== fragment.boundaryId || admitted.path !== fragment.paths[locale]) throw new Error('Worker federation publication conflicts with its service coordinate.');
170
+ return Object.freeze({
171
+ ...admitted,
172
+ styles
173
+ });
174
+ }, signal, deadlineAt);
175
+ return {
176
+ promise,
177
+ key,
178
+ pending: admissions.get(key)
179
+ };
180
+ }
181
+ return {
182
+ dispose,
183
+ admit: (options)=>admitWithin(options).promise,
184
+ async request ({ remote, expose, locale, payload, signal }) {
185
+ const deadlineAt = performance.now() + timeout;
186
+ checkDocumentLiveness();
187
+ if (signal?.aborted) throw workerAbortReason(signal);
188
+ const { service } = serviceCoordinate(remote, expose, locale);
189
+ if ('string' == typeof payload) assertWorkerFederationBodySize(payload);
190
+ const encodedPayload = assertWorkerFederationBodySize(workerFederationWireJson(payload));
191
+ const key = workerFederationWireJson([
192
+ service.remote,
193
+ expose,
194
+ locale,
195
+ encodedPayload
196
+ ]);
197
+ checkWorkerFederationDeadline(deadlineAt, signal);
198
+ return shared(snapshots, key, async (signal, checkDeadline)=>{
199
+ checkDeadline();
200
+ const admission = admitWithin({
201
+ remote,
202
+ expose,
203
+ locale,
204
+ signal
205
+ }, deadlineAt);
206
+ const admitted = await admission.promise;
207
+ checkDeadline();
208
+ try {
209
+ const body = assertWorkerFederationBodySize(workerFederationWireJson(workerFederationEnvelope('request', admitted, JSON.parse(encodedPayload))));
210
+ checkDeadline();
211
+ const response = await fetchJson(service.fetch, new Request(admitted.endpoint, {
212
+ method: 'POST',
213
+ headers: {
214
+ 'content-type': 'application/json'
215
+ },
216
+ body,
217
+ redirect: 'manual',
218
+ signal
219
+ }), signal, checkDeadline);
220
+ checkDeadline();
221
+ const decodedPayload = readWorkerFederationEnvelope(response, 'response', admitted);
222
+ checkDeadline();
223
+ return {
224
+ admitted,
225
+ payload: decodedPayload
226
+ };
227
+ } catch (error) {
228
+ if (!signal.aborted && admissions.get(admission.key) === admission.pending) admissions.delete(admission.key);
229
+ throw error;
230
+ }
231
+ }, signal, deadlineAt, false);
232
+ }
233
+ };
234
+ }
235
+ export { createWorkerFederationTransport };
@@ -10,7 +10,7 @@ export interface RendererBuildProfile<TRenderer extends Renderer = Renderer> {
10
10
  renderer: TRenderer;
11
11
  status: 'stable' | 'preview';
12
12
  protocolVersion: 1;
13
- minimumNode: '26.10.0';
13
+ minimumNode: '26.12.0';
14
14
  hmr: {
15
15
  editedBoundary: 'may-reset';
16
16
  unaffectedComponents: 'preserved';
@@ -1,4 +1,4 @@
1
- export declare const DATA_CODEC: 'seroval-json@1.6.8';
1
+ export declare const DATA_CODEC: 'seroval-json@1.6.9';
2
2
  export declare const MAX_DATA_BYTES: number;
3
3
  export declare class DataProtocolError extends Error {
4
4
  constructor(message: string);
@@ -40,6 +40,8 @@ export interface NativeClientStart<Bootstrap extends DocumentBootstrap, Router,
40
40
  export interface NativeClientAdapter<Bootstrap extends DocumentBootstrap, Router extends object, Instance extends NativeI18nInstance, LocalisedUrls> {
41
41
  readBootstrap(document: Document, identity: Readonly<RendererIdentity>): Bootstrap;
42
42
  createRouter(application: NativeRoutedApplication, options: NativeRouterOptions): Router;
43
+ /** Release the router and browser history created for this entry. */
44
+ disposeRouter(router: Router): void;
43
45
  /** Load a fresh router, or adopt the server's matches when hydrating. */
44
46
  prepareRouter(router: Router, hydrating: boolean, signal: AbortSignal): Promise<void>;
45
47
  /** Mount or hydrate the root; resolves its disposal. */
@@ -1,8 +1,14 @@
1
+ /** The Node dev runtime captures assets before releasing a container load. */
2
+ declare const LOAD_WITH_CAPTURE: unique symbol;
1
3
  /** The application compiler's native Module Federation runtime. */
2
4
  export interface FederationInstance {
3
5
  /** The application's federation name, unique among this realm's instances. */
4
6
  readonly name?: string;
5
7
  loadRemote<T>(id: string): Promise<T | null>;
8
+ [LOAD_WITH_CAPTURE]?: <T, Captured>(id: string, capture: (module: T | null, resolution?: LoadResolution) => Captured) => Promise<{
9
+ module: T | null;
10
+ captured: Captured | undefined;
11
+ }>;
6
12
  readonly remoteHandler?: {
7
13
  readonly idToRemoteMap?: Record<string, {
8
14
  name: string;
@@ -34,6 +40,10 @@ interface RemoteSnapshot {
34
40
  };
35
41
  }[];
36
42
  }
43
+ interface LoadResolution {
44
+ readonly remoteSnapshot: unknown;
45
+ readonly expose: string;
46
+ }
37
47
  /** Generated entries retain their own compiler's host, including on the server. */
38
48
  export interface NativeFederationBinding {
39
49
  readonly instance: () => FederationInstance;
@@ -50,7 +60,10 @@ export interface FederatedAssets {
50
60
  /** Capture the host before importing application modules or remote containers. */
51
61
  export declare function getFederationHost(binding: NativeFederationBinding | undefined): FederationInstance | undefined;
52
62
  /** Load through the selected application's runtime, never another app's host. */
53
- export declare function loadFederatedModule<T>(instance: FederationInstance | undefined, id: string): Promise<T>;
63
+ export declare function loadFederatedModule<T>(instance: FederationInstance | undefined, id: string): Promise<{
64
+ module: T;
65
+ assets: FederatedAssets | undefined;
66
+ }>;
54
67
  /** Browser assets from the native snapshot that admitted and loaded this remote. */
55
- export declare function remoteBrowserAssets(instance: FederationInstance, id: string): FederatedAssets | undefined;
68
+ export declare function remoteBrowserAssets(instance: FederationInstance, id: string, resolution?: LoadResolution): FederatedAssets | undefined;
56
69
  export {};
@@ -1,6 +1,7 @@
1
1
  import type { DocumentAsset } from '../document.js';
2
2
  import type { RendererIdentity } from '../identity.js';
3
3
  import type { RequestPlatform, RequestSession } from '../session/index.js';
4
+ import type { WorkerFederationTransport } from '../worker-federation/index.js';
4
5
  /** Native route matching remains with the selected router adapter. */
5
6
  export interface NativeServerConfig {
6
7
  readonly ssr?: boolean | 'stream';
@@ -15,6 +16,8 @@ export interface NativeRequestContext<Bindings extends object = object> {
15
16
  readonly nativeManifest?: unknown;
16
17
  readonly serverConfig?: NativeServerConfig;
17
18
  readonly entry: Readonly<RendererIdentity>;
19
+ /** Request-owned service transport for native Worker snapshot boundaries. */
20
+ readonly workerFederation?: WorkerFederationTransport;
18
21
  }
19
22
  export type NativeRequestHandler<Bindings extends object = object> = (request: Request, context: NativeRequestContext<Bindings>) => Response | Promise<Response>;
20
23
  /** Identity is generated with the bundle, never inferred from its filename. */
@@ -1,5 +1,6 @@
1
1
  import { type DocumentAsset } from '../document.js';
2
2
  import { type RendererIdentity } from '../identity.js';
3
+ import type { WorkerFederationPublication, WorkerFederationTransport } from '../worker-federation/index.js';
3
4
  import type { NativeExecutionContext, NativeServerConfig } from './types.js';
4
5
  /** Build-validated per-entry inputs a worker bundle cannot read from disk. */
5
6
  export interface NativeWorkerEntryResources {
@@ -11,6 +12,22 @@ export interface NativeWorkerEntryResources {
11
12
  /** Present only when the entry admits a CSR fallback request. */
12
13
  readonly csrFallbackHeader?: string;
13
14
  readonly nonce?: string;
15
+ readonly workerFederation?: {
16
+ readonly renderer: unknown;
17
+ readonly worker: WorkerFederationPublication;
18
+ };
19
+ readonly workerFederationManifestUrl?: string;
20
+ readonly workerFederationServices?: readonly {
21
+ readonly remote: string;
22
+ readonly alias?: string;
23
+ readonly manifestUrl: string;
24
+ readonly binding: string;
25
+ readonly fragments: readonly {
26
+ readonly expose: string;
27
+ readonly boundaryId: string;
28
+ readonly paths: Readonly<Record<string, string>>;
29
+ }[];
30
+ }[];
14
31
  }
15
32
  export interface NativeWorkerDispatchOptions<Bindings extends object> {
16
33
  readonly identity: RendererIdentity;
@@ -20,6 +37,8 @@ export interface NativeWorkerDispatchOptions<Bindings extends object> {
20
37
  /** The module worker `env`, forwarded unchanged as the platform binding. */
21
38
  readonly bindings: Bindings | undefined;
22
39
  readonly executionContext?: NativeExecutionContext;
40
+ /** Constructed by this compiled renderer entry for this document only. */
41
+ readonly workerFederation?: WorkerFederationTransport;
23
42
  }
24
43
  /** Worker Fetch dispatch over the same native handler the Node host loads. */
25
44
  export declare function dispatchNativeWorkerRequest<Bindings extends object>(request: Request, options: NativeWorkerDispatchOptions<Bindings>): Promise<Response>;