@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.
- package/dist/cjs/adapter.js +1 -1
- package/dist/cjs/data/codec.js +1 -1
- package/dist/cjs/entry-client.js +5 -0
- package/dist/cjs/federation.js +14 -5
- package/dist/cjs/server/worker.js +2 -1
- package/dist/cjs/worker-federation/contract.js +298 -0
- package/dist/cjs/worker-federation/handler.js +197 -0
- package/dist/cjs/worker-federation/index.js +63 -0
- package/dist/cjs/worker-federation/lease.js +167 -0
- package/dist/cjs/worker-federation/transport.js +272 -0
- package/dist/esm/adapter.mjs +1 -1
- package/dist/esm/data/codec.mjs +1 -1
- package/dist/esm/entry-client.mjs +5 -0
- package/dist/esm/federation.mjs +14 -5
- package/dist/esm/server/worker.mjs +2 -1
- package/dist/esm/worker-federation/contract.mjs +227 -0
- package/dist/esm/worker-federation/handler.mjs +159 -0
- package/dist/esm/worker-federation/index.mjs +3 -0
- package/dist/esm/worker-federation/lease.mjs +107 -0
- package/dist/esm/worker-federation/transport.mjs +234 -0
- package/dist/esm-node/adapter.mjs +1 -1
- package/dist/esm-node/data/codec.mjs +1 -1
- package/dist/esm-node/entry-client.mjs +5 -0
- package/dist/esm-node/federation.mjs +14 -5
- package/dist/esm-node/server/worker.mjs +2 -1
- package/dist/esm-node/worker-federation/contract.mjs +228 -0
- package/dist/esm-node/worker-federation/handler.mjs +160 -0
- package/dist/esm-node/worker-federation/index.mjs +4 -0
- package/dist/esm-node/worker-federation/lease.mjs +108 -0
- package/dist/esm-node/worker-federation/transport.mjs +235 -0
- package/dist/types/adapter.d.ts +1 -1
- package/dist/types/data/codec.d.ts +1 -1
- package/dist/types/entry-client.d.ts +2 -0
- package/dist/types/federation.d.ts +15 -2
- package/dist/types/server/types.d.ts +3 -0
- package/dist/types/server/worker.d.ts +19 -0
- package/dist/types/worker-federation/contract.d.ts +70 -0
- package/dist/types/worker-federation/handler.d.ts +21 -0
- package/dist/types/worker-federation/index.d.ts +6 -0
- package/dist/types/worker-federation/lease.d.ts +11 -0
- package/dist/types/worker-federation/transport.d.ts +47 -0
- package/package.json +17 -7
- package/src/adapter.ts +4 -2
- package/src/data/codec.ts +1 -1
- package/src/entry-client.ts +5 -0
- package/src/federation.ts +36 -8
- package/src/server/types.ts +3 -0
- package/src/server/worker.ts +23 -0
- package/src/worker-federation/contract.ts +449 -0
- package/src/worker-federation/handler.ts +274 -0
- package/src/worker-federation/index.ts +23 -0
- package/src/worker-federation/lease.ts +143 -0
- package/src/worker-federation/transport.ts +430 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AdmittedWorkerFederationExpose,
|
|
3
|
+
type AdmitWorkerRendererContract,
|
|
4
|
+
admitWorkerFederationExpose,
|
|
5
|
+
readWorkerFederationEnvelope,
|
|
6
|
+
readWorkerFederationPublication,
|
|
7
|
+
workerFederationEnvelope,
|
|
8
|
+
workerFederationRecord,
|
|
9
|
+
workerFederationWireJson,
|
|
10
|
+
} from './contract';
|
|
11
|
+
import {
|
|
12
|
+
assertWorkerFederationBodySize,
|
|
13
|
+
checkWorkerFederationDeadline,
|
|
14
|
+
readWorkerFederationJsonBody,
|
|
15
|
+
WorkerFederationBodyTooLargeError,
|
|
16
|
+
workerAbortable,
|
|
17
|
+
workerAbortReason,
|
|
18
|
+
workerFederationErrorMessage,
|
|
19
|
+
} from './lease';
|
|
20
|
+
|
|
21
|
+
export interface WorkerFederationExecution {
|
|
22
|
+
readonly admitted: AdmittedWorkerFederationExpose;
|
|
23
|
+
readonly signal: AbortSignal;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Bind once to the compiled registry and this Worker's own publication. */
|
|
27
|
+
export function createWorkerFederationHandler<NativeRequest>(options: {
|
|
28
|
+
publication: { readonly renderer: unknown; readonly worker: unknown };
|
|
29
|
+
manifestUrl: string;
|
|
30
|
+
exposes: Readonly<Record<string, () => Promise<unknown>>>;
|
|
31
|
+
admitRendererContract: AdmitWorkerRendererContract;
|
|
32
|
+
/** Native codec admission runs before even loading the exposed module. */
|
|
33
|
+
admitNativeRequest(
|
|
34
|
+
payload: unknown,
|
|
35
|
+
execution: WorkerFederationExecution,
|
|
36
|
+
): NativeRequest | Promise<NativeRequest>;
|
|
37
|
+
/** Close the admitted native lease, including a preparation resolving after abort. */
|
|
38
|
+
releaseNativeRequest(request: NativeRequest): void | Promise<void>;
|
|
39
|
+
renderNativeSnapshot(
|
|
40
|
+
module: unknown,
|
|
41
|
+
request: NativeRequest,
|
|
42
|
+
execution: WorkerFederationExecution,
|
|
43
|
+
): unknown | Promise<unknown>;
|
|
44
|
+
requestTimeout?: number;
|
|
45
|
+
}): (request: Request) => Promise<Response | undefined> {
|
|
46
|
+
if (
|
|
47
|
+
typeof options.admitRendererContract !== 'function' ||
|
|
48
|
+
typeof options.admitNativeRequest !== 'function' ||
|
|
49
|
+
typeof options.releaseNativeRequest !== 'function' ||
|
|
50
|
+
typeof options.renderNativeSnapshot !== 'function'
|
|
51
|
+
)
|
|
52
|
+
throw new Error(
|
|
53
|
+
'Worker federation requires canonical and native admission callbacks.',
|
|
54
|
+
);
|
|
55
|
+
const timeout = options.requestTimeout ?? 3000;
|
|
56
|
+
if (!Number.isFinite(timeout) || timeout <= 0)
|
|
57
|
+
throw new Error('Worker federation requires a positive request deadline.');
|
|
58
|
+
const renderer = options.admitRendererContract(options.publication.renderer);
|
|
59
|
+
const worker = readWorkerFederationPublication(
|
|
60
|
+
options.publication.worker,
|
|
61
|
+
renderer.identities,
|
|
62
|
+
);
|
|
63
|
+
const manifest = JSON.parse(
|
|
64
|
+
workerFederationWireJson({
|
|
65
|
+
metaData: {
|
|
66
|
+
ultramodernRenderer: renderer,
|
|
67
|
+
ultramodernWorkerFederation: worker,
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
const loaders = workerFederationRecord(options.exposes, 'compiled exposes');
|
|
72
|
+
if (
|
|
73
|
+
Object.keys(loaders).length !== Object.keys(worker.exposes).length ||
|
|
74
|
+
Object.keys(loaders).some(
|
|
75
|
+
expose =>
|
|
76
|
+
!Object.hasOwn(worker.exposes, expose) ||
|
|
77
|
+
typeof loaders[expose] !== 'function',
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
throw new Error(
|
|
81
|
+
'Worker federation registry conflicts with its own publication.',
|
|
82
|
+
);
|
|
83
|
+
const pinnedLoaders = { ...options.exposes };
|
|
84
|
+
const endpoints = new Map<string, AdmittedWorkerFederationExpose>();
|
|
85
|
+
for (const [expose, coordinate] of Object.entries(worker.exposes)) {
|
|
86
|
+
for (const locale of Object.keys(coordinate.paths)) {
|
|
87
|
+
const admitted = admitWorkerFederationExpose({
|
|
88
|
+
manifest,
|
|
89
|
+
manifestUrl: options.manifestUrl,
|
|
90
|
+
remote: worker.remote,
|
|
91
|
+
expose,
|
|
92
|
+
locale,
|
|
93
|
+
admitRendererContract: options.admitRendererContract,
|
|
94
|
+
});
|
|
95
|
+
endpoints.set(admitted.path, admitted);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const admitNativeRequest = options.admitNativeRequest;
|
|
99
|
+
const releaseNativeRequest = options.releaseNativeRequest;
|
|
100
|
+
const renderNativeSnapshot = options.renderNativeSnapshot;
|
|
101
|
+
|
|
102
|
+
return async request => {
|
|
103
|
+
const deadlineAt = performance.now() + timeout;
|
|
104
|
+
const url = new URL(request.url);
|
|
105
|
+
const admitted = endpoints.get(url.pathname);
|
|
106
|
+
if (!admitted) return undefined;
|
|
107
|
+
if (
|
|
108
|
+
request.method !== 'POST' ||
|
|
109
|
+
url.href !== admitted.endpoint ||
|
|
110
|
+
request.headers
|
|
111
|
+
.get('content-type')
|
|
112
|
+
?.split(';')[0]
|
|
113
|
+
.trim()
|
|
114
|
+
.toLowerCase() !== 'application/json'
|
|
115
|
+
) {
|
|
116
|
+
void request.body?.cancel().catch(() => undefined);
|
|
117
|
+
return new Response(
|
|
118
|
+
'Worker federation endpoint coordinate conflicts with its publication.',
|
|
119
|
+
{ status: 400 },
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
if (request.signal.aborted) {
|
|
123
|
+
void request.body?.cancel().catch(() => undefined);
|
|
124
|
+
throw workerAbortReason(request.signal);
|
|
125
|
+
}
|
|
126
|
+
const controller = new AbortController();
|
|
127
|
+
const signal = AbortSignal.any([request.signal, controller.signal]);
|
|
128
|
+
const checkDeadline = () =>
|
|
129
|
+
checkWorkerFederationDeadline(deadlineAt, signal, controller);
|
|
130
|
+
const execution = Object.freeze({ admitted, signal });
|
|
131
|
+
const deadline = setTimeout(
|
|
132
|
+
() => {
|
|
133
|
+
controller.abort(
|
|
134
|
+
new Error('Worker federation request deadline exceeded.'),
|
|
135
|
+
);
|
|
136
|
+
},
|
|
137
|
+
Math.max(0, deadlineAt - performance.now()),
|
|
138
|
+
);
|
|
139
|
+
let executing = false;
|
|
140
|
+
let admittedRequest: { value: NativeRequest } | undefined;
|
|
141
|
+
let release: Promise<void> | undefined;
|
|
142
|
+
let response: Response | undefined;
|
|
143
|
+
let failedRequest: { error: unknown } | undefined;
|
|
144
|
+
const releaseOnce = (value: NativeRequest): Promise<void> => {
|
|
145
|
+
if (!release) {
|
|
146
|
+
try {
|
|
147
|
+
release = Promise.resolve(releaseNativeRequest(value));
|
|
148
|
+
} catch (error) {
|
|
149
|
+
release = Promise.reject(error);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return release ?? Promise.resolve();
|
|
153
|
+
};
|
|
154
|
+
try {
|
|
155
|
+
if (!request.body)
|
|
156
|
+
throw new Error('Worker federation request has no native payload.');
|
|
157
|
+
const envelope = await readWorkerFederationJsonBody(
|
|
158
|
+
request.body,
|
|
159
|
+
signal,
|
|
160
|
+
request.headers.get('content-length'),
|
|
161
|
+
checkDeadline,
|
|
162
|
+
);
|
|
163
|
+
checkDeadline();
|
|
164
|
+
const payload = readWorkerFederationEnvelope(
|
|
165
|
+
envelope,
|
|
166
|
+
'request',
|
|
167
|
+
admitted,
|
|
168
|
+
);
|
|
169
|
+
checkDeadline();
|
|
170
|
+
const preparing = Promise.resolve().then(() => {
|
|
171
|
+
checkDeadline();
|
|
172
|
+
return admitNativeRequest(payload, execution);
|
|
173
|
+
});
|
|
174
|
+
void preparing.then(
|
|
175
|
+
value => {
|
|
176
|
+
if (signal.aborted) void releaseOnce(value).catch(() => undefined);
|
|
177
|
+
},
|
|
178
|
+
() => undefined,
|
|
179
|
+
);
|
|
180
|
+
const nativeRequest = await workerAbortable(preparing, signal);
|
|
181
|
+
admittedRequest = { value: nativeRequest };
|
|
182
|
+
checkDeadline();
|
|
183
|
+
executing = true;
|
|
184
|
+
const module = await workerAbortable(
|
|
185
|
+
pinnedLoaders[admitted.expose](),
|
|
186
|
+
signal,
|
|
187
|
+
);
|
|
188
|
+
checkDeadline();
|
|
189
|
+
const snapshot = await workerAbortable(
|
|
190
|
+
Promise.resolve(renderNativeSnapshot(module, nativeRequest, execution)),
|
|
191
|
+
signal,
|
|
192
|
+
);
|
|
193
|
+
checkDeadline();
|
|
194
|
+
if (typeof snapshot === 'string')
|
|
195
|
+
assertWorkerFederationBodySize(snapshot);
|
|
196
|
+
const body = assertWorkerFederationBodySize(
|
|
197
|
+
workerFederationWireJson(
|
|
198
|
+
workerFederationEnvelope('response', admitted, snapshot),
|
|
199
|
+
),
|
|
200
|
+
);
|
|
201
|
+
checkDeadline();
|
|
202
|
+
response = new Response(body, {
|
|
203
|
+
headers: { 'content-type': 'application/json' },
|
|
204
|
+
});
|
|
205
|
+
} catch (error) {
|
|
206
|
+
let failure = error;
|
|
207
|
+
try {
|
|
208
|
+
checkDeadline();
|
|
209
|
+
} catch (deadlineError) {
|
|
210
|
+
failure = deadlineError;
|
|
211
|
+
}
|
|
212
|
+
failedRequest = { error: failure };
|
|
213
|
+
response = new Response(
|
|
214
|
+
workerFederationErrorMessage(
|
|
215
|
+
failure,
|
|
216
|
+
'Worker federation request failed.',
|
|
217
|
+
),
|
|
218
|
+
{
|
|
219
|
+
status: signal.aborted
|
|
220
|
+
? 504
|
|
221
|
+
: executing
|
|
222
|
+
? 502
|
|
223
|
+
: failure instanceof WorkerFederationBodyTooLargeError
|
|
224
|
+
? 413
|
|
225
|
+
: 400,
|
|
226
|
+
},
|
|
227
|
+
);
|
|
228
|
+
} finally {
|
|
229
|
+
try {
|
|
230
|
+
if (admittedRequest) {
|
|
231
|
+
const releasing = releaseOnce(admittedRequest.value);
|
|
232
|
+
if (signal.aborted) void releasing.catch(() => undefined);
|
|
233
|
+
else {
|
|
234
|
+
try {
|
|
235
|
+
await workerAbortable(releasing, signal);
|
|
236
|
+
checkDeadline();
|
|
237
|
+
} catch (error) {
|
|
238
|
+
void response?.body?.cancel().catch(() => undefined);
|
|
239
|
+
let releaseFailure = error;
|
|
240
|
+
try {
|
|
241
|
+
checkDeadline();
|
|
242
|
+
} catch (deadlineError) {
|
|
243
|
+
releaseFailure = new AggregateError(
|
|
244
|
+
[error, deadlineError],
|
|
245
|
+
`${workerFederationErrorMessage(error, 'Native request release failed.', 384)}; ${workerFederationErrorMessage(deadlineError, 'Worker federation deadline exceeded.', 384)}`,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
const failure = failedRequest
|
|
249
|
+
? new AggregateError(
|
|
250
|
+
[failedRequest.error, releaseFailure],
|
|
251
|
+
`${workerFederationErrorMessage(failedRequest.error, 'Worker federation request failed.', 384)}; native request release failed: ${workerFederationErrorMessage(releaseFailure, 'Native request release failed.', 384)}`,
|
|
252
|
+
)
|
|
253
|
+
: releaseFailure;
|
|
254
|
+
response = new Response(
|
|
255
|
+
workerFederationErrorMessage(
|
|
256
|
+
failure,
|
|
257
|
+
'Worker federation native request release failed.',
|
|
258
|
+
),
|
|
259
|
+
{ status: signal.aborted ? 504 : 502 },
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
} finally {
|
|
265
|
+
clearTimeout(deadline);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (request.signal.aborted) {
|
|
269
|
+
void response?.body?.cancel().catch(() => undefined);
|
|
270
|
+
throw workerAbortReason(request.signal);
|
|
271
|
+
}
|
|
272
|
+
return response;
|
|
273
|
+
};
|
|
274
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
AdmittedWorkerFederationExpose,
|
|
3
|
+
AdmitWorkerRendererContract,
|
|
4
|
+
WorkerFederationAuthority,
|
|
5
|
+
WorkerFederationExpose,
|
|
6
|
+
WorkerFederationPublication,
|
|
7
|
+
} from './contract';
|
|
8
|
+
export {
|
|
9
|
+
readWorkerFederationManifestUrl,
|
|
10
|
+
readWorkerFederationPath,
|
|
11
|
+
readWorkerFederationPublication,
|
|
12
|
+
WORKER_FEDERATION_METADATA_KEY,
|
|
13
|
+
WORKER_FEDERATION_SCHEMA,
|
|
14
|
+
WORKER_FEDERATION_VERSION,
|
|
15
|
+
} from './contract';
|
|
16
|
+
export type { WorkerFederationExecution } from './handler';
|
|
17
|
+
export { createWorkerFederationHandler } from './handler';
|
|
18
|
+
export type {
|
|
19
|
+
WorkerFederationService,
|
|
20
|
+
WorkerFederationSnapshot,
|
|
21
|
+
WorkerFederationTransport,
|
|
22
|
+
} from './transport';
|
|
23
|
+
export { createWorkerFederationTransport } from './transport';
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
export function checkWorkerFederationDeadline(
|
|
2
|
+
deadlineAt: number,
|
|
3
|
+
signal?: AbortSignal,
|
|
4
|
+
controller?: AbortController,
|
|
5
|
+
): void {
|
|
6
|
+
if (signal?.aborted) throw workerAbortReason(signal);
|
|
7
|
+
if (performance.now() >= deadlineAt) {
|
|
8
|
+
const error = new Error('Worker federation request deadline exceeded.');
|
|
9
|
+
controller?.abort(error);
|
|
10
|
+
throw error;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function workerFederationErrorMessage(
|
|
15
|
+
error: unknown,
|
|
16
|
+
fallback: string,
|
|
17
|
+
maxLength = 1024,
|
|
18
|
+
): string {
|
|
19
|
+
return error instanceof Error && error.message.length <= maxLength
|
|
20
|
+
? error.message
|
|
21
|
+
: fallback;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function workerAbortReason(signal: AbortSignal): unknown {
|
|
25
|
+
return (
|
|
26
|
+
signal.reason ??
|
|
27
|
+
new DOMException('Worker federation request aborted.', 'AbortError')
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function workerAbortable<T>(
|
|
32
|
+
operation: Promise<T>,
|
|
33
|
+
signal: AbortSignal,
|
|
34
|
+
): Promise<T> {
|
|
35
|
+
if (signal.aborted) {
|
|
36
|
+
void operation.catch(() => undefined);
|
|
37
|
+
return Promise.reject(workerAbortReason(signal));
|
|
38
|
+
}
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const onAbort = () => reject(workerAbortReason(signal));
|
|
41
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
42
|
+
operation
|
|
43
|
+
.then(resolve, reject)
|
|
44
|
+
.finally(() => signal.removeEventListener('abort', onAbort));
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function readWorkerFederationJsonBody(
|
|
49
|
+
body: ReadableStream<Uint8Array>,
|
|
50
|
+
signal: AbortSignal,
|
|
51
|
+
contentLength?: string | null,
|
|
52
|
+
checkDeadline?: () => void,
|
|
53
|
+
): Promise<unknown> {
|
|
54
|
+
if (signal.aborted) {
|
|
55
|
+
void body.cancel().catch(() => undefined);
|
|
56
|
+
throw workerAbortReason(signal);
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
checkDeadline?.();
|
|
60
|
+
if (contentLength != null) {
|
|
61
|
+
if (!/^\d+$/u.test(contentLength))
|
|
62
|
+
throw new Error(
|
|
63
|
+
'Worker federation body has an invalid Content-Length.',
|
|
64
|
+
);
|
|
65
|
+
if (Number(contentLength) > WORKER_FEDERATION_MAX_BODY_BYTES)
|
|
66
|
+
throw new WorkerFederationBodyTooLargeError();
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
void body.cancel(error).catch(() => undefined);
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
const reader = body.getReader();
|
|
73
|
+
const onAbort = () => {
|
|
74
|
+
void reader.cancel(workerAbortReason(signal)).catch(() => undefined);
|
|
75
|
+
};
|
|
76
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
77
|
+
try {
|
|
78
|
+
const chunks: Uint8Array[] = [];
|
|
79
|
+
let length = 0;
|
|
80
|
+
while (true) {
|
|
81
|
+
checkDeadline?.();
|
|
82
|
+
const chunk = await workerAbortable(reader.read(), signal);
|
|
83
|
+
checkDeadline?.();
|
|
84
|
+
if (chunk.done) break;
|
|
85
|
+
if (chunk.value.byteLength > WORKER_FEDERATION_MAX_BODY_BYTES - length)
|
|
86
|
+
throw new WorkerFederationBodyTooLargeError();
|
|
87
|
+
// A small view must not retain an unbounded upstream backing buffer.
|
|
88
|
+
chunks.push(new Uint8Array(chunk.value));
|
|
89
|
+
length += chunk.value.byteLength;
|
|
90
|
+
}
|
|
91
|
+
checkDeadline?.();
|
|
92
|
+
const bytes = new Uint8Array(length);
|
|
93
|
+
let offset = 0;
|
|
94
|
+
for (const chunk of chunks) {
|
|
95
|
+
bytes.set(chunk, offset);
|
|
96
|
+
offset += chunk.byteLength;
|
|
97
|
+
}
|
|
98
|
+
if (signal.aborted) throw workerAbortReason(signal);
|
|
99
|
+
const decoded = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
100
|
+
checkDeadline?.();
|
|
101
|
+
const result: unknown = JSON.parse(decoded);
|
|
102
|
+
checkDeadline?.();
|
|
103
|
+
return result;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
void reader.cancel(error).catch(() => undefined);
|
|
106
|
+
throw error;
|
|
107
|
+
} finally {
|
|
108
|
+
signal.removeEventListener('abort', onAbort);
|
|
109
|
+
reader.releaseLock();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export const WORKER_FEDERATION_MAX_BODY_BYTES = 8 * 1024 * 1024;
|
|
113
|
+
|
|
114
|
+
export class WorkerFederationBodyTooLargeError extends Error {
|
|
115
|
+
constructor() {
|
|
116
|
+
super('Worker federation body exceeds 8 MiB.');
|
|
117
|
+
this.name = 'WorkerFederationBodyTooLargeError';
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Count UTF-8 bytes without allocating a second encoded body. */
|
|
122
|
+
export function assertWorkerFederationBodySize(value: string): string {
|
|
123
|
+
if (value.length > WORKER_FEDERATION_MAX_BODY_BYTES)
|
|
124
|
+
throw new WorkerFederationBodyTooLargeError();
|
|
125
|
+
let bytes = 0;
|
|
126
|
+
for (let index = 0; index < value.length; index++) {
|
|
127
|
+
const code = value.charCodeAt(index);
|
|
128
|
+
if (code <= 0x7f) bytes++;
|
|
129
|
+
else if (code <= 0x7ff) bytes += 2;
|
|
130
|
+
else if (
|
|
131
|
+
code >= 0xd800 &&
|
|
132
|
+
code <= 0xdbff &&
|
|
133
|
+
value.charCodeAt(index + 1) >= 0xdc00 &&
|
|
134
|
+
value.charCodeAt(index + 1) <= 0xdfff
|
|
135
|
+
) {
|
|
136
|
+
bytes += 4;
|
|
137
|
+
index++;
|
|
138
|
+
} else bytes += 3;
|
|
139
|
+
if (bytes > WORKER_FEDERATION_MAX_BODY_BYTES)
|
|
140
|
+
throw new WorkerFederationBodyTooLargeError();
|
|
141
|
+
}
|
|
142
|
+
return value;
|
|
143
|
+
}
|