@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.24 → 3.9.0-ultramodern.26
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/templates/cloudflare-entry.001-bootstrap-security.mjs +3 -1
- package/dist/cjs/templates/cloudflare-entry.005-worker-dispatch.mjs +105 -91
- package/dist/cjs/templates/cloudflare-entry.006-fetch-handler.mjs +1 -1
- package/dist/esm/templates/cloudflare-entry.001-bootstrap-security.mjs +3 -1
- package/dist/esm/templates/cloudflare-entry.005-worker-dispatch.mjs +105 -91
- package/dist/esm/templates/cloudflare-entry.006-fetch-handler.mjs +1 -1
- package/dist/esm-node/templates/cloudflare-entry.001-bootstrap-security.mjs +3 -1
- package/dist/esm-node/templates/cloudflare-entry.005-worker-dispatch.mjs +105 -91
- package/dist/esm-node/templates/cloudflare-entry.006-fetch-handler.mjs +1 -1
- package/package.json +15 -15
- package/src/templates/cloudflare-entry.001-bootstrap-security.mjs +3 -1
- package/src/templates/cloudflare-entry.005-worker-dispatch.mjs +105 -91
- package/src/templates/cloudflare-entry.006-fetch-handler.mjs +1 -1
|
@@ -3,7 +3,6 @@ const MODERN_WORKER_MANIFEST = p_workerManifest;
|
|
|
3
3
|
export const modernWorkerManifest = MODERN_WORKER_MANIFEST;
|
|
4
4
|
const WORKER_MODULE_LOADERS = p_workerModuleLoaders;
|
|
5
5
|
const workerModulePromises = new Map();
|
|
6
|
-
const effectBffDispatcherPromises = new Map();
|
|
7
6
|
const remoteJsonPromises = new Map();
|
|
8
7
|
const CORS_POLICY = MODERN_WORKER_MANIFEST.security?.cors || {};
|
|
9
8
|
const ASSET_CORS_ENABLED = CORS_POLICY.assets !== false;
|
|
@@ -239,6 +238,9 @@ function finalizeResponseForRequest(response, request) {
|
|
|
239
238
|
|
|
240
239
|
const headers = new Headers(securedResponse.headers);
|
|
241
240
|
headers.delete('content-length');
|
|
241
|
+
// A HEAD response sends no body, so release the producer's stream instead of abandoning it:
|
|
242
|
+
// a stream nobody reads never finishes, and whatever waits for its end never runs.
|
|
243
|
+
securedResponse.body?.cancel().catch(() => undefined);
|
|
242
244
|
|
|
243
245
|
return new Response(null, {
|
|
244
246
|
headers,
|
|
@@ -237,109 +237,112 @@ function createEffectBffDispatcherErrorResponse(bff, error) {
|
|
|
237
237
|
);
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
240
|
+
// workerd binds every I/O object (sockets, timers, pending promises) to the request that
|
|
241
|
+
// created it, so an Effect runtime built while serving one request cannot serve the next: a
|
|
242
|
+
// pooled PostgreSQL connection or a cached in-flight effect from an earlier request never
|
|
243
|
+
// completes. Each BFF request therefore builds its own dispatcher and disposes it once the
|
|
244
|
+
// response body has been delivered; platform pools such as Hyperdrive keep connections warm.
|
|
245
|
+
async function createEffectBffDispatcher(bff, runtime) {
|
|
246
|
+
if (
|
|
247
|
+
typeof bff.dispatcherExport !== 'string' ||
|
|
248
|
+
bff.dispatcherExport.length === 0
|
|
249
|
+
) {
|
|
250
|
+
throw new Error('manifest does not declare dispatcherExport');
|
|
245
251
|
}
|
|
246
252
|
|
|
247
|
-
|
|
248
|
-
if (
|
|
249
|
-
typeof bff.dispatcherExport !== 'string' ||
|
|
250
|
-
bff.dispatcherExport.length === 0
|
|
251
|
-
) {
|
|
252
|
-
throw new Error('manifest does not declare dispatcherExport');
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const effectDispatcherFactory = runtime[bff.dispatcherExport];
|
|
253
|
+
const effectDispatcherFactory = runtime[bff.dispatcherExport];
|
|
256
254
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
255
|
+
if (typeof effectDispatcherFactory !== 'function') {
|
|
256
|
+
throw new Error(`worker bundle does not export ${bff.dispatcherExport}`);
|
|
257
|
+
}
|
|
260
258
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
throw new Error(
|
|
290
|
-
`manifest Effect BFF cross-project policy requires boolean ${field}`,
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
if (
|
|
295
|
-
!crossProjectPolicy.expectedOperationContracts ||
|
|
296
|
-
typeof crossProjectPolicy.expectedOperationContracts !== 'object' ||
|
|
297
|
-
Array.isArray(crossProjectPolicy.expectedOperationContracts)
|
|
298
|
-
) {
|
|
259
|
+
const effectConfig = bff.effect;
|
|
260
|
+
if (
|
|
261
|
+
!effectConfig ||
|
|
262
|
+
typeof effectConfig !== 'object' ||
|
|
263
|
+
Array.isArray(effectConfig)
|
|
264
|
+
) {
|
|
265
|
+
throw new Error('manifest declares invalid Effect BFF runtime config');
|
|
266
|
+
}
|
|
267
|
+
const crossProjectPolicy = effectConfig.crossProjectPolicy;
|
|
268
|
+
if (
|
|
269
|
+
!crossProjectPolicy ||
|
|
270
|
+
typeof crossProjectPolicy !== 'object' ||
|
|
271
|
+
Array.isArray(crossProjectPolicy)
|
|
272
|
+
) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
'manifest declares invalid Effect BFF cross-project policy',
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
for (const field of [
|
|
278
|
+
'enabled',
|
|
279
|
+
'requireEnvelope',
|
|
280
|
+
'requireOperationContext',
|
|
281
|
+
'requireOperationContextDetails',
|
|
282
|
+
'requireOperationSchemaHash',
|
|
283
|
+
'requireOperationVersion',
|
|
284
|
+
'allowUnknownOperations',
|
|
285
|
+
]) {
|
|
286
|
+
if (typeof crossProjectPolicy[field] !== 'boolean') {
|
|
299
287
|
throw new Error(
|
|
300
|
-
|
|
288
|
+
`manifest Effect BFF cross-project policy requires boolean ${field}`,
|
|
301
289
|
);
|
|
302
290
|
}
|
|
291
|
+
}
|
|
292
|
+
if (
|
|
293
|
+
!crossProjectPolicy.expectedOperationContracts ||
|
|
294
|
+
typeof crossProjectPolicy.expectedOperationContracts !== 'object' ||
|
|
295
|
+
Array.isArray(crossProjectPolicy.expectedOperationContracts)
|
|
296
|
+
) {
|
|
297
|
+
throw new Error(
|
|
298
|
+
'manifest Effect BFF cross-project policy requires expectedOperationContracts object',
|
|
299
|
+
);
|
|
300
|
+
}
|
|
303
301
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
if (!effectDispatcher || typeof effectDispatcher.dispatch !== 'function') {
|
|
318
|
-
try {
|
|
319
|
-
await effectDispatcher?.dispose?.();
|
|
320
|
-
} catch {}
|
|
302
|
+
const effectDispatcher = await effectDispatcherFactory({
|
|
303
|
+
prefix: bff.prefix,
|
|
304
|
+
...(effectConfig?.openapi === undefined
|
|
305
|
+
? {}
|
|
306
|
+
: { openapi: effectConfig.openapi }),
|
|
307
|
+
...(effectConfig?.dataPlatform === undefined
|
|
308
|
+
? {}
|
|
309
|
+
: { dataPlatform: effectConfig.dataPlatform }),
|
|
310
|
+
...(effectConfig?.crossProjectPolicy === undefined
|
|
311
|
+
? {}
|
|
312
|
+
: { crossProjectPolicy }),
|
|
313
|
+
});
|
|
321
314
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
);
|
|
325
|
-
}
|
|
315
|
+
if (!effectDispatcher || typeof effectDispatcher.dispatch !== 'function') {
|
|
316
|
+
try {
|
|
317
|
+
await effectDispatcher?.dispose?.();
|
|
318
|
+
} catch {}
|
|
326
319
|
|
|
327
|
-
|
|
328
|
-
|
|
320
|
+
throw new Error(
|
|
321
|
+
`worker export ${bff.dispatcherExport} did not return a dispatcher with a dispatch function`,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
329
324
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (
|
|
333
|
-
effectBffDispatcherPromises.get(bff.worker) === effectDispatcherPromise
|
|
334
|
-
) {
|
|
335
|
-
effectBffDispatcherPromises.delete(bff.worker);
|
|
336
|
-
}
|
|
337
|
-
});
|
|
325
|
+
return effectDispatcher;
|
|
326
|
+
}
|
|
338
327
|
|
|
339
|
-
|
|
328
|
+
function disposeEffectBffDispatcherAfterResponse(response, dispatcher, ctx) {
|
|
329
|
+
if (response.body === null) {
|
|
330
|
+
ctx.waitUntil(dispatcher.dispose());
|
|
331
|
+
return response;
|
|
332
|
+
}
|
|
333
|
+
const { readable, writable } = new TransformStream();
|
|
334
|
+
// The body pipe settles when the client has the whole body or the stream failed; a failure
|
|
335
|
+
// already reached the client through `readable`, so it only has to release the runtime here.
|
|
336
|
+
ctx.waitUntil(
|
|
337
|
+
response.body
|
|
338
|
+
.pipeTo(writable)
|
|
339
|
+
.catch(() => undefined)
|
|
340
|
+
.then(() => dispatcher.dispose()),
|
|
341
|
+
);
|
|
342
|
+
return new Response(readable, response);
|
|
340
343
|
}
|
|
341
344
|
|
|
342
|
-
async function dispatchBffRequest(request, env) {
|
|
345
|
+
async function dispatchBffRequest(request, env, ctx) {
|
|
343
346
|
const bff = MODERN_WORKER_MANIFEST.bff;
|
|
344
347
|
|
|
345
348
|
const requestUrl = new URL(request.url);
|
|
@@ -384,12 +387,23 @@ async function dispatchBffRequest(request, env) {
|
|
|
384
387
|
let effectDispatcher;
|
|
385
388
|
|
|
386
389
|
try {
|
|
387
|
-
effectDispatcher = await
|
|
390
|
+
effectDispatcher = await createEffectBffDispatcher(bff, runtime);
|
|
388
391
|
} catch (error) {
|
|
389
392
|
return createEffectBffDispatcherErrorResponse(bff, error);
|
|
390
393
|
}
|
|
391
394
|
|
|
392
|
-
|
|
395
|
+
let response;
|
|
396
|
+
try {
|
|
397
|
+
response = await effectDispatcher.dispatch(canonicalRequest, { env });
|
|
398
|
+
} catch (error) {
|
|
399
|
+
ctx.waitUntil(effectDispatcher.dispose());
|
|
400
|
+
throw error;
|
|
401
|
+
}
|
|
402
|
+
return disposeEffectBffDispatcherAfterResponse(
|
|
403
|
+
response,
|
|
404
|
+
effectDispatcher,
|
|
405
|
+
ctx,
|
|
406
|
+
);
|
|
393
407
|
}
|
|
394
408
|
|
|
395
409
|
const directHandler =
|
|
@@ -9,7 +9,7 @@ export default {
|
|
|
9
9
|
return finalizeResponseForRequest(corsPreflightResponse, request);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
const bffResponse = await dispatchBffRequest(request, env);
|
|
12
|
+
const bffResponse = await dispatchBffRequest(request, env, ctx);
|
|
13
13
|
|
|
14
14
|
if (bffResponse) {
|
|
15
15
|
return finalizeResponseForRequest(
|
|
@@ -3,7 +3,6 @@ const MODERN_WORKER_MANIFEST = p_workerManifest;
|
|
|
3
3
|
export const modernWorkerManifest = MODERN_WORKER_MANIFEST;
|
|
4
4
|
const WORKER_MODULE_LOADERS = p_workerModuleLoaders;
|
|
5
5
|
const workerModulePromises = new Map();
|
|
6
|
-
const effectBffDispatcherPromises = new Map();
|
|
7
6
|
const remoteJsonPromises = new Map();
|
|
8
7
|
const CORS_POLICY = MODERN_WORKER_MANIFEST.security?.cors || {};
|
|
9
8
|
const ASSET_CORS_ENABLED = CORS_POLICY.assets !== false;
|
|
@@ -239,6 +238,9 @@ function finalizeResponseForRequest(response, request) {
|
|
|
239
238
|
|
|
240
239
|
const headers = new Headers(securedResponse.headers);
|
|
241
240
|
headers.delete('content-length');
|
|
241
|
+
// A HEAD response sends no body, so release the producer's stream instead of abandoning it:
|
|
242
|
+
// a stream nobody reads never finishes, and whatever waits for its end never runs.
|
|
243
|
+
securedResponse.body?.cancel().catch(() => undefined);
|
|
242
244
|
|
|
243
245
|
return new Response(null, {
|
|
244
246
|
headers,
|
|
@@ -237,109 +237,112 @@ function createEffectBffDispatcherErrorResponse(bff, error) {
|
|
|
237
237
|
);
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
240
|
+
// workerd binds every I/O object (sockets, timers, pending promises) to the request that
|
|
241
|
+
// created it, so an Effect runtime built while serving one request cannot serve the next: a
|
|
242
|
+
// pooled PostgreSQL connection or a cached in-flight effect from an earlier request never
|
|
243
|
+
// completes. Each BFF request therefore builds its own dispatcher and disposes it once the
|
|
244
|
+
// response body has been delivered; platform pools such as Hyperdrive keep connections warm.
|
|
245
|
+
async function createEffectBffDispatcher(bff, runtime) {
|
|
246
|
+
if (
|
|
247
|
+
typeof bff.dispatcherExport !== 'string' ||
|
|
248
|
+
bff.dispatcherExport.length === 0
|
|
249
|
+
) {
|
|
250
|
+
throw new Error('manifest does not declare dispatcherExport');
|
|
245
251
|
}
|
|
246
252
|
|
|
247
|
-
|
|
248
|
-
if (
|
|
249
|
-
typeof bff.dispatcherExport !== 'string' ||
|
|
250
|
-
bff.dispatcherExport.length === 0
|
|
251
|
-
) {
|
|
252
|
-
throw new Error('manifest does not declare dispatcherExport');
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const effectDispatcherFactory = runtime[bff.dispatcherExport];
|
|
253
|
+
const effectDispatcherFactory = runtime[bff.dispatcherExport];
|
|
256
254
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
255
|
+
if (typeof effectDispatcherFactory !== 'function') {
|
|
256
|
+
throw new Error(`worker bundle does not export ${bff.dispatcherExport}`);
|
|
257
|
+
}
|
|
260
258
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
throw new Error(
|
|
290
|
-
`manifest Effect BFF cross-project policy requires boolean ${field}`,
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
if (
|
|
295
|
-
!crossProjectPolicy.expectedOperationContracts ||
|
|
296
|
-
typeof crossProjectPolicy.expectedOperationContracts !== 'object' ||
|
|
297
|
-
Array.isArray(crossProjectPolicy.expectedOperationContracts)
|
|
298
|
-
) {
|
|
259
|
+
const effectConfig = bff.effect;
|
|
260
|
+
if (
|
|
261
|
+
!effectConfig ||
|
|
262
|
+
typeof effectConfig !== 'object' ||
|
|
263
|
+
Array.isArray(effectConfig)
|
|
264
|
+
) {
|
|
265
|
+
throw new Error('manifest declares invalid Effect BFF runtime config');
|
|
266
|
+
}
|
|
267
|
+
const crossProjectPolicy = effectConfig.crossProjectPolicy;
|
|
268
|
+
if (
|
|
269
|
+
!crossProjectPolicy ||
|
|
270
|
+
typeof crossProjectPolicy !== 'object' ||
|
|
271
|
+
Array.isArray(crossProjectPolicy)
|
|
272
|
+
) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
'manifest declares invalid Effect BFF cross-project policy',
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
for (const field of [
|
|
278
|
+
'enabled',
|
|
279
|
+
'requireEnvelope',
|
|
280
|
+
'requireOperationContext',
|
|
281
|
+
'requireOperationContextDetails',
|
|
282
|
+
'requireOperationSchemaHash',
|
|
283
|
+
'requireOperationVersion',
|
|
284
|
+
'allowUnknownOperations',
|
|
285
|
+
]) {
|
|
286
|
+
if (typeof crossProjectPolicy[field] !== 'boolean') {
|
|
299
287
|
throw new Error(
|
|
300
|
-
|
|
288
|
+
`manifest Effect BFF cross-project policy requires boolean ${field}`,
|
|
301
289
|
);
|
|
302
290
|
}
|
|
291
|
+
}
|
|
292
|
+
if (
|
|
293
|
+
!crossProjectPolicy.expectedOperationContracts ||
|
|
294
|
+
typeof crossProjectPolicy.expectedOperationContracts !== 'object' ||
|
|
295
|
+
Array.isArray(crossProjectPolicy.expectedOperationContracts)
|
|
296
|
+
) {
|
|
297
|
+
throw new Error(
|
|
298
|
+
'manifest Effect BFF cross-project policy requires expectedOperationContracts object',
|
|
299
|
+
);
|
|
300
|
+
}
|
|
303
301
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
if (!effectDispatcher || typeof effectDispatcher.dispatch !== 'function') {
|
|
318
|
-
try {
|
|
319
|
-
await effectDispatcher?.dispose?.();
|
|
320
|
-
} catch {}
|
|
302
|
+
const effectDispatcher = await effectDispatcherFactory({
|
|
303
|
+
prefix: bff.prefix,
|
|
304
|
+
...(effectConfig?.openapi === undefined
|
|
305
|
+
? {}
|
|
306
|
+
: { openapi: effectConfig.openapi }),
|
|
307
|
+
...(effectConfig?.dataPlatform === undefined
|
|
308
|
+
? {}
|
|
309
|
+
: { dataPlatform: effectConfig.dataPlatform }),
|
|
310
|
+
...(effectConfig?.crossProjectPolicy === undefined
|
|
311
|
+
? {}
|
|
312
|
+
: { crossProjectPolicy }),
|
|
313
|
+
});
|
|
321
314
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
);
|
|
325
|
-
}
|
|
315
|
+
if (!effectDispatcher || typeof effectDispatcher.dispatch !== 'function') {
|
|
316
|
+
try {
|
|
317
|
+
await effectDispatcher?.dispose?.();
|
|
318
|
+
} catch {}
|
|
326
319
|
|
|
327
|
-
|
|
328
|
-
|
|
320
|
+
throw new Error(
|
|
321
|
+
`worker export ${bff.dispatcherExport} did not return a dispatcher with a dispatch function`,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
329
324
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (
|
|
333
|
-
effectBffDispatcherPromises.get(bff.worker) === effectDispatcherPromise
|
|
334
|
-
) {
|
|
335
|
-
effectBffDispatcherPromises.delete(bff.worker);
|
|
336
|
-
}
|
|
337
|
-
});
|
|
325
|
+
return effectDispatcher;
|
|
326
|
+
}
|
|
338
327
|
|
|
339
|
-
|
|
328
|
+
function disposeEffectBffDispatcherAfterResponse(response, dispatcher, ctx) {
|
|
329
|
+
if (response.body === null) {
|
|
330
|
+
ctx.waitUntil(dispatcher.dispose());
|
|
331
|
+
return response;
|
|
332
|
+
}
|
|
333
|
+
const { readable, writable } = new TransformStream();
|
|
334
|
+
// The body pipe settles when the client has the whole body or the stream failed; a failure
|
|
335
|
+
// already reached the client through `readable`, so it only has to release the runtime here.
|
|
336
|
+
ctx.waitUntil(
|
|
337
|
+
response.body
|
|
338
|
+
.pipeTo(writable)
|
|
339
|
+
.catch(() => undefined)
|
|
340
|
+
.then(() => dispatcher.dispose()),
|
|
341
|
+
);
|
|
342
|
+
return new Response(readable, response);
|
|
340
343
|
}
|
|
341
344
|
|
|
342
|
-
async function dispatchBffRequest(request, env) {
|
|
345
|
+
async function dispatchBffRequest(request, env, ctx) {
|
|
343
346
|
const bff = MODERN_WORKER_MANIFEST.bff;
|
|
344
347
|
|
|
345
348
|
const requestUrl = new URL(request.url);
|
|
@@ -384,12 +387,23 @@ async function dispatchBffRequest(request, env) {
|
|
|
384
387
|
let effectDispatcher;
|
|
385
388
|
|
|
386
389
|
try {
|
|
387
|
-
effectDispatcher = await
|
|
390
|
+
effectDispatcher = await createEffectBffDispatcher(bff, runtime);
|
|
388
391
|
} catch (error) {
|
|
389
392
|
return createEffectBffDispatcherErrorResponse(bff, error);
|
|
390
393
|
}
|
|
391
394
|
|
|
392
|
-
|
|
395
|
+
let response;
|
|
396
|
+
try {
|
|
397
|
+
response = await effectDispatcher.dispatch(canonicalRequest, { env });
|
|
398
|
+
} catch (error) {
|
|
399
|
+
ctx.waitUntil(effectDispatcher.dispose());
|
|
400
|
+
throw error;
|
|
401
|
+
}
|
|
402
|
+
return disposeEffectBffDispatcherAfterResponse(
|
|
403
|
+
response,
|
|
404
|
+
effectDispatcher,
|
|
405
|
+
ctx,
|
|
406
|
+
);
|
|
393
407
|
}
|
|
394
408
|
|
|
395
409
|
const directHandler =
|
|
@@ -9,7 +9,7 @@ export default {
|
|
|
9
9
|
return finalizeResponseForRequest(corsPreflightResponse, request);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
const bffResponse = await dispatchBffRequest(request, env);
|
|
12
|
+
const bffResponse = await dispatchBffRequest(request, env, ctx);
|
|
13
13
|
|
|
14
14
|
if (bffResponse) {
|
|
15
15
|
return finalizeResponseForRequest(
|
|
@@ -3,7 +3,6 @@ const MODERN_WORKER_MANIFEST = p_workerManifest;
|
|
|
3
3
|
export const modernWorkerManifest = MODERN_WORKER_MANIFEST;
|
|
4
4
|
const WORKER_MODULE_LOADERS = p_workerModuleLoaders;
|
|
5
5
|
const workerModulePromises = new Map();
|
|
6
|
-
const effectBffDispatcherPromises = new Map();
|
|
7
6
|
const remoteJsonPromises = new Map();
|
|
8
7
|
const CORS_POLICY = MODERN_WORKER_MANIFEST.security?.cors || {};
|
|
9
8
|
const ASSET_CORS_ENABLED = CORS_POLICY.assets !== false;
|
|
@@ -239,6 +238,9 @@ function finalizeResponseForRequest(response, request) {
|
|
|
239
238
|
|
|
240
239
|
const headers = new Headers(securedResponse.headers);
|
|
241
240
|
headers.delete('content-length');
|
|
241
|
+
// A HEAD response sends no body, so release the producer's stream instead of abandoning it:
|
|
242
|
+
// a stream nobody reads never finishes, and whatever waits for its end never runs.
|
|
243
|
+
securedResponse.body?.cancel().catch(() => undefined);
|
|
242
244
|
|
|
243
245
|
return new Response(null, {
|
|
244
246
|
headers,
|
|
@@ -237,109 +237,112 @@ function createEffectBffDispatcherErrorResponse(bff, error) {
|
|
|
237
237
|
);
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
240
|
+
// workerd binds every I/O object (sockets, timers, pending promises) to the request that
|
|
241
|
+
// created it, so an Effect runtime built while serving one request cannot serve the next: a
|
|
242
|
+
// pooled PostgreSQL connection or a cached in-flight effect from an earlier request never
|
|
243
|
+
// completes. Each BFF request therefore builds its own dispatcher and disposes it once the
|
|
244
|
+
// response body has been delivered; platform pools such as Hyperdrive keep connections warm.
|
|
245
|
+
async function createEffectBffDispatcher(bff, runtime) {
|
|
246
|
+
if (
|
|
247
|
+
typeof bff.dispatcherExport !== 'string' ||
|
|
248
|
+
bff.dispatcherExport.length === 0
|
|
249
|
+
) {
|
|
250
|
+
throw new Error('manifest does not declare dispatcherExport');
|
|
245
251
|
}
|
|
246
252
|
|
|
247
|
-
|
|
248
|
-
if (
|
|
249
|
-
typeof bff.dispatcherExport !== 'string' ||
|
|
250
|
-
bff.dispatcherExport.length === 0
|
|
251
|
-
) {
|
|
252
|
-
throw new Error('manifest does not declare dispatcherExport');
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const effectDispatcherFactory = runtime[bff.dispatcherExport];
|
|
253
|
+
const effectDispatcherFactory = runtime[bff.dispatcherExport];
|
|
256
254
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
255
|
+
if (typeof effectDispatcherFactory !== 'function') {
|
|
256
|
+
throw new Error(`worker bundle does not export ${bff.dispatcherExport}`);
|
|
257
|
+
}
|
|
260
258
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
throw new Error(
|
|
290
|
-
`manifest Effect BFF cross-project policy requires boolean ${field}`,
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
if (
|
|
295
|
-
!crossProjectPolicy.expectedOperationContracts ||
|
|
296
|
-
typeof crossProjectPolicy.expectedOperationContracts !== 'object' ||
|
|
297
|
-
Array.isArray(crossProjectPolicy.expectedOperationContracts)
|
|
298
|
-
) {
|
|
259
|
+
const effectConfig = bff.effect;
|
|
260
|
+
if (
|
|
261
|
+
!effectConfig ||
|
|
262
|
+
typeof effectConfig !== 'object' ||
|
|
263
|
+
Array.isArray(effectConfig)
|
|
264
|
+
) {
|
|
265
|
+
throw new Error('manifest declares invalid Effect BFF runtime config');
|
|
266
|
+
}
|
|
267
|
+
const crossProjectPolicy = effectConfig.crossProjectPolicy;
|
|
268
|
+
if (
|
|
269
|
+
!crossProjectPolicy ||
|
|
270
|
+
typeof crossProjectPolicy !== 'object' ||
|
|
271
|
+
Array.isArray(crossProjectPolicy)
|
|
272
|
+
) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
'manifest declares invalid Effect BFF cross-project policy',
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
for (const field of [
|
|
278
|
+
'enabled',
|
|
279
|
+
'requireEnvelope',
|
|
280
|
+
'requireOperationContext',
|
|
281
|
+
'requireOperationContextDetails',
|
|
282
|
+
'requireOperationSchemaHash',
|
|
283
|
+
'requireOperationVersion',
|
|
284
|
+
'allowUnknownOperations',
|
|
285
|
+
]) {
|
|
286
|
+
if (typeof crossProjectPolicy[field] !== 'boolean') {
|
|
299
287
|
throw new Error(
|
|
300
|
-
|
|
288
|
+
`manifest Effect BFF cross-project policy requires boolean ${field}`,
|
|
301
289
|
);
|
|
302
290
|
}
|
|
291
|
+
}
|
|
292
|
+
if (
|
|
293
|
+
!crossProjectPolicy.expectedOperationContracts ||
|
|
294
|
+
typeof crossProjectPolicy.expectedOperationContracts !== 'object' ||
|
|
295
|
+
Array.isArray(crossProjectPolicy.expectedOperationContracts)
|
|
296
|
+
) {
|
|
297
|
+
throw new Error(
|
|
298
|
+
'manifest Effect BFF cross-project policy requires expectedOperationContracts object',
|
|
299
|
+
);
|
|
300
|
+
}
|
|
303
301
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
if (!effectDispatcher || typeof effectDispatcher.dispatch !== 'function') {
|
|
318
|
-
try {
|
|
319
|
-
await effectDispatcher?.dispose?.();
|
|
320
|
-
} catch {}
|
|
302
|
+
const effectDispatcher = await effectDispatcherFactory({
|
|
303
|
+
prefix: bff.prefix,
|
|
304
|
+
...(effectConfig?.openapi === undefined
|
|
305
|
+
? {}
|
|
306
|
+
: { openapi: effectConfig.openapi }),
|
|
307
|
+
...(effectConfig?.dataPlatform === undefined
|
|
308
|
+
? {}
|
|
309
|
+
: { dataPlatform: effectConfig.dataPlatform }),
|
|
310
|
+
...(effectConfig?.crossProjectPolicy === undefined
|
|
311
|
+
? {}
|
|
312
|
+
: { crossProjectPolicy }),
|
|
313
|
+
});
|
|
321
314
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
);
|
|
325
|
-
}
|
|
315
|
+
if (!effectDispatcher || typeof effectDispatcher.dispatch !== 'function') {
|
|
316
|
+
try {
|
|
317
|
+
await effectDispatcher?.dispose?.();
|
|
318
|
+
} catch {}
|
|
326
319
|
|
|
327
|
-
|
|
328
|
-
|
|
320
|
+
throw new Error(
|
|
321
|
+
`worker export ${bff.dispatcherExport} did not return a dispatcher with a dispatch function`,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
329
324
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (
|
|
333
|
-
effectBffDispatcherPromises.get(bff.worker) === effectDispatcherPromise
|
|
334
|
-
) {
|
|
335
|
-
effectBffDispatcherPromises.delete(bff.worker);
|
|
336
|
-
}
|
|
337
|
-
});
|
|
325
|
+
return effectDispatcher;
|
|
326
|
+
}
|
|
338
327
|
|
|
339
|
-
|
|
328
|
+
function disposeEffectBffDispatcherAfterResponse(response, dispatcher, ctx) {
|
|
329
|
+
if (response.body === null) {
|
|
330
|
+
ctx.waitUntil(dispatcher.dispose());
|
|
331
|
+
return response;
|
|
332
|
+
}
|
|
333
|
+
const { readable, writable } = new TransformStream();
|
|
334
|
+
// The body pipe settles when the client has the whole body or the stream failed; a failure
|
|
335
|
+
// already reached the client through `readable`, so it only has to release the runtime here.
|
|
336
|
+
ctx.waitUntil(
|
|
337
|
+
response.body
|
|
338
|
+
.pipeTo(writable)
|
|
339
|
+
.catch(() => undefined)
|
|
340
|
+
.then(() => dispatcher.dispose()),
|
|
341
|
+
);
|
|
342
|
+
return new Response(readable, response);
|
|
340
343
|
}
|
|
341
344
|
|
|
342
|
-
async function dispatchBffRequest(request, env) {
|
|
345
|
+
async function dispatchBffRequest(request, env, ctx) {
|
|
343
346
|
const bff = MODERN_WORKER_MANIFEST.bff;
|
|
344
347
|
|
|
345
348
|
const requestUrl = new URL(request.url);
|
|
@@ -384,12 +387,23 @@ async function dispatchBffRequest(request, env) {
|
|
|
384
387
|
let effectDispatcher;
|
|
385
388
|
|
|
386
389
|
try {
|
|
387
|
-
effectDispatcher = await
|
|
390
|
+
effectDispatcher = await createEffectBffDispatcher(bff, runtime);
|
|
388
391
|
} catch (error) {
|
|
389
392
|
return createEffectBffDispatcherErrorResponse(bff, error);
|
|
390
393
|
}
|
|
391
394
|
|
|
392
|
-
|
|
395
|
+
let response;
|
|
396
|
+
try {
|
|
397
|
+
response = await effectDispatcher.dispatch(canonicalRequest, { env });
|
|
398
|
+
} catch (error) {
|
|
399
|
+
ctx.waitUntil(effectDispatcher.dispose());
|
|
400
|
+
throw error;
|
|
401
|
+
}
|
|
402
|
+
return disposeEffectBffDispatcherAfterResponse(
|
|
403
|
+
response,
|
|
404
|
+
effectDispatcher,
|
|
405
|
+
ctx,
|
|
406
|
+
);
|
|
393
407
|
}
|
|
394
408
|
|
|
395
409
|
const directHandler =
|
|
@@ -9,7 +9,7 @@ export default {
|
|
|
9
9
|
return finalizeResponseForRequest(corsPreflightResponse, request);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
const bffResponse = await dispatchBffRequest(request, env);
|
|
12
|
+
const bffResponse = await dispatchBffRequest(request, env, ctx);
|
|
13
13
|
|
|
14
14
|
if (bffResponse) {
|
|
15
15
|
return finalizeResponseForRequest(
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"directory": "packages/solutions/app-tools-extensions"
|
|
12
12
|
},
|
|
13
13
|
"license": "MIT",
|
|
14
|
-
"version": "3.9.0-ultramodern.
|
|
14
|
+
"version": "3.9.0-ultramodern.26",
|
|
15
15
|
"files": [
|
|
16
16
|
"dist",
|
|
17
17
|
"src"
|
|
@@ -153,16 +153,16 @@
|
|
|
153
153
|
"@babel/parser": "^8.0.6",
|
|
154
154
|
"@babel/traverse": "^8.0.6",
|
|
155
155
|
"@loadable/component": "5.16.7",
|
|
156
|
-
"@modern-js/backend-federation-contracts": "npm:@bleedingdev/modern-js-backend-federation-contracts@3.9.0-ultramodern.
|
|
157
|
-
"@modern-js/builder": "npm:@bleedingdev/modern-js-builder@3.9.0-ultramodern.
|
|
158
|
-
"@modern-js/prod-server": "npm:@bleedingdev/modern-js-prod-server@3.9.0-ultramodern.
|
|
159
|
-
"@modern-js/runtime-renderer-extensions": "npm:@bleedingdev/modern-js-runtime-renderer-extensions@3.9.0-ultramodern.
|
|
160
|
-
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.9.0-ultramodern.
|
|
161
|
-
"@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.9.0-ultramodern.
|
|
162
|
-
"@modern-js/server-runtime-extensions": "npm:@bleedingdev/modern-js-server-runtime-extensions@3.9.0-ultramodern.
|
|
163
|
-
"@modern-js/surface-resolution": "npm:@bleedingdev/modern-js-surface-resolution@3.9.0-ultramodern.
|
|
164
|
-
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.9.0-ultramodern.
|
|
165
|
-
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.
|
|
156
|
+
"@modern-js/backend-federation-contracts": "npm:@bleedingdev/modern-js-backend-federation-contracts@3.9.0-ultramodern.26",
|
|
157
|
+
"@modern-js/builder": "npm:@bleedingdev/modern-js-builder@3.9.0-ultramodern.26",
|
|
158
|
+
"@modern-js/prod-server": "npm:@bleedingdev/modern-js-prod-server@3.9.0-ultramodern.26",
|
|
159
|
+
"@modern-js/runtime-renderer-extensions": "npm:@bleedingdev/modern-js-runtime-renderer-extensions@3.9.0-ultramodern.26",
|
|
160
|
+
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.9.0-ultramodern.26",
|
|
161
|
+
"@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.9.0-ultramodern.26",
|
|
162
|
+
"@modern-js/server-runtime-extensions": "npm:@bleedingdev/modern-js-server-runtime-extensions@3.9.0-ultramodern.26",
|
|
163
|
+
"@modern-js/surface-resolution": "npm:@bleedingdev/modern-js-surface-resolution@3.9.0-ultramodern.26",
|
|
164
|
+
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.9.0-ultramodern.26",
|
|
165
|
+
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.26",
|
|
166
166
|
"@swc/helpers": "^0.5.23",
|
|
167
167
|
"@vercel/nft": "^1.11.0",
|
|
168
168
|
"compression-webpack-plugin": "^12.0.0",
|
|
@@ -179,10 +179,10 @@
|
|
|
179
179
|
}
|
|
180
180
|
},
|
|
181
181
|
"devDependencies": {
|
|
182
|
-
"@modern-js/bff-effect": "npm:@bleedingdev/modern-js-bff-effect@3.9.0-ultramodern.
|
|
183
|
-
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.
|
|
184
|
-
"@modern-js/plugin-bff-extensions": "npm:@bleedingdev/modern-js-plugin-bff-extensions@3.9.0-ultramodern.
|
|
185
|
-
"@modern-js/plugin-data-loader": "npm:@bleedingdev/modern-js-plugin-data-loader@3.9.0-ultramodern.
|
|
182
|
+
"@modern-js/bff-effect": "npm:@bleedingdev/modern-js-bff-effect@3.9.0-ultramodern.26",
|
|
183
|
+
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.26",
|
|
184
|
+
"@modern-js/plugin-bff-extensions": "npm:@bleedingdev/modern-js-plugin-bff-extensions@3.9.0-ultramodern.26",
|
|
185
|
+
"@modern-js/plugin-data-loader": "npm:@bleedingdev/modern-js-plugin-data-loader@3.9.0-ultramodern.26",
|
|
186
186
|
"@module-federation/modern-js-v3": "2.9.2",
|
|
187
187
|
"@module-federation/runtime-tools": "2.9.2",
|
|
188
188
|
"@rsbuild/core": "2.2.10",
|
|
@@ -3,7 +3,6 @@ const MODERN_WORKER_MANIFEST = p_workerManifest;
|
|
|
3
3
|
export const modernWorkerManifest = MODERN_WORKER_MANIFEST;
|
|
4
4
|
const WORKER_MODULE_LOADERS = p_workerModuleLoaders;
|
|
5
5
|
const workerModulePromises = new Map();
|
|
6
|
-
const effectBffDispatcherPromises = new Map();
|
|
7
6
|
const remoteJsonPromises = new Map();
|
|
8
7
|
const CORS_POLICY = MODERN_WORKER_MANIFEST.security?.cors || {};
|
|
9
8
|
const ASSET_CORS_ENABLED = CORS_POLICY.assets !== false;
|
|
@@ -239,6 +238,9 @@ function finalizeResponseForRequest(response, request) {
|
|
|
239
238
|
|
|
240
239
|
const headers = new Headers(securedResponse.headers);
|
|
241
240
|
headers.delete('content-length');
|
|
241
|
+
// A HEAD response sends no body, so release the producer's stream instead of abandoning it:
|
|
242
|
+
// a stream nobody reads never finishes, and whatever waits for its end never runs.
|
|
243
|
+
securedResponse.body?.cancel().catch(() => undefined);
|
|
242
244
|
|
|
243
245
|
return new Response(null, {
|
|
244
246
|
headers,
|
|
@@ -237,109 +237,112 @@ function createEffectBffDispatcherErrorResponse(bff, error) {
|
|
|
237
237
|
);
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
240
|
+
// workerd binds every I/O object (sockets, timers, pending promises) to the request that
|
|
241
|
+
// created it, so an Effect runtime built while serving one request cannot serve the next: a
|
|
242
|
+
// pooled PostgreSQL connection or a cached in-flight effect from an earlier request never
|
|
243
|
+
// completes. Each BFF request therefore builds its own dispatcher and disposes it once the
|
|
244
|
+
// response body has been delivered; platform pools such as Hyperdrive keep connections warm.
|
|
245
|
+
async function createEffectBffDispatcher(bff, runtime) {
|
|
246
|
+
if (
|
|
247
|
+
typeof bff.dispatcherExport !== 'string' ||
|
|
248
|
+
bff.dispatcherExport.length === 0
|
|
249
|
+
) {
|
|
250
|
+
throw new Error('manifest does not declare dispatcherExport');
|
|
245
251
|
}
|
|
246
252
|
|
|
247
|
-
|
|
248
|
-
if (
|
|
249
|
-
typeof bff.dispatcherExport !== 'string' ||
|
|
250
|
-
bff.dispatcherExport.length === 0
|
|
251
|
-
) {
|
|
252
|
-
throw new Error('manifest does not declare dispatcherExport');
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const effectDispatcherFactory = runtime[bff.dispatcherExport];
|
|
253
|
+
const effectDispatcherFactory = runtime[bff.dispatcherExport];
|
|
256
254
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
255
|
+
if (typeof effectDispatcherFactory !== 'function') {
|
|
256
|
+
throw new Error(`worker bundle does not export ${bff.dispatcherExport}`);
|
|
257
|
+
}
|
|
260
258
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
throw new Error(
|
|
290
|
-
`manifest Effect BFF cross-project policy requires boolean ${field}`,
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
if (
|
|
295
|
-
!crossProjectPolicy.expectedOperationContracts ||
|
|
296
|
-
typeof crossProjectPolicy.expectedOperationContracts !== 'object' ||
|
|
297
|
-
Array.isArray(crossProjectPolicy.expectedOperationContracts)
|
|
298
|
-
) {
|
|
259
|
+
const effectConfig = bff.effect;
|
|
260
|
+
if (
|
|
261
|
+
!effectConfig ||
|
|
262
|
+
typeof effectConfig !== 'object' ||
|
|
263
|
+
Array.isArray(effectConfig)
|
|
264
|
+
) {
|
|
265
|
+
throw new Error('manifest declares invalid Effect BFF runtime config');
|
|
266
|
+
}
|
|
267
|
+
const crossProjectPolicy = effectConfig.crossProjectPolicy;
|
|
268
|
+
if (
|
|
269
|
+
!crossProjectPolicy ||
|
|
270
|
+
typeof crossProjectPolicy !== 'object' ||
|
|
271
|
+
Array.isArray(crossProjectPolicy)
|
|
272
|
+
) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
'manifest declares invalid Effect BFF cross-project policy',
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
for (const field of [
|
|
278
|
+
'enabled',
|
|
279
|
+
'requireEnvelope',
|
|
280
|
+
'requireOperationContext',
|
|
281
|
+
'requireOperationContextDetails',
|
|
282
|
+
'requireOperationSchemaHash',
|
|
283
|
+
'requireOperationVersion',
|
|
284
|
+
'allowUnknownOperations',
|
|
285
|
+
]) {
|
|
286
|
+
if (typeof crossProjectPolicy[field] !== 'boolean') {
|
|
299
287
|
throw new Error(
|
|
300
|
-
|
|
288
|
+
`manifest Effect BFF cross-project policy requires boolean ${field}`,
|
|
301
289
|
);
|
|
302
290
|
}
|
|
291
|
+
}
|
|
292
|
+
if (
|
|
293
|
+
!crossProjectPolicy.expectedOperationContracts ||
|
|
294
|
+
typeof crossProjectPolicy.expectedOperationContracts !== 'object' ||
|
|
295
|
+
Array.isArray(crossProjectPolicy.expectedOperationContracts)
|
|
296
|
+
) {
|
|
297
|
+
throw new Error(
|
|
298
|
+
'manifest Effect BFF cross-project policy requires expectedOperationContracts object',
|
|
299
|
+
);
|
|
300
|
+
}
|
|
303
301
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
if (!effectDispatcher || typeof effectDispatcher.dispatch !== 'function') {
|
|
318
|
-
try {
|
|
319
|
-
await effectDispatcher?.dispose?.();
|
|
320
|
-
} catch {}
|
|
302
|
+
const effectDispatcher = await effectDispatcherFactory({
|
|
303
|
+
prefix: bff.prefix,
|
|
304
|
+
...(effectConfig?.openapi === undefined
|
|
305
|
+
? {}
|
|
306
|
+
: { openapi: effectConfig.openapi }),
|
|
307
|
+
...(effectConfig?.dataPlatform === undefined
|
|
308
|
+
? {}
|
|
309
|
+
: { dataPlatform: effectConfig.dataPlatform }),
|
|
310
|
+
...(effectConfig?.crossProjectPolicy === undefined
|
|
311
|
+
? {}
|
|
312
|
+
: { crossProjectPolicy }),
|
|
313
|
+
});
|
|
321
314
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
);
|
|
325
|
-
}
|
|
315
|
+
if (!effectDispatcher || typeof effectDispatcher.dispatch !== 'function') {
|
|
316
|
+
try {
|
|
317
|
+
await effectDispatcher?.dispose?.();
|
|
318
|
+
} catch {}
|
|
326
319
|
|
|
327
|
-
|
|
328
|
-
|
|
320
|
+
throw new Error(
|
|
321
|
+
`worker export ${bff.dispatcherExport} did not return a dispatcher with a dispatch function`,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
329
324
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (
|
|
333
|
-
effectBffDispatcherPromises.get(bff.worker) === effectDispatcherPromise
|
|
334
|
-
) {
|
|
335
|
-
effectBffDispatcherPromises.delete(bff.worker);
|
|
336
|
-
}
|
|
337
|
-
});
|
|
325
|
+
return effectDispatcher;
|
|
326
|
+
}
|
|
338
327
|
|
|
339
|
-
|
|
328
|
+
function disposeEffectBffDispatcherAfterResponse(response, dispatcher, ctx) {
|
|
329
|
+
if (response.body === null) {
|
|
330
|
+
ctx.waitUntil(dispatcher.dispose());
|
|
331
|
+
return response;
|
|
332
|
+
}
|
|
333
|
+
const { readable, writable } = new TransformStream();
|
|
334
|
+
// The body pipe settles when the client has the whole body or the stream failed; a failure
|
|
335
|
+
// already reached the client through `readable`, so it only has to release the runtime here.
|
|
336
|
+
ctx.waitUntil(
|
|
337
|
+
response.body
|
|
338
|
+
.pipeTo(writable)
|
|
339
|
+
.catch(() => undefined)
|
|
340
|
+
.then(() => dispatcher.dispose()),
|
|
341
|
+
);
|
|
342
|
+
return new Response(readable, response);
|
|
340
343
|
}
|
|
341
344
|
|
|
342
|
-
async function dispatchBffRequest(request, env) {
|
|
345
|
+
async function dispatchBffRequest(request, env, ctx) {
|
|
343
346
|
const bff = MODERN_WORKER_MANIFEST.bff;
|
|
344
347
|
|
|
345
348
|
const requestUrl = new URL(request.url);
|
|
@@ -384,12 +387,23 @@ async function dispatchBffRequest(request, env) {
|
|
|
384
387
|
let effectDispatcher;
|
|
385
388
|
|
|
386
389
|
try {
|
|
387
|
-
effectDispatcher = await
|
|
390
|
+
effectDispatcher = await createEffectBffDispatcher(bff, runtime);
|
|
388
391
|
} catch (error) {
|
|
389
392
|
return createEffectBffDispatcherErrorResponse(bff, error);
|
|
390
393
|
}
|
|
391
394
|
|
|
392
|
-
|
|
395
|
+
let response;
|
|
396
|
+
try {
|
|
397
|
+
response = await effectDispatcher.dispatch(canonicalRequest, { env });
|
|
398
|
+
} catch (error) {
|
|
399
|
+
ctx.waitUntil(effectDispatcher.dispose());
|
|
400
|
+
throw error;
|
|
401
|
+
}
|
|
402
|
+
return disposeEffectBffDispatcherAfterResponse(
|
|
403
|
+
response,
|
|
404
|
+
effectDispatcher,
|
|
405
|
+
ctx,
|
|
406
|
+
);
|
|
393
407
|
}
|
|
394
408
|
|
|
395
409
|
const directHandler =
|
|
@@ -9,7 +9,7 @@ export default {
|
|
|
9
9
|
return finalizeResponseForRequest(corsPreflightResponse, request);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
const bffResponse = await dispatchBffRequest(request, env);
|
|
12
|
+
const bffResponse = await dispatchBffRequest(request, env, ctx);
|
|
13
13
|
|
|
14
14
|
if (bffResponse) {
|
|
15
15
|
return finalizeResponseForRequest(
|