@bleedingdev/modern-js-plugin-bff 3.4.0-ultramodern.2 → 3.4.0-ultramodern.20

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.
@@ -17,16 +17,18 @@ function isEffectServiceContext(context) {
17
17
  return 'object' == typeof context && null !== context && 'mapUnsafe' in context;
18
18
  }
19
19
  const emptyEffectServiceContext = __rspack_external_effect_Context_f1289ca3.empty();
20
+ const strictEffectApproachMessage = '[BFF][Effect] strictEffectApproach is enforced: Effect API entries must export defineEffectBff(...) or a { api, layer } HttpApi module. Raw handler exports, default request handlers, and unbranded custom createHandler factories are not valid Effect API entries.';
21
+ function rejectLegacyEffectModuleShape(options, shape) {
22
+ options.onWarning?.(`${strictEffectApproachMessage} Rejected ${shape}.`);
23
+ return true;
24
+ }
20
25
  function callEffectBffRequestHandler(handler, request, context) {
21
26
  return void 0 === context ? handler(request) : handler(request, context);
22
27
  }
23
- function createLoadedHandler(webHandler, appliesRequestValidator) {
28
+ function createLoadedHandler(webHandler) {
24
29
  return {
25
30
  handler: (request, context)=>callEffectBffRequestHandler(webHandler.handler, request, context),
26
- dispose: webHandler.dispose,
27
- ...appliesRequestValidator ? {
28
- appliesRequestValidator: true
29
- } : {}
31
+ dispose: webHandler.dispose
30
32
  };
31
33
  }
32
34
  function createLoadedHttpApiHandler(webHandler) {
@@ -35,18 +37,23 @@ function createLoadedHttpApiHandler(webHandler) {
35
37
  const effectContext = isEffectServiceContext(context) ? context : emptyEffectServiceContext;
36
38
  return webHandler.handler(request, effectContext);
37
39
  },
38
- dispose: webHandler.dispose,
39
- appliesRequestValidator: true
40
+ dispose: webHandler.dispose
40
41
  };
41
42
  }
42
43
  function resolveNormalizedEffectBffModuleHandler(normalizedModule, options = {}) {
43
- if (isRequestHandler(normalizedModule.handler)) return {
44
- handler: normalizedModule.handler
45
- };
44
+ if (isRequestHandler(normalizedModule.handler)) {
45
+ if (rejectLegacyEffectModuleShape(options, '`handler` export')) return null;
46
+ return {
47
+ handler: normalizedModule.handler
48
+ };
49
+ }
46
50
  const entry = normalizedModule.default;
47
- if (isRequestHandler(entry)) return {
48
- handler: entry
49
- };
51
+ if (isRequestHandler(entry)) {
52
+ if (rejectLegacyEffectModuleShape(options, 'default request handler')) return null;
53
+ return {
54
+ handler: entry
55
+ };
56
+ }
50
57
  if (isRecord(entry)) normalizedModule = {
51
58
  ...normalizedModule,
52
59
  ...entry
@@ -58,19 +65,22 @@ function resolveNormalizedEffectBffModuleHandler(normalizedModule, options = {})
58
65
  handler: maybeHandler
59
66
  };
60
67
  }
61
- if (isRequestHandler(normalizedModule.handler)) return {
62
- handler: normalizedModule.handler
63
- };
68
+ if (isRequestHandler(normalizedModule.handler)) {
69
+ if (rejectLegacyEffectModuleShape(options, '`handler` export')) return null;
70
+ return {
71
+ handler: normalizedModule.handler
72
+ };
73
+ }
64
74
  if ('function' == typeof normalizedModule.createHandler) {
65
75
  const factory = normalizedModule.createHandler;
66
76
  const validatorAware = isValidatorAwareHandlerFactory(factory);
67
- if (!validatorAware && void 0 !== options.validateRequest) options.onWarning?.('[BFF][Effect] Custom createHandler export detected: it cannot be verified to apply validateRequest (cross-project policy), so the policy is enforced by the adapter middleware on the outer request. Batched calls will be denied at the batch POST (it carries no per-operation contract); export defineEffectBff(...) to get per-batch-item enforcement.');
77
+ if (!validatorAware && rejectLegacyEffectModuleShape(options, 'unbranded `createHandler` export')) return null;
68
78
  const webHandler = factory({
69
79
  openapi: options.openapi,
70
80
  dataPlatform: options.dataPlatform,
71
81
  validateRequest: options.validateRequest
72
82
  });
73
- return createLoadedHandler(webHandler, validatorAware);
83
+ return createLoadedHandler(webHandler);
74
84
  }
