@bleedingdev/modern-js-bff-effect 3.9.0-ultramodern.2 → 3.9.0-ultramodern.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/README.md +20 -4
  2. package/dist/cjs/assembly.js +50 -0
  3. package/dist/cjs/{effect-client/guards.js → data-platform/batch/options.js} +13 -9
  4. package/dist/cjs/data-platform/batch/queue.js +48 -83
  5. package/dist/cjs/data-platform/trace.js +2 -2
  6. package/dist/cjs/effect/context.js +28 -4
  7. package/dist/cjs/effect/edge-dispatcher.js +12 -12
  8. package/dist/cjs/effect/edge.js +36 -3
  9. package/dist/cjs/effect/endpoint-contracts.js +3 -4
  10. package/dist/cjs/effect/entry-shape.js +40 -12
  11. package/dist/cjs/effect/generated-entry.js +57 -0
  12. package/dist/cjs/effect/handler/batch-handler.js +7 -7
  13. package/dist/cjs/effect/handler/batch.js +8 -10
  14. package/dist/cjs/effect/handler/definition.js +1 -21
  15. package/dist/cjs/effect/index.js +8 -8
  16. package/dist/cjs/effect/module.js +8 -4
  17. package/dist/cjs/effect/operation-context.js +10 -14
  18. package/dist/cjs/effect/rpcSerialization.js +0 -2
  19. package/dist/cjs/effect-client/cross-project.js +89 -0
  20. package/dist/cjs/effect-client/index.js +5 -3
  21. package/dist/cjs/microvertical-api.js +120 -0
  22. package/dist/esm/assembly.mjs +12 -0
  23. package/dist/esm/data-platform/batch/options.mjs +10 -0
  24. package/dist/esm/data-platform/batch/queue.mjs +48 -83
  25. package/dist/esm/data-platform/trace.mjs +1 -1
  26. package/dist/esm/effect/context.mjs +21 -6
  27. package/dist/esm/effect/edge-dispatcher.mjs +6 -6
  28. package/dist/esm/effect/edge.mjs +3 -0
  29. package/dist/esm/effect/endpoint-contracts.mjs +2 -3
  30. package/dist/esm/effect/entry-shape-registry.mjs +2 -2
  31. package/dist/esm/effect/entry-shape.mjs +29 -6
  32. package/dist/esm/effect/generated-entry.mjs +19 -0
  33. package/dist/esm/effect/handler/batch-handler.mjs +8 -8
  34. package/dist/esm/effect/handler/batch.mjs +9 -8
  35. package/dist/esm/effect/handler/definition.mjs +1 -21
  36. package/dist/esm/effect/index.mjs +1 -1
  37. package/dist/esm/effect/module.mjs +8 -4
  38. package/dist/esm/effect/operation-context.mjs +3 -4
  39. package/dist/esm/effect/rpcSerialization.mjs +0 -2
  40. package/dist/esm/effect-client/cross-project.mjs +51 -0
  41. package/dist/esm/effect-client/index.mjs +4 -2
  42. package/dist/esm/microvertical-api.mjs +67 -0
  43. package/dist/esm-node/assembly.mjs +13 -0
  44. package/dist/esm-node/data-platform/batch/options.mjs +11 -0
  45. package/dist/esm-node/data-platform/batch/queue.mjs +48 -83
  46. package/dist/esm-node/data-platform/trace.mjs +1 -1
  47. package/dist/esm-node/effect/context.mjs +21 -6
  48. package/dist/esm-node/effect/edge-dispatcher.mjs +6 -6
  49. package/dist/esm-node/effect/edge.mjs +3 -0
  50. package/dist/esm-node/effect/endpoint-contracts.mjs +2 -3
  51. package/dist/esm-node/effect/entry-shape.mjs +29 -6
  52. package/dist/esm-node/effect/generated-entry.mjs +20 -0
  53. package/dist/esm-node/effect/handler/batch-handler.mjs +8 -8
  54. package/dist/esm-node/effect/handler/batch.mjs +9 -8
  55. package/dist/esm-node/effect/handler/definition.mjs +1 -21
  56. package/dist/esm-node/effect/index.mjs +1 -1
  57. package/dist/esm-node/effect/module.mjs +8 -4
  58. package/dist/esm-node/effect/operation-context.mjs +3 -4
  59. package/dist/esm-node/effect/rpcSerialization.mjs +0 -2
  60. package/dist/esm-node/effect-client/cross-project.mjs +52 -0
  61. package/dist/esm-node/effect-client/index.mjs +4 -2
  62. package/dist/esm-node/microvertical-api.mjs +68 -0
  63. package/dist/types/assembly.d.ts +10 -0
  64. package/dist/types/data-platform/batch/options.d.ts +7 -0
  65. package/dist/types/data-platform/batch/queue.d.ts +0 -1
  66. package/dist/types/effect/context.d.ts +15 -2
  67. package/dist/types/effect/edge-dispatcher.d.ts +3 -3
  68. package/dist/types/effect/edge.d.ts +3 -0
  69. package/dist/types/effect/endpoint-contracts.d.ts +2 -20
  70. package/dist/types/effect/entry-shape.d.ts +16 -2
  71. package/dist/types/effect/generated-entry.d.ts +7 -0
  72. package/dist/types/effect/handler/batch.d.ts +7 -2
  73. package/dist/types/effect/handler/types.d.ts +2 -17
  74. package/dist/types/effect/handler.d.ts +1 -1
  75. package/dist/types/effect/module.d.ts +2 -1
  76. package/dist/types/effect/operation-context.d.ts +1 -2
  77. package/dist/types/effect-client/cross-project.d.ts +10 -0
  78. package/dist/types/effect-client/index.d.ts +7 -4
  79. package/dist/types/microvertical-api.d.ts +65 -0
  80. package/package.json +47 -42
  81. package/src/assembly.ts +43 -0
  82. package/src/data-platform/batch/options.ts +20 -0
  83. package/src/data-platform/batch/queue.ts +63 -92
  84. package/src/data-platform/trace.ts +1 -1
  85. package/src/effect/context.ts +64 -12
  86. package/src/effect/edge-dispatcher.ts +14 -11
  87. package/src/effect/edge.ts +6 -0
  88. package/src/effect/endpoint-contracts.ts +3 -24
  89. package/src/effect/entry-shape.ts +105 -10
  90. package/src/effect/generated-entry.ts +36 -0
  91. package/src/effect/handler/batch-handler.ts +27 -28
  92. package/src/effect/handler/batch.ts +9 -13
  93. package/src/effect/handler/definition.ts +0 -45
  94. package/src/effect/handler/http.ts +2 -2
  95. package/src/effect/handler/types.ts +3 -32
  96. package/src/effect/handler.ts +0 -1
  97. package/src/effect/index.ts +1 -1
  98. package/src/effect/module.ts +11 -4
  99. package/src/effect/operation-context.ts +2 -6
  100. package/src/effect/rpcSerialization.ts +0 -2
  101. package/src/effect-client/cross-project.ts +104 -0
  102. package/src/effect-client/index.ts +17 -7
  103. package/src/microvertical-api.ts +102 -0
  104. package/dist/cjs/effect/edge-context.js +0 -61
  105. package/dist/cjs/effect-client/batch.js +0 -73
  106. package/dist/cjs/effect-client/envelope.js +0 -145
  107. package/dist/cjs/effect-client/request-context.js +0 -64
  108. package/dist/cjs/effect-client/request.js +0 -66
  109. package/dist/cjs/effect-client/runtime.js +0 -100
  110. package/dist/cjs/effect-client/types.js +0 -18
  111. package/dist/esm/effect/edge-context.mjs +0 -14
  112. package/dist/esm/effect-client/batch.mjs +0 -35
  113. package/dist/esm/effect-client/envelope.mjs +0 -104
  114. package/dist/esm/effect-client/guards.mjs +0 -3
  115. package/dist/esm/effect-client/request-context.mjs +0 -23
  116. package/dist/esm/effect-client/request.mjs +0 -28
  117. package/dist/esm/effect-client/runtime.mjs +0 -62
  118. package/dist/esm/effect-client/types.mjs +0 -0
  119. package/dist/esm-node/effect/edge-context.mjs +0 -15
  120. package/dist/esm-node/effect-client/batch.mjs +0 -36
  121. package/dist/esm-node/effect-client/envelope.mjs +0 -105
  122. package/dist/esm-node/effect-client/guards.mjs +0 -4
  123. package/dist/esm-node/effect-client/request-context.mjs +0 -24
  124. package/dist/esm-node/effect-client/request.mjs +0 -29
  125. package/dist/esm-node/effect-client/runtime.mjs +0 -63
  126. package/dist/esm-node/effect-client/types.mjs +0 -1
  127. package/dist/types/effect/edge-context.d.ts +0 -5
  128. package/dist/types/effect-client/batch.d.ts +0 -2
  129. package/dist/types/effect-client/envelope.d.ts +0 -15
  130. package/dist/types/effect-client/guards.d.ts +0 -2
  131. package/dist/types/effect-client/request-context.d.ts +0 -3
  132. package/dist/types/effect-client/request.d.ts +0 -1
  133. package/dist/types/effect-client/runtime.d.ts +0 -5
  134. package/dist/types/effect-client/types.d.ts +0 -68
  135. package/src/effect/edge-context.ts +0 -51
  136. package/src/effect-client/batch.ts +0 -50
  137. package/src/effect-client/envelope.ts +0 -238
  138. package/src/effect-client/guards.ts +0 -6
  139. package/src/effect-client/request-context.ts +0 -54
  140. package/src/effect-client/request.ts +0 -51
  141. package/src/effect-client/runtime.ts +0 -122
  142. package/src/effect-client/types.ts +0 -84
