@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.23 → 3.9.0-ultramodern.25

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.
@@ -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
- function getEffectBffDispatcher(bff, runtime) {
241
- let effectDispatcherPromise = effectBffDispatcherPromises.get(bff.worker);
242
-
243
- if (effectDispatcherPromise) {
244
- return effectDispatcherPromise;
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
- effectDispatcherPromise = Promise.resolve().then(async () => {
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
- if (typeof effectDispatcherFactory !== 'function') {
258
- throw new Error(`worker bundle does not export ${bff.dispatcherExport}`);
259
- }
255
+ if (typeof effectDispatcherFactory !== 'function') {
256
+ throw new Error(`worker bundle does not export ${bff.dispatcherExport}`);
257
+ }
260
258
 
261
- const effectConfig = bff.effect;
262
- if (
263
- !effectConfig ||
264
- typeof effectConfig !== 'object' ||
265
- Array.isArray(effectConfig)
266
- ) {
267
- throw new Error('manifest declares invalid Effect BFF runtime config');
268
- }
269
- const crossProjectPolicy = effectConfig.crossProjectPolicy;
270
- if (
271
- !crossProjectPolicy ||
272
- typeof crossProjectPolicy !== 'object' ||
273
- Array.isArray(crossProjectPolicy)
274
- ) {
275
- throw new Error(
276
- 'manifest declares invalid Effect BFF cross-project policy',
277
- );
278
- }
279
- for (const field of [
280
- 'enabled',
281
- 'requireEnvelope',
282
- 'requireOperationContext',
283
- 'requireOperationContextDetails',
284
- 'requireOperationSchemaHash',
285
- 'requireOperationVersion',
286
- 'allowUnknownOperations',
287
- ]) {
288
- if (typeof crossProjectPolicy[field] !== 'boolean') {
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
- 'manifest Effect BFF cross-project policy requires expectedOperationContracts object',
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
- const effectDispatcher = await effectDispatcherFactory({
305
- prefix: bff.prefix,
306
- ...(effectConfig?.openapi === undefined
307
- ? {}
308
- : { openapi: effectConfig.openapi }),
309
- ...(effectConfig?.dataPlatform === undefined
310
- ? {}
311
- : { dataPlatform: effectConfig.dataPlatform }),
312
- ...(effectConfig?.crossProjectPolicy === undefined
313
- ? {}
314
- : { crossProjectPolicy }),
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
- throw new Error(
323
- `worker export ${bff.dispatcherExport} did not return a dispatcher with a dispatch function`,
324
- );
325
- }
315
+ if (!effectDispatcher || typeof effectDispatcher.dispatch !== 'function') {
316
+ try {
317
+ await effectDispatcher?.dispose?.();
318
+ } catch {}
326
319
 
327
- return effectDispatcher;
328
- });
320
+ throw new Error(
321
+ `worker export ${bff.dispatcherExport} did not return a dispatcher with a dispatch function`,
322
+ );
323
+ }
329
324
 
330
- effectBffDispatcherPromises.set(bff.worker, effectDispatcherPromise);
331
- effectDispatcherPromise.catch(() => {
332
- if (
333
- effectBffDispatcherPromises.get(bff.worker) === effectDispatcherPromise
334
- ) {
335
- effectBffDispatcherPromises.delete(bff.worker);
336
- }
337
- });
325
+ return effectDispatcher;
326
+ }
338
327
 
339
- return effectDispatcherPromise;
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 getEffectBffDispatcher(bff, runtime);
390
+ effectDispatcher = await createEffectBffDispatcher(bff, runtime);
388
391
  } catch (error) {
389
392
  return createEffectBffDispatcherErrorResponse(bff, error);
390
393
  }
391
394
 
392
- return effectDispatcher.dispatch(canonicalRequest, { env });
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
- function getEffectBffDispatcher(bff, runtime) {
241
- let effectDispatcherPromise = effectBffDispatcherPromises.get(bff.worker);
242
-
243
- if (effectDispatcherPromise) {
244
- return effectDispatcherPromise;
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
- effectDispatcherPromise = Promise.resolve().then(async () => {
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
- if (typeof effectDispatcherFactory !== 'function') {
258
- throw new Error(`worker bundle does not export ${bff.dispatcherExport}`);
259
- }
255
+ if (typeof effectDispatcherFactory !== 'function') {
256
+ throw new Error(`worker bundle does not export ${bff.dispatcherExport}`);
257
+ }
260
258
 
261
- const effectConfig = bff.effect;
262
- if (
263
- !effectConfig ||
264
- typeof effectConfig !== 'object' ||
265
- Array.isArray(effectConfig)
266
- ) {
267
- throw new Error('manifest declares invalid Effect BFF runtime config');
268
- }
269
- const crossProjectPolicy = effectConfig.crossProjectPolicy;
270
- if (
271
- !crossProjectPolicy ||
272
- typeof crossProjectPolicy !== 'object' ||
273
- Array.isArray(crossProjectPolicy)
274
- ) {
275
- throw new Error(
276
- 'manifest declares invalid Effect BFF cross-project policy',
277
- );
278
- }
279
- for (const field of [
280
- 'enabled',
281
- 'requireEnvelope',
282
- 'requireOperationContext',
283
- 'requireOperationContextDetails',
284
- 'requireOperationSchemaHash',
285
- 'requireOperationVersion',
286
- 'allowUnknownOperations',
287
- ]) {
288
- if (typeof crossProjectPolicy[field] !== 'boolean') {
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
- 'manifest Effect BFF cross-project policy requires expectedOperationContracts object',
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
- const effectDispatcher = await effectDispatcherFactory({
305
- prefix: bff.prefix,
306
- ...(effectConfig?.openapi === undefined
307
- ? {}
308
- : { openapi: effectConfig.openapi }),
309
- ...(effectConfig?.dataPlatform === undefined
310
- ? {}
311
- : { dataPlatform: effectConfig.dataPlatform }),
312
- ...(effectConfig?.crossProjectPolicy === undefined
313
- ? {}
314
- : { crossProjectPolicy }),
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
- throw new Error(
323
- `worker export ${bff.dispatcherExport} did not return a dispatcher with a dispatch function`,
324
- );
325
- }
315
+ if (!effectDispatcher || typeof effectDispatcher.dispatch !== 'function') {
316
+ try {
317
+ await effectDispatcher?.dispose?.();
318
+ } catch {}
326
319
 
327
- return effectDispatcher;
328
- });
320
+ throw new Error(
321
+ `worker export ${bff.dispatcherExport} did not return a dispatcher with a dispatch function`,
322
+ );
323
+ }
329
324
 
330
- effectBffDispatcherPromises.set(bff.worker, effectDispatcherPromise);
331
- effectDispatcherPromise.catch(() => {
332
- if (
333
- effectBffDispatcherPromises.get(bff.worker) === effectDispatcherPromise
334
- ) {
335
- effectBffDispatcherPromises.delete(bff.worker);
336
- }
337
- });
325
+ return effectDispatcher;
326
+ }
338
327
 
339
- return effectDispatcherPromise;
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 getEffectBffDispatcher(bff, runtime);
390
+ effectDispatcher = await createEffectBffDispatcher(bff, runtime);
388
391
  } catch (error) {
389
392
  return createEffectBffDispatcherErrorResponse(bff, error);
390
393
  }
391
394
 
392
- return effectDispatcher.dispatch(canonicalRequest, { env });
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
- function getEffectBffDispatcher(bff, runtime) {
241
- let effectDispatcherPromise = effectBffDispatcherPromises.get(bff.worker);
242
-
243
- if (effectDispatcherPromise) {
244
- return effectDispatcherPromise;
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
- effectDispatcherPromise = Promise.resolve().then(async () => {
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
- if (typeof effectDispatcherFactory !== 'function') {
258
- throw new Error(`worker bundle does not export ${bff.dispatcherExport}`);
259
- }
255
+ if (typeof effectDispatcherFactory !== 'function') {
256
+ throw new Error(`worker bundle does not export ${bff.dispatcherExport}`);
257
+ }
260
258
 
261
- const effectConfig = bff.effect;
262
- if (
263
- !effectConfig ||
264
- typeof effectConfig !== 'object' ||
265
- Array.isArray(effectConfig)
266
- ) {
267
- throw new Error('manifest declares invalid Effect BFF runtime config');
268
- }
269
- const crossProjectPolicy = effectConfig.crossProjectPolicy;
270
- if (
271
- !crossProjectPolicy ||
272
- typeof crossProjectPolicy !== 'object' ||
273
- Array.isArray(crossProjectPolicy)
274
- ) {
275
- throw new Error(
276
- 'manifest declares invalid Effect BFF cross-project policy',
277
- );
278
- }
279
- for (const field of [
280
- 'enabled',
281
- 'requireEnvelope',
282
- 'requireOperationContext',
283
- 'requireOperationContextDetails',
284
- 'requireOperationSchemaHash',
285
- 'requireOperationVersion',
286
- 'allowUnknownOperations',
287
- ]) {
288
- if (typeof crossProjectPolicy[field] !== 'boolean') {
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
- 'manifest Effect BFF cross-project policy requires expectedOperationContracts object',
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
- const effectDispatcher = await effectDispatcherFactory({
305
- prefix: bff.prefix,
306
- ...(effectConfig?.openapi === undefined
307
- ? {}
308
- : { openapi: effectConfig.openapi }),
309
- ...(effectConfig?.dataPlatform === undefined
310
- ? {}
311
- : { dataPlatform: effectConfig.dataPlatform }),
312
- ...(effectConfig?.crossProjectPolicy === undefined
313
- ? {}
314
- : { crossProjectPolicy }),
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
- throw new Error(
323
- `worker export ${bff.dispatcherExport} did not return a dispatcher with a dispatch function`,
324
- );
325
- }
315
+ if (!effectDispatcher || typeof effectDispatcher.dispatch !== 'function') {
316
+ try {
317
+ await effectDispatcher?.dispose?.();
318
+ } catch {}
326
319
 
327
- return effectDispatcher;
328
- });
320
+ throw new Error(
321
+ `worker export ${bff.dispatcherExport} did not return a dispatcher with a dispatch function`,
322
+ );
323
+ }
329
324
 
330
- effectBffDispatcherPromises.set(bff.worker, effectDispatcherPromise);
331
- effectDispatcherPromise.catch(() => {
332
- if (
333
- effectBffDispatcherPromises.get(bff.worker) === effectDispatcherPromise
334
- ) {
335
- effectBffDispatcherPromises.delete(bff.worker);
336
- }
337
- });
325
+ return effectDispatcher;
326
+ }
338
327
 
339
- return effectDispatcherPromise;
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 getEffectBffDispatcher(bff, runtime);
390
+ effectDispatcher = await createEffectBffDispatcher(bff, runtime);
388
391
  } catch (error) {
389
392
  return createEffectBffDispatcherErrorResponse(bff, error);
390
393
  }
391
394
 
392
- return effectDispatcher.dispatch(canonicalRequest, { env });
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.23",
14
+ "version": "3.9.0-ultramodern.25",
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.23",
157
- "@modern-js/builder": "npm:@bleedingdev/modern-js-builder@3.9.0-ultramodern.23",
158
- "@modern-js/prod-server": "npm:@bleedingdev/modern-js-prod-server@3.9.0-ultramodern.23",
159
- "@modern-js/runtime-renderer-extensions": "npm:@bleedingdev/modern-js-runtime-renderer-extensions@3.9.0-ultramodern.23",
160
- "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.9.0-ultramodern.23",
161
- "@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.9.0-ultramodern.23",
162
- "@modern-js/server-runtime-extensions": "npm:@bleedingdev/modern-js-server-runtime-extensions@3.9.0-ultramodern.23",
163
- "@modern-js/surface-resolution": "npm:@bleedingdev/modern-js-surface-resolution@3.9.0-ultramodern.23",
164
- "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.9.0-ultramodern.23",
165
- "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.23",
156
+ "@modern-js/backend-federation-contracts": "npm:@bleedingdev/modern-js-backend-federation-contracts@3.9.0-ultramodern.25",
157
+ "@modern-js/builder": "npm:@bleedingdev/modern-js-builder@3.9.0-ultramodern.25",
158
+ "@modern-js/prod-server": "npm:@bleedingdev/modern-js-prod-server@3.9.0-ultramodern.25",
159
+ "@modern-js/runtime-renderer-extensions": "npm:@bleedingdev/modern-js-runtime-renderer-extensions@3.9.0-ultramodern.25",
160
+ "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.9.0-ultramodern.25",
161
+ "@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.9.0-ultramodern.25",
162
+ "@modern-js/server-runtime-extensions": "npm:@bleedingdev/modern-js-server-runtime-extensions@3.9.0-ultramodern.25",
163
+ "@modern-js/surface-resolution": "npm:@bleedingdev/modern-js-surface-resolution@3.9.0-ultramodern.25",
164
+ "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.9.0-ultramodern.25",
165
+ "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.25",
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.23",
183
- "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.23",
184
- "@modern-js/plugin-bff-extensions": "npm:@bleedingdev/modern-js-plugin-bff-extensions@3.9.0-ultramodern.23",
185
- "@modern-js/plugin-data-loader": "npm:@bleedingdev/modern-js-plugin-data-loader@3.9.0-ultramodern.23",
182
+ "@modern-js/bff-effect": "npm:@bleedingdev/modern-js-bff-effect@3.9.0-ultramodern.25",
183
+ "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.25",
184
+ "@modern-js/plugin-bff-extensions": "npm:@bleedingdev/modern-js-plugin-bff-extensions@3.9.0-ultramodern.25",
185
+ "@modern-js/plugin-data-loader": "npm:@bleedingdev/modern-js-plugin-data-loader@3.9.0-ultramodern.25",
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
- function getEffectBffDispatcher(bff, runtime) {
241
- let effectDispatcherPromise = effectBffDispatcherPromises.get(bff.worker);
242
-
243
- if (effectDispatcherPromise) {
244
- return effectDispatcherPromise;
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
- effectDispatcherPromise = Promise.resolve().then(async () => {
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
- if (typeof effectDispatcherFactory !== 'function') {
258
- throw new Error(`worker bundle does not export ${bff.dispatcherExport}`);
259
- }
255
+ if (typeof effectDispatcherFactory !== 'function') {
256
+ throw new Error(`worker bundle does not export ${bff.dispatcherExport}`);
257
+ }
260
258
 
261
- const effectConfig = bff.effect;
262
- if (
263
- !effectConfig ||
264
- typeof effectConfig !== 'object' ||
265
- Array.isArray(effectConfig)
266
- ) {
267
- throw new Error('manifest declares invalid Effect BFF runtime config');
268
- }
269
- const crossProjectPolicy = effectConfig.crossProjectPolicy;
270
- if (
271
- !crossProjectPolicy ||
272
- typeof crossProjectPolicy !== 'object' ||
273
- Array.isArray(crossProjectPolicy)
274
- ) {
275
- throw new Error(
276
- 'manifest declares invalid Effect BFF cross-project policy',
277
- );
278
- }
279
- for (const field of [
280
- 'enabled',
281
- 'requireEnvelope',
282
- 'requireOperationContext',
283
- 'requireOperationContextDetails',
284
- 'requireOperationSchemaHash',
285
- 'requireOperationVersion',
286
- 'allowUnknownOperations',
287
- ]) {
288
- if (typeof crossProjectPolicy[field] !== 'boolean') {
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
- 'manifest Effect BFF cross-project policy requires expectedOperationContracts object',
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
- const effectDispatcher = await effectDispatcherFactory({
305
- prefix: bff.prefix,
306
- ...(effectConfig?.openapi === undefined
307
- ? {}
308
- : { openapi: effectConfig.openapi }),
309
- ...(effectConfig?.dataPlatform === undefined
310
- ? {}
311
- : { dataPlatform: effectConfig.dataPlatform }),
312
- ...(effectConfig?.crossProjectPolicy === undefined
313
- ? {}
314
- : { crossProjectPolicy }),
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
- throw new Error(
323
- `worker export ${bff.dispatcherExport} did not return a dispatcher with a dispatch function`,
324
- );
325
- }
315
+ if (!effectDispatcher || typeof effectDispatcher.dispatch !== 'function') {
316
+ try {
317
+ await effectDispatcher?.dispose?.();
318
+ } catch {}
326
319
 
327
- return effectDispatcher;
328
- });
320
+ throw new Error(
321
+ `worker export ${bff.dispatcherExport} did not return a dispatcher with a dispatch function`,
322
+ );
323
+ }
329
324
 
330
- effectBffDispatcherPromises.set(bff.worker, effectDispatcherPromise);
331
- effectDispatcherPromise.catch(() => {
332
- if (
333
- effectBffDispatcherPromises.get(bff.worker) === effectDispatcherPromise
334
- ) {
335
- effectBffDispatcherPromises.delete(bff.worker);
336
- }
337
- });
325
+ return effectDispatcher;
326
+ }
338
327
 
339
- return effectDispatcherPromise;
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 getEffectBffDispatcher(bff, runtime);
390
+ effectDispatcher = await createEffectBffDispatcher(bff, runtime);
388
391
  } catch (error) {
389
392
  return createEffectBffDispatcherErrorResponse(bff, error);
390
393
  }
391
394
 
392
- return effectDispatcher.dispatch(canonicalRequest, { env });
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(