75
85
  if (isEffectApiDefinition(normalizedModule)) {
76
86
  options.onWarning?.('[BFF][Effect] Detected { api, layer } export without createHandler. Prefer `defineEffectBff(...)` from @modern-js/plugin-bff/server to avoid module instance mismatch.');
@@ -94,17 +104,26 @@ function resolveEffectBffModuleHandler(mod, options = {}) {
94
104
  ...value
95
105
  };
96
106
  };
97
- if (isRequestHandler(normalizedModule.handler)) return Promise.resolve({
98
- handler: normalizedModule.handler
99
- });
107
+ if (isRequestHandler(normalizedModule.handler)) {
108
+ if (rejectLegacyEffectModuleShape(options, '`handler` export')) return Promise.resolve(null);
109
+ return Promise.resolve({
110
+ handler: normalizedModule.handler
111
+ });
112
+ }
100
113
  const entry = normalizedModule.default;
101
- if (isRequestHandler(entry)) return Promise.resolve({
102
- handler: entry
103
- });
114
+ if (isRequestHandler(entry)) {
115
+ if (rejectLegacyEffectModuleShape(options, 'default request handler')) return Promise.resolve(null);
116
+ return Promise.resolve({
117
+ handler: entry
118
+ });
119
+ }
104
120
  if ('function' == typeof entry && 0 === entry.length) return Promise.resolve(entry()).then((out)=>{
105
- if (isRequestHandler(out)) return {
106
- handler: out
107
- };
121
+ if (isRequestHandler(out)) {
122
+ if (rejectLegacyEffectModuleShape(options, 'default factory returning a request handler')) return null;
123
+ return {
124
+ handler: out
125
+ };
126
+ }
108
127
  mergeRuntimeExports(out);
109
128
  return resolveNormalizedEffectBffModuleHandler(normalizedModule, options);
110
129
  });
@@ -5,6 +5,7 @@ import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/
5
5
  import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "effect/unstable/httpapi";
6
6
  import { Rpc, RpcClient, RpcGroup, RpcSchema, RpcSerialization } from "effect/unstable/rpc";
7
7
  import * as __rspack_external_effect_Schema_f8472650 from "effect/Schema";
8
+ import * as __rspack_external_effect_unstable_http_HttpClientError_b9ead235 from "effect/unstable/http/HttpClientError";
8
9
  import * as __rspack_external_effect_Data_7c33fca9 from "effect/Data";
9
10
  import * as __rspack_external_effect_Exit_a10cfb96 from "effect/Exit";
10
11
  import * as __rspack_external_effect_ManagedRuntime_94dd2709 from "effect/ManagedRuntime";
@@ -104,4 +105,4 @@ function makeEffectRpcClient(group, options) {
104
105
  });
105
106
  }
106
107
  const runEffectRequest = __rspack_external_effect_Effect_194ac36c.runPromise;