@@ -1,8 +1,21 @@
1
+ import {
2
+ adoptEffectContextStorage,
3
+ EFFECT_BFF_CONTEXT_STORAGE_SHARE,
4
+ } from '@modern-js/bff-effect/context';
5
+ import type { EffectRpcBffDefinition } from './handler/types';
6
+
1
7
  type ValidatorAwareHandlerFactoryRegistry = {
2
8
  register<TFactory extends Function>(factory: TFactory): TFactory;
3
9
  is(factory: unknown): boolean;
4
10
  };
5
11
 
12
+ /**
13
+ * Module Federation share through which a backend federation host hands its
14
+ * package-private handler factory registry to the containers it initializes.
15
+ */
16
+ export const EFFECT_BFF_HANDLER_FACTORY_REGISTRY_SHARE =
17
+ '@modern-js/bff-effect/handler-factory-registry';
18
+
6
19
  function createLocalValidatorAwareHandlerFactoryRegistry(): ValidatorAwareHandlerFactoryRegistry {
7
20
  const factories = new WeakSet<Function>();
8
21
  return {
@@ -19,6 +32,8 @@ function createLocalValidatorAwareHandlerFactoryRegistry(): ValidatorAwareHandle
19
32
  function loadNodeValidatorAwareHandlerFactoryRegistry(): ValidatorAwareHandlerFactoryRegistry {
20
33
  const moduleUrl = import.meta.url;
21
34
  if (typeof moduleUrl !== 'string' || !moduleUrl.startsWith('file:')) {
35
+ // A bundled backend federation container has no file URL; its host share
36
+ // arrives through adoptEffectBffShareScope() during container init.
22
37
  return createLocalValidatorAwareHandlerFactoryRegistry();
23
38
  }
24
39
 
@@ -42,24 +57,104 @@ function loadNodeValidatorAwareHandlerFactoryRegistry(): ValidatorAwareHandlerFa
42
57
  }
43
58
  }
44
59
 
45
- const validatorAwareHandlerFactoryRegistry =
46
- process.env.MODERN_EFFECT_NODE_RUNTIME === 'true'
47
- ? loadNodeValidatorAwareHandlerFactoryRegistry()
48
- : createLocalValidatorAwareHandlerFactoryRegistry();
60
+ declare const __MODERN_EFFECT_NODE_RUNTIME__: boolean;
61
+
62
+ let validatorAwareHandlerFactoryRegistry:
63
+ | ValidatorAwareHandlerFactoryRegistry
64
+ | undefined;
65
+
66
+ function handlerFactoryRegistry(): ValidatorAwareHandlerFactoryRegistry {
67
+ validatorAwareHandlerFactoryRegistry ??=
68
+ typeof __MODERN_EFFECT_NODE_RUNTIME__ !== 'undefined' &&
69
+ __MODERN_EFFECT_NODE_RUNTIME__
70
+ ? loadNodeValidatorAwareHandlerFactoryRegistry()
71
+ : createLocalValidatorAwareHandlerFactoryRegistry();
72
+ return validatorAwareHandlerFactoryRegistry;
73
+ }
74
+
75
+ type SharedVersions = Record<string, { get?: () => unknown } | undefined>;
76
+
77
+ const adoptShared = (
78
+ shareScope: unknown,
79
+ name: string,
80
+ adopt: (value: unknown) => void,
81
+ ): Promise<void> => {
82
+ const versions =
83
+ typeof shareScope === 'object' && shareScope !== null
84
+ ? (shareScope as Record<string, SharedVersions | undefined>)[name]
85
+ : undefined;
86
+ const shared =
87
+ versions === undefined ? undefined : Object.values(versions)[0];
88
+ if (typeof shared?.get !== 'function') {
89
+ return Promise.resolve();
90
+ }
91
+ return Promise.resolve(shared.get()).then(factory =>
92
+ adopt(typeof factory === 'function' ? factory() : undefined),
93
+ );
94
+ };
95
+
96
+ /**
97
+ * @internal Called by generated backend federation containers from `init()`,
98
+ * before any expose evaluates, so factories created by `defineEffectBff`
99
+ * register with the host's registry and endpoints read the host's request
100
+ * storage instead of bundle-local ones.
101
+ */
102
+ export function adoptEffectBffShareScope(shareScope: unknown): Promise<void> {
103
+ return Promise.all([
104
+ adoptShared(
105
+ shareScope,
106
+ EFFECT_BFF_HANDLER_FACTORY_REGISTRY_SHARE,
107
+ registry =>
108
+ adoptHandlerFactoryRegistry(
109
+ registry as ValidatorAwareHandlerFactoryRegistry | undefined,
110
+ ),
111
+ ),
112
+ adoptShared(
113
+ shareScope,
114
+ EFFECT_BFF_CONTEXT_STORAGE_SHARE,
115
+ adoptEffectContextStorage,
116
+ ),
117
+ ]).then(() => undefined);
118
+ }
119
+
120
+ function adoptHandlerFactoryRegistry(
121
+ registry: ValidatorAwareHandlerFactoryRegistry | undefined,
122
+ ): void {
123
+ if (
124
+ typeof registry?.register !== 'function' ||
125
+ typeof registry.is !== 'function'
126
+ ) {
127
+ throw new Error(
128
+ `[BFF][Effect] Share ${EFFECT_BFF_HANDLER_FACTORY_REGISTRY_SHARE} must provide a handler factory registry.`,
129
+ );
130
+ }
131
+ if (
132
+ validatorAwareHandlerFactoryRegistry !== undefined &&
133
+ validatorAwareHandlerFactoryRegistry !== registry
134
+ ) {
135
+ throw new Error(
136
+ '[BFF][Effect] A backend federation container received the host handler factory registry after an Effect API already used its own. Evaluate exposes only after container init().',
137
+ );
138
+ }
139
+ validatorAwareHandlerFactoryRegistry = registry;
140
+ }
49
141
 
50
142
  type EffectBffEntryModule = {
51
143
  api?: unknown;
52
144
  layer?: unknown;
145
+ rpc?: EffectRpcBffDefinition;
53
146
  handler?: unknown;
54
147
  createHandler?: unknown;
55
148
  default?: unknown;
56
149
  };
57
150
 
58
- type LegacyEffectBffEntryShape = '`handler` export' | 'default request handler';
151
+ type UnsupportedEffectBffEntryShape =
152
+ | '`handler` export'
153
+ | 'default request handler';
59
154
 
60
155
  export type EffectBffEntryShapeFacts = {
61
156
  module: EffectBffEntryModule;
62
- legacyShape?: LegacyEffectBffEntryShape;
157
+ unsupportedShape?: UnsupportedEffectBffEntryShape;
63
158
  createHandler?: unknown;
64
159
  createHandlerValidatorAware: boolean;
65
160
  api?: unknown;
@@ -77,7 +172,7 @@ type EffectBffEntryShapePredicates = {
77
172
  export function registerValidatorAwareHandlerFactory<TFactory extends Function>(
78
173
  factory: TFactory,
79
174
  ): TFactory {
80
- return validatorAwareHandlerFactoryRegistry.register(factory);
175
+ return handlerFactoryRegistry().register(factory);
81
176
  }
82
177
 
83
178
  /**
@@ -85,7 +180,7 @@ export function registerValidatorAwareHandlerFactory<TFactory extends Function>(
85
180
  * therefore forwards strict cross-project validation into createHttpApiHandler.
86
181
  */
87
182
  export function isValidatorAwareHandlerFactory(factory: unknown): boolean {
88
- return validatorAwareHandlerFactoryRegistry.is(factory);
183
+ return handlerFactoryRegistry().is(factory);
89
184
  }
90
185
 
91
186
  export const strictEffectApproachMessage =
@@ -132,7 +227,7 @@ export function classifyEffectBffEntryModule(
132
227
  function createEntryShapeFacts(
133
228
  module: EffectBffEntryModule,
134
229
  predicates: EffectBffEntryShapePredicates,
135
- legacyShape?: LegacyEffectBffEntryShape,
230
+ unsupportedShape?: UnsupportedEffectBffEntryShape,
136
231
  ): EffectBffEntryShapeFacts {
137
232
  const createHandler =
138
233
  typeof module.createHandler === 'function'
@@ -143,7 +238,7 @@ function createEntryShapeFacts(
143
238
 
144
239
  return {
145
240
  module,
146
- legacyShape,
241
+ unsupportedShape,
147
242
  createHandler,
148
243
  createHandlerValidatorAware:
149
244
  createHandler !== undefined &&
@@ -0,0 +1,36 @@
1
+ import {
2
+ createEffectBffEdgeDispatcher,
3
+ type EffectBffEdgeHandlerOptions,
4
+ } from './edge-dispatcher';
5
+
6
+ type OperationContracts = NonNullable<
7
+ EffectBffEdgeHandlerOptions['crossProjectPolicy']
8
+ >['expectedOperationContracts'];
9
+
10
+ export function createEffectBffEdgeDispatcherFactory(
11
+ module: EffectBffEdgeHandlerOptions['module'],
12
+ generatedOperationContracts: OperationContracts,
13
+ ) {
14
+ return (options?: Omit<EffectBffEdgeHandlerOptions, 'module'>) => {
15
+ const policy = options?.crossProjectPolicy;
16
+ const crossProjectPolicy =
17
+ policy === undefined ||
18
+ policy === null ||
19
+ policy.expectedOperationContracts === null ||
20
+ typeof policy.expectedOperationContracts !== 'object' ||
21
+ Array.isArray(policy.expectedOperationContracts)
22
+ ? policy
23
+ : {
24
+ ...policy,
25
+ expectedOperationContracts: {
26
+ ...policy.expectedOperationContracts,
27
+ ...generatedOperationContracts,
28
+ },
29
+ };
30
+ return createEffectBffEdgeDispatcher({
31
+ ...options,
32
+ crossProjectPolicy,
33
+ module,
34
+ });
35
+ };
36
+ }
@@ -1,4 +1,5 @@
1
1
  // @effect-diagnostics anyUnknownInErrorContext:off asyncFunction:off globalDate:off globalTimers:off newPromise:off strictBooleanExpressions:off
2
+ import * as Effect from 'effect/Effect';
2
3
  import {
3
4
  type DataBatchResponsePayload,
4
5
  DEFAULT_DATA_BATCH_HEADER,
@@ -7,6 +8,7 @@ import {
7
8
  isPlainObject,
8
9
  normalizeMethod as normalizeItemMethod,
9
10
  } from '../../data-platform';
11
+ import { normalizeBatchLimits } from '../../data-platform/batch/options';
10
12
  import {
11
13
  decodeBatchBody,
12
14
  isBatchBody,
@@ -17,7 +19,6 @@ import {
17
19
  createBatchValidationResponse,
18
20
  isBatchRequestPayload,
19
21
  mapWithConcurrency,
20
- normalizeBatchAllowedMethods,
21
22
  normalizeBatchPath,
22
23
  promiseWithTimeout,
23
24
  toBatchItemError,
@@ -128,14 +129,11 @@ export function createDataPlatformBatchRequestHandler<TContext>(options: {
128
129
  const dataPlatformBatchOptions = options.dataPlatform?.batch;
129
130
  const batchEnabled = dataPlatformBatchOptions?.enabled !== false;
130
131
  const batchPath = normalizeBatchPath(dataPlatformBatchOptions?.endpoint);
131
- const batchMaxSize = Math.max(
132
- 1,
133
- dataPlatformBatchOptions?.maxBatchSize ?? 16,
134
- );
135
- const batchMaxBytes = Math.max(
136
- 1024,
137
- dataPlatformBatchOptions?.maxBatchBytes ?? 64 * 1024,
138
- );
132
+ const {
133
+ maxBatchSize: batchMaxSize,
134
+ maxBatchBytes: batchMaxBytes,
135
+ allowedMethods: batchAllowedMethods,
136
+ } = normalizeBatchLimits(dataPlatformBatchOptions);
139
137
  const batchConcurrency = Math.max(
140
138
  1,
141
139
  dataPlatformBatchOptions?.maxConcurrency ?? 4,
@@ -144,9 +142,6 @@ export function createDataPlatformBatchRequestHandler<TContext>(options: {
144
142
  0,
145
143
  dataPlatformBatchOptions?.requestTimeoutMs ?? 10_000,
146
144
  );
147
- const batchAllowedMethods = normalizeBatchAllowedMethods(
148
- dataPlatformBatchOptions?.allowedMethods,
149
- );
150
145
  const envelopeHeader =
151
146
  options.dataPlatform?.envelopeHeader || DEFAULT_DATA_ENVELOPE_HEADER;
152
147
  const normalizedEnvelopeHeader = envelopeHeader.toLowerCase();
@@ -417,14 +412,16 @@ export function createDataPlatformBatchRequestHandler<TContext>(options: {
417
412
  );
418
413
  } catch (error) {
419
414
  if (error instanceof BatchItemTimeoutError) {
420
- console.error({
421
- event: 'bff.batch.item.timeout',
422
- batchId: payload.batchId,
423
- itemId,
424
- method: itemMethod,
425
- path: itemPathname,
426
- errorName: error.name,
427
- });
415
+ Effect.runSync(
416
+ Effect.logError({
417
+ event: 'bff.batch.item.timeout',
418
+ batchId: payload.batchId,
419
+ itemId,
420
+ method: itemMethod,
421
+ path: itemPathname,
422
+ errorName: error.name,
423
+ }),
424
+ );
428
425
  return toBatchItemError(
429
426
  itemId,
430
427
  504,
@@ -435,14 +432,16 @@ export function createDataPlatformBatchRequestHandler<TContext>(options: {
435
432
  return toBatchResponseItem(itemId, error, itemMethod === 'HEAD');
436
433
  }
437
434
 
438
- console.error({
439
- event: 'bff.batch.item.failure',
440
- batchId: payload.batchId,
441
- itemId,
442
- method: itemMethod,
443
- path: itemPathname,
444
- errorName: error instanceof Error ? error.name : 'UnknownError',
445
- });
435
+ Effect.runSync(
436
+ Effect.logError({
437
+ event: 'bff.batch.item.failure',
438
+ batchId: payload.batchId,
439
+ itemId,
440
+ method: itemMethod,
441
+ path: itemPathname,
442
+ errorName: error instanceof Error ? error.name : 'UnknownError',
443
+ }),
444
+ );
446
445
  return toBatchItemError(itemId, 500, 'Internal Server Error');
447
446
  } finally {
448
447
  request.signal.removeEventListener('abort', abortFromBatchRequest);
@@ -1,15 +1,14 @@
1
1
  // @effect-diagnostics anyUnknownInErrorContext:off asyncFunction:off globalDate:off globalTimers:off newPromise:off strictBooleanExpressions:off
2
2
 
3
+ import * as Data from 'effect/Data';
3
4
  import {
4
5
  type DataBatchRequestPayload,
5
6
  type DataBatchResponseItem,
6
7
  DEFAULT_DATA_BATCH_ENDPOINT,
7
8
  isPlainObject,
8
- measureTextBytes,
9
9
  normalizeMethod as normalizeItemMethod,
10
10
  } from '../../data-platform';
11
11
  import { encodeBatchText } from '../../data-platform/batch/protocol';
12
- import { toHeaderRecord } from '../../headers';
13
12
 
14
13
  export { toHeaderRecord } from '../../headers';
15
14
 
@@ -23,16 +22,6 @@ export function normalizeBatchPath(pathname: string | undefined) {
23
22
  return pathname as `/${string}`;
24
23
  }
25
24
 
26
- export function normalizeBatchAllowedMethods(
27
- allowedMethods: string[] | undefined,
28
- ) {
29
- const source =
30
- Array.isArray(allowedMethods) && allowedMethods.length > 0
31
- ? allowedMethods
32
- : ['GET'];
33
- return new Set(source.map(method => method.toUpperCase()));
34
- }
35
-
36
25
  type ParsedBatchRequestPayload = Omit<DataBatchRequestPayload, 'items'> & {
37
26
  items: unknown[];
38
27
  };
@@ -117,7 +106,14 @@ export function toBatchItemError(
117
106
  };
118
107
  }
119
108
 
120
- export class BatchItemTimeoutError extends Error {}
109
+ export class BatchItemTimeoutError extends Data.TaggedError(
110
+ 'BatchItemTimeoutError',
111
+ )<{ readonly message: string }> {
112
+ constructor(message: string) {
113
+ super({ message });
114
+ this.name = 'Error';
115
+ }
116
+ }
121
117
 
122
118
  export function promiseWithTimeout<T>(
123
119
  effect: Promise<T>,
@@ -7,11 +7,9 @@ import { mergeDataPlatformOptions } from './envelope';
7
7
  import { createHttpApiHandler } from './http';
8
8
  import { createRpcApiHandler } from './rpc';
9
9
  import type {
10
- EffectApiPromiseClientFromApi,
11
10
  EffectBffDefinition,
12
11
  EffectBffHandlerFactory,
13
12
  EffectBffRuntime,
14
- EffectDataPlatformValidationOptions,
15
13
  EffectRpcBffDefinition,
16
14
  EffectRpcBffHandlerFactory,
17
15
  EffectRpcRuntimeLayer,
@@ -51,55 +49,12 @@ export function defineEffectBff<
51
49
  validateRequest: options?.validateRequest,
52
50
  });
53
51
  });
54
- const client = createLoaderMaterializedClientPlaceholder<TApi>();
55
52
  return {
56
53
  ...definition,
57
54
  createHandler,
58
- client,
59
55
  };
60
56
  }
61
57
 
62
- const LOADER_CLIENT_IGNORED_KEYS = new Set<PropertyKey>([
63
- 'then',
64
- 'catch',
65
- 'finally',
66
- 'toJSON',
67
- '$$typeof',
68
- ]);
69
-
70
- /**
71
- * `defineEffectBff(...).client` is only materialized when the entry module
72
- * is imported through the `@api/index` transform (the webpack/rspack
73
- * loader swaps the module for generated client code). Importing the entry
74
- * directly (server code, scripts, tests) used to return a `client` typed as
75
- * fully functional but `undefined` at runtime — a bare TypeError with zero
76
- * explanation. This placeholder keeps the type contract while failing with
77
- * an actionable error on first property access.
78
- */
79
- function createLoaderMaterializedClientPlaceholder<
80
- TApi extends HttpApi.Constraint,
81
- >(): EffectApiPromiseClientFromApi<TApi> {
82
- const explain = (property: PropertyKey): never => {
83
- throw new Error(
84
- `[BFF][Effect] client.${String(property)} is not available here: the typed client only exists when the API entry is imported through the "@api/index" transformed path (the BFF loader replaces it with generated client code). On the server, use HttpApiClient or call the Effect layer directly.`,
85
- );
86
- };
87
-
88
- return new Proxy(Object.create(null), {
89
- get(_target, property) {
90
- // Keep async/inspection protocols (await, console.log, React internals)
91
- // from throwing so the placeholder stays inert until really used.
92
- if (
93
- typeof property === 'symbol' ||
94
- LOADER_CLIENT_IGNORED_KEYS.has(property)
95
- ) {
96
- return undefined;
97
- }
98
- return explain(property);
99
- },
100
- }) as EffectApiPromiseClientFromApi<TApi>;
101
- }
102
-
103
58
  export function defineEffectRpcBff<
104
59
  TRpcs extends Rpc.Any = Rpc.Any,
105
60
  TLayer extends EffectRpcRuntimeLayer<TRpcs> = EffectRpcRuntimeLayer<TRpcs>,
@@ -66,8 +66,8 @@ export function createHttpApiHandler<
66
66
  ) => {
67
67
  // Policy seam first: every HttpApi request, direct or batched item,
68
68
  // passes through here, so batch fan-out cannot bypass validator. The
69
- // interceptor receives the original request and may own malformed or
70
- // legacy payloads; delegated requests still pass through body validation.
69
+ // interceptor receives the original request; delegated requests still
70
+ // pass through body validation.
71
71
  const policyDenial = options.validateRequest?.(request);
72
72
  if (policyDenial) {
73
73
  return policyDenial;
@@ -1,7 +1,6 @@
1
1
  // @effect-diagnostics anyUnknownInErrorContext:off asyncFunction:off globalDate:off globalTimers:off newPromise:off strictBooleanExpressions:off
2
2
  import type { FileSystem, Path } from 'effect';
3
3
  import * as Context from 'effect/Context';
4
- import type * as EffectType from 'effect/Effect';
5
4
  import type * as Layer from 'effect/Layer';
6
5
  import type { Etag, HttpPlatform, HttpRouter } from 'effect/unstable/http';
7
6
  import type {
@@ -43,8 +42,7 @@ export type EffectRpcSerialization =
43
42
  | 'json'
44
43
  | 'ndjson'
45
44
  | 'jsonRpc'
46
- | 'ndJsonRpc'
47
- | 'msgPack';
45
+ | 'ndJsonRpc';
48
46
 
49
47
  export type EffectRpcRuntimeLayer<TRpcs extends Rpc.Any = Rpc.Any> =
50
48
  Layer.Layer<
@@ -77,22 +75,6 @@ export type EffectRpcBffHandlerOptions = Pick<
77
75
  | 'disableFatalDefects'
78
76
  >;
79
77
 
80
- type EffectApiPromiseClient<TClient> = {
81
- [GroupName in keyof TClient]: {
82
- [EndpointName in keyof TClient[GroupName]]: TClient[GroupName][EndpointName] extends (
83
- ...args: infer TArgs
84
- ) => infer TResult
85
- ? TResult extends EffectType.Effect<unknown, unknown, unknown>
86
- ? (
87
- ...args: TArgs
88
- ) => Promise<
89
- Exclude<EffectType.Success<TResult>, readonly [unknown, unknown]>
90
- >
91
- : never
92
- : never;
93
- };
94
- };
95
-
96
78
  export type EffectApiClientFromApi<
97
79
  TApi extends HttpApi.Constraint = HttpApi.Top,
98
80
  > =
@@ -104,10 +86,6 @@ export type EffectApiClientFromApi<
104
86
  >
105
87
  : never;
106
88
 
107
- export type EffectApiPromiseClientFromApi<
108
- TApi extends HttpApi.Constraint = HttpApi.Top,
109
- > = EffectApiPromiseClient<EffectApiClientFromApi<TApi>>;
110
-
111
89
  export type EffectBffDefinition<
112
90
  TApi extends HttpApi.Constraint = HttpApi.Top,
113
91
  TLayer extends EffectRuntimeLayer = EffectRuntimeLayer,
@@ -147,12 +125,6 @@ export type EffectDataPlatformBatchOptions = {
147
125
  * Defaults to `65536` (64KiB).
148
126
  */
149
127
  maxBatchBytes?: number;
150
- /**
151
- * Client-side micro-batch flush window in milliseconds.
152
- * Server runtime ignores this value and passes it through for codegen.
153
- * Defaults to `8`.
154
- */
155
- flushIntervalMs?: number;
156
128
  /**
157
129
  * Maximum per-batch internal request concurrency.
158
130
  * Defaults to `4`.
@@ -227,8 +199,8 @@ export type EffectRequestInterceptor = (options: {
227
199
  }) => Response | Promise<Response>;
228
200
 
229
201
  export type EffectBffHandlerFactory<
230
- TApi extends HttpApi.Constraint = HttpApi.Top,
231
- TLayer extends EffectRuntimeLayer = EffectRuntimeLayer,
202
+ _TApi extends HttpApi.Constraint = HttpApi.Top,
203
+ _TLayer extends EffectRuntimeLayer = EffectRuntimeLayer,
232
204
  > = (options?: {
233
205
  openapi?: EffectBffOpenApiConfig;
234
206
  rpc?: Partial<EffectRpcBffHandlerOptions>;
@@ -241,7 +213,6 @@ export type EffectBffRuntime<
241
213
  TLayer extends EffectRuntimeLayer = EffectRuntimeLayer,
242
214
  > = {
243
215
  createHandler: EffectBffHandlerFactory<TApi, TLayer>;
244
- client: EffectApiPromiseClientFromApi<TApi>;
245
216
  };
246
217
 
247
218
  export type EffectBffOpenApiConfig =
@@ -14,7 +14,6 @@ export { defineEffectBff, defineEffectRpcBff } from './handler/definition';
14
14
  export { createHttpApiHandler } from './handler/http';
15
15
  export type {
16
16
  EffectApiClientFromApi,
17
- EffectApiPromiseClientFromApi,
18
17
  EffectBffDefinition,
19
18
  EffectBffHandlerFactory,
20
19
  EffectBffOpenApiConfig,
@@ -6,7 +6,7 @@ export {
6
6
  runWithEffectContext,
7
7
  useEffectContext,
8
8
  useOperationContext,
9
- } from './context';
9
+ } from '@modern-js/bff-effect/context';
10
10
  export {
11
11
  createEffectBffEdgeHandler,
12
12
  createEffectBffTestHandler,
@@ -12,6 +12,7 @@ import {
12
12
  type EffectBffOpenApiConfig,
13
13
  type EffectDataPlatformValidationOptions,
14
14
  type EffectRequestValidator,
15
+ type EffectRpcBffDefinition,
15
16
  type EffectRuntimeLayer,
16
17
  } from './handler';
17
18
  import type { EffectContext } from './operation-context';
@@ -33,6 +34,7 @@ export type EffectBffHandlerFactory = (options?: {
33
34
  export type EffectApiModule = {
34
35
  api?: unknown;
35
36
  layer?: unknown;
37
+ rpc?: EffectRpcBffDefinition;
36
38
  handler?: EffectBffRequestHandler;
37
39
  createHandler?: EffectBffHandlerFactory;
38
40
  default?: unknown;
@@ -67,7 +69,7 @@ function shareDispose(dispose: () => Promise<void>) {
67
69
  };
68
70
  }
69
71
 
70
- function rejectLegacyEffectModuleShape(
72
+ function rejectUnsupportedEffectModuleShape(
71
73
  options: ResolveEffectBffModuleHandlerOptions,
72
74
  shape: string,
73
75
  ) {
@@ -119,12 +121,17 @@ function resolveClassifiedEffectBffModuleHandler(
119
121
  return null;
120
122
  }
121
123
 
122
- if (facts.legacyShape) {
123
- if (rejectLegacyEffectModuleShape(options, facts.legacyShape)) {
124
+ if (facts.unsupportedShape) {
125
+ if (rejectUnsupportedEffectModuleShape(options, facts.unsupportedShape)) {
124
126
  return null;
125
127
  }
126
128
  }
127
129
 
130
+ if (facts.module.rpc !== undefined && !facts.createHandlerValidatorAware) {
131
+ rejectUnsupportedEffectModuleShape(options, 'unbranded RPC runtime');
132
+ return null;
133
+ }
134
+
128
135
  if (typeof facts.createHandler === 'function') {
129
136
  if (facts.createHandlerValidatorAware) {
130
137
  const factory = facts.createHandler as EffectBffHandlerFactory;
@@ -137,7 +144,7 @@ function resolveClassifiedEffectBffModuleHandler(
137
144
  }
138
145
 
139
146
  if (facts.api === undefined || !facts.hasRuntimeLayer) {
140
- rejectLegacyEffectModuleShape(
147
+ rejectUnsupportedEffectModuleShape(
141
148
  options,
142
149
  'unbranded `createHandler` export',
143
150
  );
@@ -6,7 +6,7 @@ import {
6
6
  BFF_TRACEPARENT_HEADER,
7
7
  type OperationContext,
8
8
  parseTraceparent,
9
- } from '@modern-js/create-request';
9
+ } from '@modern-js/runtime-extensions/request-context';
10
10
 
11
11
  export type EffectContext = {
12
12
  request: Request;
@@ -21,10 +21,6 @@ export type EffectContextStorage = {
21
21
  run: <TResult>(value: EffectContext, cb: () => TResult) => TResult;
22
22
  };
23
23
 
24
- export const kEffectContextStorage = Symbol.for(
25
- 'modernjs.plugin-bff.effectContextStorage',
26
- );
27
-
28
24
  export type CreateEffectOperationContextOptions = Omit<
29
25
  EffectContext,
30
26
  'operationContext'
@@ -128,7 +124,7 @@ export const createEffectOperationContext = ({
128
124
  : {}),
129
125
  routePath: servicePath,
130
126
  method: (method || request.method || 'GET').toUpperCase(),
131
- source: 'effect-adapter',
127
+ source: 'effect-server',
132
128
  ...(path && path !== servicePath
133
129
  ? { attributes: { mountedPath: path } }
134
130
  : {}),
@@ -12,8 +12,6 @@ export function getRpcSerializationLayer(
12
12
  return RpcSerialization.layerJsonRpc();
13
13
  case 'ndJsonRpc':
14
14
  return RpcSerialization.layerNdJsonRpc();
15
- case 'msgPack':
16
- return RpcSerialization.layerMsgPack;
17
15
  default:
18
16
  return RpcSerialization.layerJsonRpc();
19
17
  }