@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,227 @@
1
+ import { identityCacheKey, readRendererIdentity } from "../identity.mjs";
2
+ const WORKER_FEDERATION_METADATA_KEY = 'ultramodernWorkerFederation';
3
+ const WORKER_FEDERATION_SCHEMA = 'ultramodern.worker-federation';
4
+ const WORKER_FEDERATION_VERSION = 1;
5
+ function workerFederationRecord(value, label) {
6
+ if (!value || 'object' != typeof value || ![
7
+ Object.prototype,
8
+ null
9
+ ].includes(Object.getPrototypeOf(value))) throw new Error(`Worker federation ${label} must be a plain record.`);
10
+ return value;
11
+ }
12
+ function exactFields(value, fields, label) {
13
+ if (Object.keys(value).length !== fields.length || Object.keys(value).some((key)=>!fields.includes(key))) throw new Error(`Worker federation ${label} has unsupported fields.`);
14
+ }
15
+ function requiredString(value, label) {
16
+ if ('string' != typeof value || 0 === value.length || value.trim() !== value || /[\u0000-\u001f\u007f]/u.test(value)) throw new Error(`Worker federation requires ${label}.`);
17
+ return value;
18
+ }
19
+ function readWorkerFederationPath(value) {
20
+ const path = requiredString(value, 'an endpoint path');
21
+ let decoded;
22
+ try {
23
+ decoded = decodeURIComponent(path);
24
+ } catch {
25
+ throw new Error('Worker federation endpoint path has invalid encoding.');
26
+ }
27
+ 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.');
28
+ return path;
29
+ }
30
+ function readWorkerFederationManifestUrl(value) {
31
+ const url = new URL(value);
32
+ if (![
33
+ 'http:',
34
+ 'https:'
35
+ ].includes(url.protocol) || url.username || url.password || url.hash) throw new Error('Worker federation manifest requires an HTTP(S) publication URL.');
36
+ return url;
37
+ }
38
+ function readWorkerFederationPublication(value, identities) {
39
+ const record = workerFederationRecord(value, 'publication');
40
+ exactFields(record, [
41
+ 'schema',
42
+ 'schemaVersion',
43
+ 'remote',
44
+ 'exposes'
45
+ ], 'publication');
46
+ if (record.schema !== WORKER_FEDERATION_SCHEMA || record.schemaVersion !== WORKER_FEDERATION_VERSION) throw new Error('Unsupported Worker federation publication protocol.');
47
+ const remote = requiredString(record.remote, 'a remote');
48
+ const entries = workerFederationRecord(record.exposes, 'exposes');
49
+ if (0 === Object.keys(entries).length) throw new Error('Worker federation publication has no exposes.');
50
+ const paths = new Set();
51
+ const exposes = Object.fromEntries(Object.entries(entries).map(([expose, raw])=>{
52
+ 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}.`);
53
+ const coordinate = workerFederationRecord(raw, 'expose coordinate');
54
+ exactFields(coordinate, [
55
+ 'entryName',
56
+ 'boundaryId',
57
+ 'paths',
58
+ 'native'
59
+ ], 'expose coordinate');
60
+ const entryName = requiredString(coordinate.entryName, 'an entry name');
61
+ if (!Object.hasOwn(identities, entryName)) throw new Error(`Worker federation expose ${expose} has no built entry.`);
62
+ const identity = identities[entryName];
63
+ identityCacheKey(identity);
64
+ if (identity.entryName !== entryName) throw new Error('Worker federation entry identity conflicts with its key.');
65
+ const rawPaths = workerFederationRecord(coordinate.paths, 'locale paths');
66
+ const native = workerFederationRecord(coordinate.native, 'native authority');
67
+ exactFields(native, [
68
+ 'publisherBuildId',
69
+ 'runtimeABI'
70
+ ], 'native authority');
71
+ if (1 !== native.runtimeABI) throw new Error('Unsupported native Worker federation runtime ABI.');
72
+ if (0 === Object.keys(rawPaths).length) throw new Error('Worker federation expose has no locale endpoints.');
73
+ const locales = Object.keys(rawPaths);
74
+ 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.');
75
+ const localePaths = Object.fromEntries(Object.entries(rawPaths).map(([locale, rawPath])=>{
76
+ if (!/^[a-zA-Z0-9]+(?:[-_][a-zA-Z0-9]+)*$/u.test(locale)) throw new Error('Worker federation endpoint has an invalid locale.');
77
+ const path = readWorkerFederationPath(rawPath);
78
+ if (paths.has(path)) throw new Error('Worker federation exposes have conflicting endpoint paths.');
79
+ paths.add(path);
80
+ return [
81
+ locale,
82
+ path
83
+ ];
84
+ }));
85
+ return [
86
+ expose,
87
+ Object.freeze({
88
+ entryName,
89
+ boundaryId: requiredString(coordinate.boundaryId, 'a boundary ID'),
90
+ paths: Object.freeze(localePaths),
91
+ native: Object.freeze({
92
+ publisherBuildId: requiredString(native.publisherBuildId, 'a native publisher build ID'),
93
+ runtimeABI: 1
94
+ })
95
+ })
96
+ ];
97
+ }));
98
+ return Object.freeze({
99
+ schema: WORKER_FEDERATION_SCHEMA,
100
+ schemaVersion: WORKER_FEDERATION_VERSION,
101
+ remote,
102
+ exposes: Object.freeze(exposes)
103
+ });
104
+ }
105
+ function workerFederationWireJson(value) {
106
+ const encode = (input)=>{
107
+ if (null === input || 'string' == typeof input || 'boolean' == typeof input) return input;
108
+ if ('number' == typeof input && Number.isFinite(input)) return input;
109
+ if (Array.isArray(input)) return input.map(encode);
110
+ const record = workerFederationRecord(input, 'wire value');
111
+ return Object.fromEntries(Object.keys(record).toSorted().map((key)=>[
112
+ key,
113
+ encode(record[key])
114
+ ]));
115
+ };
116
+ return JSON.stringify(encode(value));
117
+ }
118
+ function freezeWireValue(value) {
119
+ const copy = JSON.parse(workerFederationWireJson(value));
120
+ const freeze = (input)=>{
121
+ if (input && 'object' == typeof input) {
122
+ Object.values(input).forEach(freeze);
123
+ Object.freeze(input);
124
+ }
125
+ };
126
+ freeze(copy);
127
+ return copy;
128
+ }
129
+ function readWorkerFederationStyles(options) {
130
+ const manifest = workerFederationRecord(options.manifest, 'manifest');
131
+ const metadata = workerFederationRecord(manifest.metaData, 'manifest metadata');
132
+ const manifestUrl = readWorkerFederationManifestUrl(options.manifestUrl);
133
+ if (!Array.isArray(manifest.exposes)) throw new Error('Worker federation manifest exposes must be an array.');
134
+ const selected = manifest.exposes.map((value)=>workerFederationRecord(value, 'manifest expose')).filter((value)=>value.path === options.expose);
135
+ if (1 !== selected.length) throw new Error('Worker federation styles require one exact manifest expose.');
136
+ if (void 0 === selected[0].assets) return Object.freeze([]);
137
+ const assets = workerFederationRecord(selected[0].assets, 'expose assets');
138
+ if (void 0 === assets.css) return Object.freeze([]);
139
+ const css = workerFederationRecord(assets.css, 'expose CSS assets');
140
+ const files = [
141
+ 'sync',
142
+ 'async'
143
+ ].flatMap((kind)=>{
144
+ const values = css[kind];
145
+ if (void 0 === values) return [];
146
+ if (!Array.isArray(values)) throw new Error('Worker federation CSS assets must be string arrays.');
147
+ return values.map((value)=>requiredString(value, 'a CSS asset URL'));
148
+ });
149
+ if (0 === files.length) return Object.freeze([]);
150
+ 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.');
151
+ const publicPath = requiredString(metadata.publicPath, 'a completed public path');
152
+ const explicitScheme = /^[a-z][a-z0-9+.-]*:/iu;
153
+ const isHttpScheme = /^https?:/iu;
154
+ const assertScheme = (value)=>{
155
+ if (explicitScheme.test(value) && !isHttpScheme.test(value)) throw new Error('Worker federation styles require an HTTP(S) publication URL.');
156
+ };
157
+ assertScheme(publicPath);
158
+ const publicUrl = new URL(publicPath, manifestUrl);
159
+ if (![
160
+ 'http:',
161
+ 'https:'
162
+ ].includes(publicUrl.protocol) || publicUrl.username || publicUrl.password || publicUrl.hash) throw new Error('Worker federation styles require an HTTP(S) publication URL.');
163
+ const styles = files.map((file)=>{
164
+ assertScheme(file);
165
+ const resolved = /^(?:https?:)?\/\//iu.test(file) ? file : `${publicPath.replace(/\/$/u, '')}/${file.replace(/^\//u, '')}`;
166
+ const url = new URL(resolved, manifestUrl);
167
+ if (![
168
+ 'http:',
169
+ 'https:'
170
+ ].includes(url.protocol) || url.username || url.password || url.hash) throw new Error('Worker federation styles require an HTTP(S) publication URL.');
171
+ return resolved;
172
+ });
173
+ return Object.freeze([
174
+ ...new Set(styles)
175
+ ]);
176
+ }
177
+ function admitWorkerFederationExpose(options) {
178
+ const url = readWorkerFederationManifestUrl(options.manifestUrl);
179
+ const manifest = workerFederationRecord(options.manifest, 'manifest');
180
+ const metadata = workerFederationRecord(manifest.metaData, 'manifest metadata');
181
+ const renderer = freezeWireValue(options.admitRendererContract(metadata.ultramodernRenderer));
182
+ const worker = readWorkerFederationPublication(metadata[WORKER_FEDERATION_METADATA_KEY], renderer.identities);
183
+ if (worker.remote !== options.remote) throw new Error('Worker federation publication names another remote.');
184
+ if (!Object.hasOwn(worker.exposes, options.expose)) throw new Error(`Worker federation expose ${options.expose} is not published.`);
185
+ const coordinate = worker.exposes[options.expose];
186
+ if (!Object.hasOwn(coordinate.paths, options.locale)) throw new Error('Worker federation locale has no published endpoint.');
187
+ const path = coordinate.paths[options.locale];
188
+ const identity = readRendererIdentity(renderer.identities[coordinate.entryName], renderer.identities[coordinate.entryName]);
189
+ return Object.freeze({
190
+ authority: Object.freeze({
191
+ origin: url.origin,
192
+ renderer,
193
+ worker
194
+ }),
195
+ expose: options.expose,
196
+ locale: options.locale,
197
+ coordinate,
198
+ path,
199
+ identity,
200
+ endpoint: new URL(path, url.origin).href,
201
+ styles: Object.freeze([])
202
+ });
203
+ }
204
+ function workerFederationEnvelope(kind, admitted, payload) {
205
+ return {
206
+ schema: `ultramodern.worker-federation-${kind}`,
207
+ schemaVersion: 1,
208
+ authority: admitted.authority,
209
+ expose: admitted.expose,
210
+ locale: admitted.locale,
211
+ payload
212
+ };
213
+ }
214
+ function readWorkerFederationEnvelope(value, kind, admitted) {
215
+ const record = workerFederationRecord(value, `${kind} envelope`);
216
+ exactFields(record, [
217
+ 'schema',
218
+ 'schemaVersion',
219
+ 'authority',
220
+ 'expose',
221
+ 'locale',
222
+ 'payload'
223
+ ], `${kind} envelope`);
224
+ 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.`);
225
+ return record.payload;
226
+ }
227
+ export { WORKER_FEDERATION_METADATA_KEY, WORKER_FEDERATION_SCHEMA, WORKER_FEDERATION_VERSION, admitWorkerFederationExpose, readWorkerFederationEnvelope, readWorkerFederationManifestUrl, readWorkerFederationPath, readWorkerFederationPublication, readWorkerFederationStyles, workerFederationEnvelope, workerFederationRecord, workerFederationWireJson };
@@ -0,0 +1,159 @@
1
+ import { admitWorkerFederationExpose, readWorkerFederationEnvelope, readWorkerFederationPublication, workerFederationEnvelope, workerFederationRecord, workerFederationWireJson } from "./contract.mjs";
2
+ import { WorkerFederationBodyTooLargeError, assertWorkerFederationBodySize, checkWorkerFederationDeadline, readWorkerFederationJsonBody, workerAbortReason, workerAbortable, workerFederationErrorMessage } from "./lease.mjs";
3
+ function createWorkerFederationHandler(options) {
4
+ 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.');
5
+ const timeout = options.requestTimeout ?? 3000;
6
+ if (!Number.isFinite(timeout) || timeout <= 0) throw new Error('Worker federation requires a positive request deadline.');
7
+ const renderer = options.admitRendererContract(options.publication.renderer);
8
+ const worker = readWorkerFederationPublication(options.publication.worker, renderer.identities);
9
+ const manifest = JSON.parse(workerFederationWireJson({
10
+ metaData: {
11
+ ultramodernRenderer: renderer,
12
+ ultramodernWorkerFederation: worker
13
+ }
14
+ }));
15
+ const loaders = workerFederationRecord(options.exposes, 'compiled exposes');
16
+ 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.');
17
+ const pinnedLoaders = {
18
+ ...options.exposes
19
+ };
20
+ const endpoints = new Map();
21
+ for (const [expose, coordinate] of Object.entries(worker.exposes))for (const locale of Object.keys(coordinate.paths)){
22
+ const admitted = admitWorkerFederationExpose({
23
+ manifest,
24
+ manifestUrl: options.manifestUrl,
25
+ remote: worker.remote,
26
+ expose,
27
+ locale,
28
+ admitRendererContract: options.admitRendererContract
29
+ });
30
+ endpoints.set(admitted.path, admitted);
31
+ }
32
+ const admitNativeRequest = options.admitNativeRequest;
33
+ const releaseNativeRequest = options.releaseNativeRequest;
34
+ const renderNativeSnapshot = options.renderNativeSnapshot;
35
+ return async (request)=>{
36
+ const deadlineAt = performance.now() + timeout;
37
+ const url = new URL(request.url);
38
+ const admitted = endpoints.get(url.pathname);
39
+ if (!admitted) return;
40
+ if ('POST' !== request.method || url.href !== admitted.endpoint || request.headers.get('content-type')?.split(';')[0].trim().toLowerCase() !== 'application/json') {
41
+ request.body?.cancel().catch(()=>void 0);
42
+ return new Response('Worker federation endpoint coordinate conflicts with its publication.', {
43
+ status: 400
44
+ });
45
+ }
46
+ if (request.signal.aborted) {
47
+ request.body?.cancel().catch(()=>void 0);
48
+ throw workerAbortReason(request.signal);
49
+ }
50
+ const controller = new AbortController();
51
+ const signal = AbortSignal.any([
52
+ request.signal,
53
+ controller.signal
54
+ ]);
55
+ const checkDeadline = ()=>checkWorkerFederationDeadline(deadlineAt, signal, controller);
56
+ const execution = Object.freeze({
57
+ admitted,
58
+ signal
59
+ });
60
+ const deadline = setTimeout(()=>{
61
+ controller.abort(new Error('Worker federation request deadline exceeded.'));
62
+ }, Math.max(0, deadlineAt - performance.now()));
63
+ let executing = false;
64
+ let admittedRequest;
65
+ let release;
66
+ let response;
67
+ let failedRequest;
68
+ const releaseOnce = (value)=>{
69
+ if (!release) try {
70
+ release = Promise.resolve(releaseNativeRequest(value));
71
+ } catch (error) {
72
+ release = Promise.reject(error);
73
+ }
74
+ return release ?? Promise.resolve();
75
+ };
76
+ try {
77
+ if (!request.body) throw new Error('Worker federation request has no native payload.');
78
+ const envelope = await readWorkerFederationJsonBody(request.body, signal, request.headers.get('content-length'), checkDeadline);
79
+ checkDeadline();
80
+ const payload = readWorkerFederationEnvelope(envelope, 'request', admitted);
81
+ checkDeadline();
82
+ const preparing = Promise.resolve().then(()=>{
83
+ checkDeadline();
84
+ return admitNativeRequest(payload, execution);
85
+ });
86
+ preparing.then((value)=>{
87
+ if (signal.aborted) releaseOnce(value).catch(()=>void 0);
88
+ }, ()=>void 0);
89
+ const nativeRequest = await workerAbortable(preparing, signal);
90
+ admittedRequest = {
91
+ value: nativeRequest
92
+ };
93
+ checkDeadline();
94
+ executing = true;
95
+ const module = await workerAbortable(pinnedLoaders[admitted.expose](), signal);
96
+ checkDeadline();
97
+ const snapshot = await workerAbortable(Promise.resolve(renderNativeSnapshot(module, nativeRequest, execution)), signal);
98
+ checkDeadline();
99
+ if ('string' == typeof snapshot) assertWorkerFederationBodySize(snapshot);
100
+ const body = assertWorkerFederationBodySize(workerFederationWireJson(workerFederationEnvelope('response', admitted, snapshot)));
101
+ checkDeadline();
102
+ response = new Response(body, {
103
+ headers: {
104
+ 'content-type': 'application/json'
105
+ }
106
+ });
107
+ } catch (error) {
108
+ let failure = error;
109
+ try {
110
+ checkDeadline();
111
+ } catch (deadlineError) {
112
+ failure = deadlineError;
113
+ }
114
+ failedRequest = {
115
+ error: failure
116
+ };
117
+ response = new Response(workerFederationErrorMessage(failure, 'Worker federation request failed.'), {
118
+ status: signal.aborted ? 504 : executing ? 502 : failure instanceof WorkerFederationBodyTooLargeError ? 413 : 400
119
+ });
120
+ } finally{
121
+ try {
122
+ if (admittedRequest) {
123
+ const releasing = releaseOnce(admittedRequest.value);
124
+ if (signal.aborted) releasing.catch(()=>void 0);
125
+ else try {
126
+ await workerAbortable(releasing, signal);
127
+ checkDeadline();
128
+ } catch (error) {
129
+ response?.body?.cancel().catch(()=>void 0);
130
+ let releaseFailure = error;
131
+ try {
132
+ checkDeadline();
133
+ } catch (deadlineError) {
134
+ releaseFailure = new AggregateError([
135
+ error,
136
+ deadlineError
137
+ ], `${workerFederationErrorMessage(error, 'Native request release failed.', 384)}; ${workerFederationErrorMessage(deadlineError, 'Worker federation deadline exceeded.', 384)}`);
138
+ }
139
+ const failure = failedRequest ? new AggregateError([
140
+ failedRequest.error,
141
+ releaseFailure
142
+ ], `${workerFederationErrorMessage(failedRequest.error, 'Worker federation request failed.', 384)}; native request release failed: ${workerFederationErrorMessage(releaseFailure, 'Native request release failed.', 384)}`) : releaseFailure;
143
+ response = new Response(workerFederationErrorMessage(failure, 'Worker federation native request release failed.'), {
144
+ status: signal.aborted ? 504 : 502
145
+ });
146
+ }
147
+ }
148
+ } finally{
149
+ clearTimeout(deadline);
150
+ }
151
+ }
152
+ if (request.signal.aborted) {
153
+ response?.body?.cancel().catch(()=>void 0);
154
+ throw workerAbortReason(request.signal);
155
+ }
156
+ return response;
157
+ };
158
+ }
159
+ export { createWorkerFederationHandler };
@@ -0,0 +1,3 @@
1
+ export { WORKER_FEDERATION_METADATA_KEY, WORKER_FEDERATION_SCHEMA, WORKER_FEDERATION_VERSION, readWorkerFederationManifestUrl, readWorkerFederationPath, readWorkerFederationPublication } from "./contract.mjs";
2
+ export { createWorkerFederationHandler } from "./handler.mjs";
3
+ export { createWorkerFederationTransport } from "./transport.mjs";
@@ -0,0 +1,107 @@
1
+ function checkWorkerFederationDeadline(deadlineAt, signal, controller) {
2
+ if (signal?.aborted) throw workerAbortReason(signal);
3
+ if (performance.now() >= deadlineAt) {
4
+ const error = new Error('Worker federation request deadline exceeded.');
5
+ controller?.abort(error);
6
+ throw error;
7
+ }
8
+ }
9
+ function workerFederationErrorMessage(error, fallback, maxLength = 1024) {
10
+ return error instanceof Error && error.message.length <= maxLength ? error.message : fallback;
11
+ }
12
+ function workerAbortReason(signal) {
13
+ return signal.reason ?? new DOMException('Worker federation request aborted.', 'AbortError');
14
+ }
15
+ function workerAbortable(operation, signal) {
16
+ if (signal.aborted) {
17
+ operation.catch(()=>void 0);
18
+ return Promise.reject(workerAbortReason(signal));
19
+ }
20
+ return new Promise((resolve, reject)=>{
21
+ const onAbort = ()=>reject(workerAbortReason(signal));
22
+ signal.addEventListener('abort', onAbort, {
23
+ once: true
24
+ });
25
+ operation.then(resolve, reject).finally(()=>signal.removeEventListener('abort', onAbort));
26
+ });
27
+ }
28
+ async function readWorkerFederationJsonBody(body, signal, contentLength, checkDeadline) {
29
+ if (signal.aborted) {
30
+ body.cancel().catch(()=>void 0);
31
+ throw workerAbortReason(signal);
32
+ }
33
+ try {
34
+ checkDeadline?.();
35
+ if (null != contentLength) {
36
+ if (!/^\d+$/u.test(contentLength)) throw new Error('Worker federation body has an invalid Content-Length.');
37
+ if (Number(contentLength) > WORKER_FEDERATION_MAX_BODY_BYTES) throw new WorkerFederationBodyTooLargeError();
38
+ }
39
+ } catch (error) {
40
+ body.cancel(error).catch(()=>void 0);
41
+ throw error;
42
+ }
43
+ const reader = body.getReader();
44
+ const onAbort = ()=>{
45
+ reader.cancel(workerAbortReason(signal)).catch(()=>void 0);
46
+ };
47
+ signal.addEventListener('abort', onAbort, {
48
+ once: true
49
+ });
50
+ try {
51
+ const chunks = [];
52
+ let length = 0;
53
+ while(true){
54
+ checkDeadline?.();
55
+ const chunk = await workerAbortable(reader.read(), signal);
56
+ checkDeadline?.();
57
+ if (chunk.done) break;
58
+ if (chunk.value.byteLength > WORKER_FEDERATION_MAX_BODY_BYTES - length) throw new WorkerFederationBodyTooLargeError();
59
+ chunks.push(new Uint8Array(chunk.value));
60
+ length += chunk.value.byteLength;
61
+ }
62
+ checkDeadline?.();
63
+ const bytes = new Uint8Array(length);
64
+ let offset = 0;
65
+ for (const chunk of chunks){
66
+ bytes.set(chunk, offset);
67
+ offset += chunk.byteLength;
68
+ }
69
+ if (signal.aborted) throw workerAbortReason(signal);
70
+ const decoded = new TextDecoder('utf-8', {
71
+ fatal: true
72
+ }).decode(bytes);
73
+ checkDeadline?.();
74
+ const result = JSON.parse(decoded);
75
+ checkDeadline?.();
76
+ return result;
77
+ } catch (error) {
78
+ reader.cancel(error).catch(()=>void 0);
79
+ throw error;
80
+ } finally{
81
+ signal.removeEventListener('abort', onAbort);
82
+ reader.releaseLock();
83
+ }
84
+ }
85
+ const WORKER_FEDERATION_MAX_BODY_BYTES = 8388608;
86
+ class WorkerFederationBodyTooLargeError extends Error {
87
+ constructor(){
88
+ super('Worker federation body exceeds 8 MiB.');
89
+ this.name = 'WorkerFederationBodyTooLargeError';
90
+ }
91
+ }
92
+ function assertWorkerFederationBodySize(value) {
93
+ if (value.length > WORKER_FEDERATION_MAX_BODY_BYTES) throw new WorkerFederationBodyTooLargeError();
94
+ let bytes = 0;
95
+ for(let index = 0; index < value.length; index++){
96
+ const code = value.charCodeAt(index);
97
+ if (code <= 0x7f) bytes++;
98
+ else if (code <= 0x7ff) bytes += 2;
99
+ else if (code >= 0xd800 && code <= 0xdbff && value.charCodeAt(index + 1) >= 0xdc00 && value.charCodeAt(index + 1) <= 0xdfff) {
100
+ bytes += 4;
101
+ index++;
102
+ } else bytes += 3;
103
+ if (bytes > WORKER_FEDERATION_MAX_BODY_BYTES) throw new WorkerFederationBodyTooLargeError();
104
+ }
105
+ return value;
106
+ }
107
+ export { WORKER_FEDERATION_MAX_BODY_BYTES, WorkerFederationBodyTooLargeError, assertWorkerFederationBodySize, checkWorkerFederationDeadline, readWorkerFederationJsonBody, workerAbortReason, workerAbortable, workerFederationErrorMessage };