107
- export { EffectRpcClientError, HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiSchema, Rpc, RpcClient, RpcGroup, RpcSchema, RpcSerialization, __rspack_external_effect_Effect_194ac36c as Effect, __rspack_external_effect_Layer_16f7a8fc as Layer, __rspack_external_effect_Schema_f8472650 as Schema, makeEffectHttpApiClient, makeEffectRpcClient, mask, runEffectRequest, runEffectView, view };
108
+ export { EffectRpcClientError, HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiSchema, Rpc, RpcClient, RpcGroup, RpcSchema, RpcSerialization, __rspack_external_effect_Effect_194ac36c as Effect, __rspack_external_effect_Layer_16f7a8fc as Layer, __rspack_external_effect_Schema_f8472650 as Schema, __rspack_external_effect_unstable_http_HttpClientError_b9ead235 as HttpClientError, makeEffectHttpApiClient, makeEffectRpcClient, mask, runEffectRequest, runEffectView, view };
@@ -127,14 +127,14 @@ const __generated = createGeneratedEffectClient(__manifest, __config, __requestR
127
127
  const client = __generated.client;
128
128
  const operationManifest = __generated.operationManifest;
129
129
  const createEffectRequestContext = __generated.createEffectRequestContext;
130
- const effectBffModule = {
130
+ const apiModule = {
131
131
  client,
132
132
  operationManifest,
133
133
  createEffectRequestContext,
134
134
  };
135
135
 
136
136
  export { client, createEffectRequestContext, operationManifest };
137
- export default effectBffModule;
137
+ export default apiModule;
138
138
  `;
139
139
  }
140
140
  function renderClientShape(endpoints, valueType) {
@@ -207,13 +207,13 @@ export declare const createEffectRequestContext: (
207
207
  requestContext: Record<string, unknown>,
208
208
  ) => EffectRequestContext;
209
209
  export declare const operationManifest: GeneratedEffectOperationManifest;
210
- declare const effectBffModule: {
210
+ declare const apiModule: {
211
211
  client: GeneratedEffectClient;
212
212
  createEffectRequestContext: typeof createEffectRequestContext;
213
213
  operationManifest: GeneratedEffectOperationManifest;
214
214
  };
215
215
 
216
- export default effectBffModule;
216
+ export default apiModule;
217
217
  `;
218
218
  }
219
219
  async function generateEffectClient(options) {
@@ -236,9 +236,14 @@ async function generateEffectClientCode(options) {
236
236
  }
237
237
  function resolveEffectEntryFile(options) {
238
238
  const { appDir, apiDir, effectEntry } = options;
239
- const defaultEntry = path.resolve(apiDir, 'effect', 'index');
240
- const entryWithoutExt = effectEntry ? path.isAbsolute(effectEntry) ? effectEntry : path.resolve(appDir, effectEntry) : defaultEntry;
241
- if (path.extname(entryWithoutExt)) return fs.existsSync(entryWithoutExt) ? entryWithoutExt : void 0;
242
- return findExists(JS_OR_TS_EXTS.map((ext)=>`${entryWithoutExt}${ext}`));
239
+ const resolveEntry = (entryWithoutExt)=>{
240
+ if (path.extname(entryWithoutExt)) return fs.existsSync(entryWithoutExt) ? entryWithoutExt : void 0;
241
+ return findExists(JS_OR_TS_EXTS.map((ext)=>`${entryWithoutExt}${ext}`));
242
+ };
243
+ if (effectEntry) {
244
+ const entryWithoutExt = path.isAbsolute(effectEntry) ? effectEntry : path.resolve(appDir, effectEntry);
245
+ return resolveEntry(entryWithoutExt);
246
+ }
247
+ return resolveEntry(path.resolve(apiDir, 'index'));
243
248
  }
244
249
  export { generateEffectClient, generateEffectClientCode, renderEffectClientDeclaration, resolveEffectEntryFile };
@@ -1,6 +1,5 @@
1
1
  import __rslib_shim_module__ from "node:module";
2
2
  const require = /*#__PURE__*/ __rslib_shim_module__.createRequire(/*#__PURE__*/ (()=>import.meta.url)());
3
- import { ApiRouter } from "@modern-js/bff-core";
4
3
  import { API_DIR, compatibleRequire, findExists, fs, isProd, logger } from "@modern-js/utils";
5
4
  import { HttpApi } from "effect/unstable/httpapi";
6
5
  import path from "path";
@@ -54,43 +53,12 @@ class EffectAdapter {
54
53
  const { appDirectory, apiDirectory } = this.api.getServerContext();
55
54
  const bffConfig = this.api.getServerConfig()?.bff;
56
55
  const configuredEntry = bffConfig?.effect?.entry;
57
- const defaultEntry = path.resolve(appDirectory || process.cwd(), apiDirectory || API_DIR, 'effect', 'index');
58
- const entryWithoutExt = configuredEntry ? path.isAbsolute(configuredEntry) ? configuredEntry : path.resolve(appDirectory || process.cwd(), configuredEntry) : defaultEntry;
59
- return resolveJsOrTsEntry(entryWithoutExt);
60
- }
61
- isApiRequestPath(requestPath, prefix, enableHandleWeb) {
62
- if (!enableHandleWeb) return true;
63
- const normalized = normalizePrefix(prefix);
64
- if (!normalized) return true;
65
- return requestPath === normalized || requestPath.startsWith(`${normalized}/`);
66
- }
67
- async collectLambdaContractSources() {
68
- try {
69
- const serverContext = this.api.getServerContext();
70
- const appDir = serverContext.distDirectory || serverContext.appDirectory;
71
- if (!appDir) return [];
72
- const apiDir = 'string' == typeof serverContext.apiDirectory ? serverContext.apiDirectory : path.resolve(appDir, API_DIR);
73
- const lambdaDir = 'string' == typeof serverContext.lambdaDirectory ? serverContext.lambdaDirectory : path.join(apiDir, 'lambda');
74
- if (!await fs.pathExists(lambdaDir)) return [];
75
- const apiRouter = new ApiRouter({
76
- appDir,
77
- apiDir,
78
- lambdaDir,
79
- prefix: this.prefix,
80
- httpMethodDecider: this.api.getServerConfig()?.bff?.httpMethodDecider
81
- });
82
- const handlerInfos = await apiRouter.getApiHandlers();
83
- return handlerInfos.map((info)=>({
84
- name: info.name,
85
- httpMethod: info.httpMethod,
86
- routePath: info.routePath,
87
- filename: info.filename,
88
- handler: info.handler
89
- }));
90
- } catch (error) {
91
- logger.warn(`[BFF][Effect] Failed to derive lambda operation contracts for the cross-project policy: ${String(error)}`);
92
- return [];
56
+ if (configuredEntry) {
57
+ const entryWithoutExt = path.isAbsolute(configuredEntry) ? configuredEntry : path.resolve(appDirectory || process.cwd(), configuredEntry);
58
+ return resolveJsOrTsEntry(entryWithoutExt);
93
59
  }
60
+ const apiRoot = path.resolve(appDirectory || process.cwd(), apiDirectory || API_DIR);
61
+ return resolveJsOrTsEntry(path.resolve(apiRoot, 'index'));
94
62
  }
95
63
  async refreshCrossProjectPolicy(mod) {
96
64
  let contractSources = [];
@@ -106,24 +74,15 @@ class EffectAdapter {
106
74
  } catch (error) {
107
75
  logger.warn(`[BFF][Effect] Failed to reflect HttpApi endpoints for the cross-project policy: ${String(error)}`);
108
76
  }
109
- let policy = resolveAdapterCrossProjectPolicy(this.api, contractSources);
110
- if (policy?.enabled) {
111
- const lambdaSources = await this.collectLambdaContractSources();
112
- if (lambdaSources.length > 0) {
113
- contractSources = [
114
- ...contractSources,
115
- ...lambdaSources
116
- ];
117
- policy = resolveAdapterCrossProjectPolicy(this.api, contractSources);
118
- }
119
- }
77
+ const policy = resolveAdapterCrossProjectPolicy(this.api, contractSources);
120
78
  this.crossProjectPolicy = policy;
121
79
  if (this.crossProjectPolicy?.enabled && 0 === contractSources.length) logger.warn('[BFF][Effect] Cross-project policy is enabled but no HttpApi endpoints could be reflected; operation-contract matching is disabled for this server (envelope and operation-context checks still apply).');
122
80
  }
123
81
  async loadEffectHandlerFromModule(mod) {
82
+ const effectConfig = this.api.getServerConfig()?.bff?.effect;
124
83
  return resolveEffectBffModuleHandler(mod, {
125
- openapi: this.api.getServerConfig()?.bff?.effect?.openapi,
126
- dataPlatform: this.api.getServerConfig()?.bff?.effect?.dataPlatform,
84
+ openapi: effectConfig?.openapi,
85
+ dataPlatform: effectConfig?.dataPlatform,
127
86
  validateRequest: (request)=>checkCrossProjectPolicyForRequest(request, this.crossProjectPolicy),
128
87
  onWarning: (message)=>{
129
88
  logger.warn(message);
@@ -157,13 +116,12 @@ class EffectAdapter {
157
116
  await this.refreshCrossProjectPolicy(mod);
158
117
  const loaded = await this.loadEffectHandlerFromModule(mod);
159
118
  if (!loaded) {
160
- logger.warn(`[BFF][Effect] Invalid Effect entry module: ${entryFile}. Export { api, layer } or handler.`);
119
+ logger.warn(`[BFF][Effect] Invalid Effect entry module: ${entryFile}. Export defineEffectBff(...) or a { api, layer } HttpApi module.`);
161
120
  this.handler = null;
162
121
  return;
163
122
  }
164
123
  this.handler = loaded.handler;
165
124
  this.dispose = loaded.dispose || null;
166
- this.policyEnforcedInMiddleware = !loaded.appliesRequestValidator;
167
125
  }
168
126
  async disposeCurrentHandler() {
169
127
  if (!this.dispose) return;
@@ -215,7 +173,6 @@ class EffectAdapter {
215
173
  this.handler = null;
216
174
  this.dispose = null;
217
175
  this.prefix = '/api';
218
- this.policyEnforcedInMiddleware = false;
219
176
  this.registerMiddleware = async (options)=>{
220
177
  const { prefix, enableHandleWeb } = options;
221
178
  const { bffRuntimeFramework, middlewares: globalMiddlewares } = this.api.getServerContext();
@@ -226,7 +183,7 @@ class EffectAdapter {
226
183
  this.prefix = prefix || this.prefix;
227
184
  await this.reloadHandler();
228
185
  this.effectMiddleware = {
229
- name: 'effect-bff-handler',
186
+ name: 'effect-api-handler',
230
187
  path: enableHandleWeb ? '*' : `${prefix}/*`,
231
188
  method: 'all',
232
189
  order: 'post',
@@ -234,11 +191,7 @@ class EffectAdapter {
234
191
  handler: async (c, next)=>{
235
192
  if (!this.handler) {
236
193
  if (enableHandleWeb) return void await next();
237
- return this.handleRuntimeError(new Error('[BFF][Effect] Missing Effect entry. Define api/effect/index or configure bff.effect.entry.'), c);
238
- }
239
- if (this.crossProjectPolicy?.enabled && this.policyEnforcedInMiddleware && this.isApiRequestPath(c.req.path, prefix, enableHandleWeb)) {
240
- const denial = checkCrossProjectPolicyForRequest(c.req.raw, this.crossProjectPolicy);
241
- if (denial) return denial;
194
+ return this.handleRuntimeError(new Error('[BFF][Effect] Missing Effect entry. Define api/index or configure bff.effect.entry.'), c);
242
195
  }
243
196
  let response;
244
197
  try {
@@ -69,7 +69,7 @@ async function createEffectBffEdgeHandler(options) {
69
69
  dataPlatform: options.dataPlatform,
70
70
  onWarning: options.onWarning
71
71
  });
72
- if (!loaded) throw new Error('[BFF][Effect] Invalid Effect edge module. Export { api, layer }, createHandler, or handler.');
72
+ if (!loaded) throw new Error('[BFF][Effect] Invalid Effect edge module. Export defineEffectBff(...) or a { api, layer } HttpApi module.');
73
73
  return {
74
74
  handler: (request, dispatchOptions = {})=>dispatchEffectBffRequest(loaded.handler, request, {
75
75
  ...dispatchOptions,
@@ -75,6 +75,41 @@ function createBatchValidationResponse(message, status = 400) {
75
75
  }
76
76
  });
77
77
  }
78
+ function createJsonValidationResponse(message, status = 400) {
79
+ return new Response(JSON.stringify({
80
+ message
81
+ }), {
82
+ status,
83
+ headers: {
84
+ 'content-type': 'application/json; charset=utf-8'
85
+ }
86
+ });
87
+ }
88
+ function cloneWithoutJsonBodyHeaders(request) {
89
+ const headers = new Headers(request.headers);
90
+ headers.delete('content-type');
91
+ headers.delete('content-length');
92
+ return new Request(request.url, {
93
+ method: request.method,
94
+ headers,
95
+ signal: request.signal
96
+ });
97
+ }
98
+ async function prepareJsonRequestBody(request) {
99
+ const method = normalizeItemMethod(request.method);
100
+ if ('GET' === method || 'HEAD' === method) return request;
101
+ const contentType = (request.headers.get('content-type') || '').toLowerCase();
102
+ if (!contentType.includes('application/json')) return request;
103
+ if (null === request.body) return cloneWithoutJsonBodyHeaders(request);
104
+ try {
105
+ const bodyText = await request.clone().text();
106
+ if ('' === bodyText) return cloneWithoutJsonBodyHeaders(request);
107
+ JSON.parse(bodyText);
108
+ } catch {
109
+ return createJsonValidationResponse('Invalid JSON request body');
110
+ }
111
+ return request;
112
+ }
78
113
  function toBatchItemError(id, status, message) {
79
114
  return {
80
115
  id,
@@ -295,7 +330,7 @@ const LOADER_CLIENT_IGNORED_KEYS = new Set([
295
330
  ]);
296
331
  function createLoaderMaterializedClientPlaceholder() {
297
332
  const explain = (property)=>{
298
- throw new Error(`[BFF][Effect] effectBff.client.${String(property)} is not available here: the typed client only exists when this module is imported through the "@api/effect/*" transformed path (the BFF loader replaces it with generated client code). On the server, use HttpApiClient or call the Effect layer directly.`);
333
+ throw new Error(`[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.`);
299
334
  };
300
335
  return new Proxy(Object.create(null), {
301
336
  get (_target, property) {
@@ -332,9 +367,11 @@ function createHttpApiHandler(options) {
332
367
  const withDataPlatformValidation = async (request, context)=>{
333
368
  const policyDenial = options.validateRequest?.(request);
334
369
  if (policyDenial) return policyDenial;
335
- const validationError = validateDataPlatformRequestEnvelope(request, options.dataPlatform);
370
+ const preparedRequest = await prepareJsonRequestBody(request);
371
+ if (preparedRequest instanceof Response) return preparedRequest;
372
+ const validationError = validateDataPlatformRequestEnvelope(preparedRequest, options.dataPlatform);
336
373
  if (validationError) return validationError;
337
- return httpApiHandler.handler(request, context ?? emptyEffectServiceContext);
374
+ return httpApiHandler.handler(preparedRequest, context ?? emptyEffectServiceContext);
338
375
  };
339
376
  const handleBatchRequest = async (request, context)=>{
340
377
  const mountedPrefix = getMountedPrefixFromContext(request, context);
@@ -18,16 +18,18 @@ function isEffectServiceContext(context) {
18
18
  return 'object' == typeof context && null !== context && 'mapUnsafe' in context;
19
19
  }
20
20
  const emptyEffectServiceContext = __rspack_external_effect_Context_f1289ca3.empty();
21
+ const strictEffectApproachMessage = '[BFF][Effect] strictEffectApproach is enforced: Effect API entries must export defineEffectBff(...) or a { api, layer } HttpApi module. Raw handler exports, default request handlers, and unbranded custom createHandler factories are not valid Effect API entries.';
22
+ function rejectLegacyEffectModuleShape(options, shape) {
23
+ options.onWarning?.(`${strictEffectApproachMessage} Rejected ${shape}.`);
24
+ return true;
25
+ }
21
26
  function callEffectBffRequestHandler(handler, request, context) {
22
27
  return void 0 === context ? handler(request) : handler(request, context);
23
28
  }
24
- function createLoadedHandler(webHandler, appliesRequestValidator) {
29
+ function createLoadedHandler(webHandler) {
25
30
  return {
26
31
  handler: (request, context)=>callEffectBffRequestHandler(webHandler.handler, request, context),
27
- dispose: webHandler.dispose,
28
- ...appliesRequestValidator ? {
29
- appliesRequestValidator: true
30
- } : {}
32
+ dispose: webHandler.dispose
31
33
  };
32
34
  }
33
35
  function createLoadedHttpApiHandler(webHandler) {
@@ -36,18 +38,23 @@ function createLoadedHttpApiHandler(webHandler) {
36
38
  const effectContext = isEffectServiceContext(context) ? context : emptyEffectServiceContext;
37
39
  return webHandler.handler(request, effectContext);
38
40
  },
39
- dispose: webHandler.dispose,
40
- appliesRequestValidator: true
41
+ dispose: webHandler.dispose
41
42
  };
42
43
  }
43
44
  function resolveNormalizedEffectBffModuleHandler(normalizedModule, options = {}) {
44
- if (isRequestHandler(normalizedModule.handler)) return {
45
- handler: normalizedModule.handler
46
- };
45
+ if (isRequestHandler(normalizedModule.handler)) {
46
+ if (rejectLegacyEffectModuleShape(options, '`handler` export')) return null;
47
+ return {
48
+ handler: normalizedModule.handler
49
+ };
50
+ }
47
51
  const entry = normalizedModule.default;
48
- if (isRequestHandler(entry)) return {
49
- handler: entry
50
- };
52
+ if (isRequestHandler(entry)) {
53
+ if (rejectLegacyEffectModuleShape(options, 'default request handler')) return null;
54
+ return {
55
+ handler: entry
56
+ };
57
+ }
51
58
  if (isRecord(entry)) normalizedModule = {
52
59
  ...normalizedModule,
53
60
  ...entry
@@ -59,19 +66,22 @@ function resolveNormalizedEffectBffModuleHandler(normalizedModule, options = {})
59
66
  handler: maybeHandler
60
67
  };
61
68
  }
62
- if (isRequestHandler(normalizedModule.handler)) return {
63
- handler: normalizedModule.handler
64
- };
69
+ if (isRequestHandler(normalizedModule.handler)) {
70
+ if (rejectLegacyEffectModuleShape(options, '`handler` export')) return null;
71
+ return {
72
+ handler: normalizedModule.handler
73
+ };
74
+ }
65
75
  if ('function' == typeof normalizedModule.createHandler) {
66
76
  const factory = normalizedModule.createHandler;
67
77
  const validatorAware = isValidatorAwareHandlerFactory(factory);
68
- if (!validatorAware && void 0 !== options.validateRequest) options.onWarning?.('[BFF][Effect] Custom createHandler export detected: it cannot be verified to apply validateRequest (cross-project policy), so the policy is enforced by the adapter middleware on the outer request. Batched calls will be denied at the batch POST (it carries no per-operation contract); export defineEffectBff(...) to get per-batch-item enforcement.');
78
+ if (!validatorAware && rejectLegacyEffectModuleShape(options, 'unbranded `createHandler` export')) return null;
69
79
  const webHandler = factory({
70
80
  openapi: options.openapi,
71
81
  dataPlatform: options.dataPlatform,
72
82
  validateRequest: options.validateRequest
73
83
  });
74
- return createLoadedHandler(webHandler, validatorAware);
84
+ return createLoadedHandler(webHandler);
75
85
  }
76
86
  if (isEffectApiDefinition(normalizedModule)) {
77
87
  options.onWarning?.('[BFF][Effect] Detected { api, layer } export without createHandler. Prefer `defineEffectBff(...)` from @modern-js/plugin-bff/server to avoid module instance mismatch.');
@@ -95,17 +105,26 @@ function resolveEffectBffModuleHandler(mod, options = {}) {
95
105
  ...value
96
106
  };
97
107
  };
98
- if (isRequestHandler(normalizedModule.handler)) return Promise.resolve({
99
- handler: normalizedModule.handler
100
- });
108
+ if (isRequestHandler(normalizedModule.handler)) {
109
+ if (rejectLegacyEffectModuleShape(options, '`handler` export')) return Promise.resolve(null);
110
+ return Promise.resolve({
111
+ handler: normalizedModule.handler
112
+ });
113
+ }
101
114
  const entry = normalizedModule.default;
102
- if (isRequestHandler(entry)) return Promise.resolve({
103
- handler: entry
104
- });
115
+ if (isRequestHandler(entry)) {
116
+ if (rejectLegacyEffectModuleShape(options, 'default request handler')) return Promise.resolve(null);
117
+ return Promise.resolve({
118
+ handler: entry
119
+ });
120
+ }
105
121
  if ('function' == typeof entry && 0 === entry.length) return Promise.resolve(entry()).then((out)=>{
106
- if (isRequestHandler(out)) return {
107
- handler: out
108
- };
122
+ if (isRequestHandler(out)) {
123
+ if (rejectLegacyEffectModuleShape(options, 'default factory returning a request handler')) return null;
124
+ return {
125
+ handler: out
126
+ };
127
+ }
109
128
  mergeRuntimeExports(out);
110
129
  return resolveNormalizedEffectBffModuleHandler(normalizedModule, options);
111
130
  });
@@ -6,6 +6,7 @@ import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/
6
6
  import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "effect/unstable/httpapi";
7
7
  import { Rpc, RpcClient, RpcGroup, RpcSchema, RpcSerialization } from "effect/unstable/rpc";
8
8
  import * as __rspack_external_effect_Schema_f8472650 from "effect/Schema";
9
+ import * as __rspack_external_effect_unstable_http_HttpClientError_b9ead235 from "effect/unstable/http/HttpClientError";
9
10
  import * as __rspack_external_effect_Data_7c33fca9 from "effect/Data";
10
11
  import * as __rspack_external_effect_Exit_a10cfb96 from "effect/Exit";
11
12
  import * as __rspack_external_effect_ManagedRuntime_94dd2709 from "effect/ManagedRuntime";
@@ -105,4 +106,4 @@ function makeEffectRpcClient(group, options) {
105
106
  });
106
107
  }
107
108
  const runEffectRequest = __rspack_external_effect_Effect_194ac36c.runPromise;
108
- export { EffectRpcClientError, HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiSchema, Rpc, RpcClient, RpcGroup, RpcSchema, RpcSerialization, __rspack_external_effect_Effect_194ac36c as Effect, __rspack_external_effect_Layer_16f7a8fc as Layer, __rspack_external_effect_Schema_f8472650 as Schema, makeEffectHttpApiClient, makeEffectRpcClient, mask, runEffectRequest, runEffectView, view };
109
+ export { EffectRpcClientError, HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiSchema, Rpc, RpcClient, RpcGroup, RpcSchema, RpcSerialization, __rspack_external_effect_Effect_194ac36c as Effect, __rspack_external_effect_Layer_16f7a8fc as Layer, __rspack_external_effect_Schema_f8472650 as Schema, __rspack_external_effect_unstable_http_HttpClientError_b9ead235 as HttpClientError, makeEffectHttpApiClient, makeEffectRpcClient, mask, runEffectRequest, runEffectView, view };
@@ -129,14 +129,14 @@ const __generated = createGeneratedEffectClient(__manifest, __config, __requestR
129
129
  const client = __generated.client;
130
130
  const operationManifest = __generated.operationManifest;
131
131
  const createEffectRequestContext = __generated.createEffectRequestContext;
132
- const effectBffModule = {
132
+ const apiModule = {
133
133
  client,
134
134
  operationManifest,
135
135
  createEffectRequestContext,
136
136
  };
137
137
 
138
138
  export { client, createEffectRequestContext, operationManifest };
139
- export default effectBffModule;
139
+ export default apiModule;
140
140
  `;
141
141
  }
142
142
  function renderClientShape(endpoints, valueType) {
@@ -209,13 +209,13 @@ export declare const createEffectRequestContext: (
209
209
  requestContext: Record<string, unknown>,
210
210
  ) => EffectRequestContext;
211
211
  export declare const operationManifest: GeneratedEffectOperationManifest;
212
- declare const effectBffModule: {
212
+ declare const apiModule: {
213
213
  client: GeneratedEffectClient;
214
214
  createEffectRequestContext: typeof createEffectRequestContext;
215
215
  operationManifest: GeneratedEffectOperationManifest;
216
216
  };
217
217
 
218
- export default effectBffModule;
218
+ export default apiModule;
219
219
  `;
220
220
  }
221
221
  async function generateEffectClient(options) {
@@ -238,9 +238,14 @@ async function generateEffectClientCode(options) {
238
238
  }
239
239
  function resolveEffectEntryFile(options) {
240
240
  const { appDir, apiDir, effectEntry } = options;
241
- const defaultEntry = path.resolve(apiDir, 'effect', 'index');
242
- const entryWithoutExt = effectEntry ? path.isAbsolute(effectEntry) ? effectEntry : path.resolve(appDir, effectEntry) : defaultEntry;
243
- if (path.extname(entryWithoutExt)) return fs.existsSync(entryWithoutExt) ? entryWithoutExt : void 0;
244
- return findExists(JS_OR_TS_EXTS.map((ext)=>`${entryWithoutExt}${ext}`));
241
+ const resolveEntry = (entryWithoutExt)=>{
242
+ if (path.extname(entryWithoutExt)) return fs.existsSync(entryWithoutExt) ? entryWithoutExt : void 0;
243
+ return findExists(JS_OR_TS_EXTS.map((ext)=>`${entryWithoutExt}${ext}`));
244
+ };
245
+ if (effectEntry) {
246
+ const entryWithoutExt = path.isAbsolute(effectEntry) ? effectEntry : path.resolve(appDir, effectEntry);
247
+ return resolveEntry(entryWithoutExt);
248
+ }
249
+ return resolveEntry(path.resolve(apiDir, 'index'));
245
250
  }
246
251
  export { generateEffectClient, generateEffectClientCode, renderEffectClientDeclaration, resolveEffectEntryFile };
@@ -12,30 +12,14 @@ export declare class EffectAdapter {
12
12
  private handler;
13
13
  private dispose;
14
14
  private prefix;
15
- /**
16
- * True when the loaded handler does NOT run the policy seam internally
17
- * (plain `handler` exports); the adapter middleware enforces it instead.
18
- */
19
- private policyEnforcedInMiddleware;
20
15
  constructor(api: ServerPluginAPI);
21
16
  registerMiddleware: (options: MiddlewareOptions) => Promise<void>;
22
17
  onApiHandlersUpdated: () => Promise<void>;
23
18
  private resolveEntryFile;
24
- private isApiRequestPath;
25
- /**
26
- * Contract sources for the lambda-lane handlers hosted alongside the
27
- * effect entry. Cross-project SDKs generate per-operation contracts for
28
- * `api/lambda` handlers with the exact same `ApiRouter` derivation used
29
- * here, so the server-side expected-contract map must include them —
30
- * otherwise every lambda-lane operation of a hosted producer SDK would be
31
- * denied as `unknown_operation_contract`.
32
- */
33
- private collectLambdaContractSources;
34
19
  /**
35
- * Resolves the cross-project policy from the reflected HttpApi endpoints
36
- * (plus any hosted lambda-lane handlers) so the expected operation
37
- * contracts match the hashes the client generators stamp into generated
38
- * SDKs.
20
+ * Resolves the cross-project policy from the reflected HttpApi endpoints so
21
+ * the expected operation contracts match the hashes the client generators
22
+ * stamp into generated SDKs.
39
23
  */
40
24
  private refreshCrossProjectPolicy;
41
25
  private loadEffectHandlerFromModule;
@@ -144,11 +144,8 @@ export type EffectRequestValidator = (request: Request) => Response | null | und
144
144
  * seam runs inside the produced handler, including per batched item.
145
145
  *
146
146
  * `defineEffectBff` brands its factory automatically. Hand-written factories
147
- * are NOT assumed to honor `validateRequest` (the option is newer than the
148
- * factory shape), so the effect adapter falls back to middleware enforcement
149
- * for unbranded factories instead of silently skipping the policy. Custom
150
- * factories that do forward `validateRequest` into `createHttpApiHandler`
151
- * may opt in by setting this symbol property to `true`.
147
+ * are NOT assumed to honor `validateRequest`, so strict module resolution
148
+ * rejects unbranded factories instead of silently skipping the policy.
152
149
  *
153
150
  * Registered via `Symbol.for` so independently loaded runtime copies agree.
154
151
  */