@bleedingdev/modern-js-plugin-bff 3.2.0-ultramodern.120 → 3.2.0-ultramodern.121

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 (49) hide show
  1. package/dist/cjs/runtime/data-platform/index.js +2 -13
  2. package/dist/cjs/runtime/effect/adapter.js +78 -9
  3. package/dist/cjs/runtime/effect/edge.js +13 -17
  4. package/dist/cjs/runtime/effect/endpoint-contracts.js +130 -0
  5. package/dist/cjs/runtime/effect/handler.js +50 -5
  6. package/dist/cjs/runtime/effect/module.js +16 -7
  7. package/dist/cjs/runtime/effect/operation-context.js +1 -13
  8. package/dist/cjs/runtime/effect-client/runtime.js +266 -0
  9. package/dist/cjs/runtime/hono/adapter.js +21 -9
  10. package/dist/cjs/runtime/safe-failure.js +83 -0
  11. package/dist/cjs/utils/clientGenerator.js +4 -4
  12. package/dist/cjs/utils/crossProjectServerPolicy.js +104 -0
  13. package/dist/cjs/utils/effectClientGenerator.js +90 -483
  14. package/dist/esm/runtime/data-platform/index.mjs +2 -13
  15. package/dist/esm/runtime/effect/adapter.mjs +78 -9
  16. package/dist/esm/runtime/effect/edge.mjs +2 -9
  17. package/dist/esm/runtime/effect/endpoint-contracts.mjs +68 -0
  18. package/dist/esm/runtime/effect/handler.mjs +36 -4
  19. package/dist/esm/runtime/effect/module.mjs +17 -8
  20. package/dist/esm/runtime/effect/operation-context.mjs +1 -13
  21. package/dist/esm/runtime/effect-client/runtime.mjs +228 -0
  22. package/dist/esm/runtime/hono/adapter.mjs +21 -9
  23. package/dist/esm/runtime/safe-failure.mjs +45 -0
  24. package/dist/esm/utils/clientGenerator.mjs +5 -5
  25. package/dist/esm/utils/crossProjectServerPolicy.mjs +50 -0
  26. package/dist/esm/utils/effectClientGenerator.mjs +88 -484
  27. package/dist/esm-node/runtime/data-platform/index.mjs +2 -13
  28. package/dist/esm-node/runtime/effect/adapter.mjs +78 -9
  29. package/dist/esm-node/runtime/effect/edge.mjs +2 -9
  30. package/dist/esm-node/runtime/effect/endpoint-contracts.mjs +69 -0
  31. package/dist/esm-node/runtime/effect/handler.mjs +36 -4
  32. package/dist/esm-node/runtime/effect/module.mjs +17 -8
  33. package/dist/esm-node/runtime/effect/operation-context.mjs +1 -13
  34. package/dist/esm-node/runtime/effect-client/runtime.mjs +229 -0
  35. package/dist/esm-node/runtime/hono/adapter.mjs +21 -9
  36. package/dist/esm-node/runtime/safe-failure.mjs +46 -0
  37. package/dist/esm-node/utils/clientGenerator.mjs +5 -5
  38. package/dist/esm-node/utils/crossProjectServerPolicy.mjs +52 -0
  39. package/dist/esm-node/utils/effectClientGenerator.mjs +88 -484
  40. package/dist/types/runtime/effect/adapter.d.ts +25 -0
  41. package/dist/types/runtime/effect/endpoint-contracts.d.ts +62 -0
  42. package/dist/types/runtime/effect/handler.d.ts +30 -0
  43. package/dist/types/runtime/effect/module.d.ts +21 -1
  44. package/dist/types/runtime/effect-client/runtime.d.ts +71 -0
  45. package/dist/types/runtime/hono/adapter.d.ts +3 -0
  46. package/dist/types/runtime/safe-failure.d.ts +1 -0
  47. package/dist/types/utils/crossProjectServerPolicy.d.ts +35 -0
  48. package/dist/types/utils/effectClientGenerator.d.ts +15 -1
  49. package/package.json +24 -12
@@ -53,6 +53,7 @@ __webpack_require__.d(__webpack_exports__, {
53
53
  validateRequestEnvelope: ()=>validateRequestEnvelope,
54
54
  validateSelectionPlan: ()=>validateSelectionPlan
55
55
  });
56
+ const create_request_namespaceObject = require("@modern-js/create-request");
56
57
  const api_namespaceObject = require("@opentelemetry/api");
57
58
  const DATA_BATCH_TRANSPORT_OTEL_EVENT = 'modernjs.data.batch';
58
59
  function createDataBatchTransportTelemetryAttributes(event) {
@@ -78,7 +79,6 @@ function emitDataBatchTransportEvent(onEvent, event) {
78
79
  const DEFAULT_DATA_ENVELOPE_HEADER = 'x-modernjs-data-envelope';
79
80
  const DEFAULT_DATA_BATCH_ENDPOINT = '/_data/batch';
80
81
  const DEFAULT_DATA_BATCH_HEADER = 'x-modernjs-data-batch';
81
- const TRACEPARENT_REGEX = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/i;
82
82
  function isPlainObject(value) {
83
83
  if ('object' != typeof value || null === value || Array.isArray(value)) return false;
84
84
  const proto = Object.getPrototypeOf(value);
@@ -175,18 +175,7 @@ function isValidHex(value, length) {
175
175
  return value.length === length && /^[0-9a-f]+$/.test(value);
176
176
  }
177
177
  function parseTraceparentHeader(header) {
178
- const match = header.trim().match(TRACEPARENT_REGEX);
179
- if (!match) return null;
180
- const traceId = match[1].toLowerCase();
181
- const spanId = match[2].toLowerCase();
182
- const flags = match[3].toLowerCase();
183
- if (isAllZeroHex(traceId) || isAllZeroHex(spanId)) return null;
184
- const sampled = (0x1 & Number.parseInt(flags, 16)) === 1;
185
- return {
186
- traceId,
187
- spanId,
188
- sampled
189
- };
178
+ return (0, create_request_namespaceObject.parseTraceparent)(header) ?? null;
190
179
  }
191
180
  function formatTraceparentHeader(trace) {
192
181
  const traceId = trace.traceId.toLowerCase();
@@ -39,10 +39,15 @@ __webpack_require__.r(__webpack_exports__);
39
39
  __webpack_require__.d(__webpack_exports__, {
40
40
  EffectAdapter: ()=>EffectAdapter
41
41
  });
42
+ const bff_core_namespaceObject = require("@modern-js/bff-core");
42
43
  const utils_namespaceObject = require("@modern-js/utils");
44
+ const httpapi_namespaceObject = require("effect/unstable/httpapi");
43
45
  const external_path_namespaceObject = require("path");
44
46
  var external_path_default = /*#__PURE__*/ __webpack_require__.n(external_path_namespaceObject);
47
+ const crossProjectServerPolicy_js_namespaceObject = require("../../utils/crossProjectServerPolicy.js");
48
+ const external_safe_failure_js_namespaceObject = require("../safe-failure.js");
45
49
  const external_context_js_namespaceObject = require("./context.js");
50
+ const external_endpoint_contracts_js_namespaceObject = require("./endpoint-contracts.js");
46
51
  const external_module_js_namespaceObject = require("./module.js");
47
52
  const before = [
48
53
  'custom-server-hook',
@@ -88,10 +93,73 @@ class EffectAdapter {
88
93
  const entryWithoutExt = configuredEntry ? external_path_default().isAbsolute(configuredEntry) ? configuredEntry : external_path_default().resolve(appDirectory || process.cwd(), configuredEntry) : defaultEntry;
89
94
  return (0, utils_namespaceObject.findExists)(JS_OR_TS_EXTS.map((ext)=>`${entryWithoutExt}${ext}`));
90
95
  }
96
+ isApiRequestPath(requestPath, prefix, enableHandleWeb) {
97
+ if (!enableHandleWeb) return true;
98
+ const normalized = normalizePrefix(prefix);
99
+ if (!normalized) return true;
100
+ return requestPath === normalized || requestPath.startsWith(`${normalized}/`);
101
+ }
102
+ async collectLambdaContractSources() {
103
+ try {
104
+ const serverContext = this.api.getServerContext();
105
+ const appDir = serverContext.distDirectory || serverContext.appDirectory;
106
+ if (!appDir) return [];
107
+ const apiDir = 'string' == typeof serverContext.apiDirectory ? serverContext.apiDirectory : external_path_default().resolve(appDir, utils_namespaceObject.API_DIR);
108
+ const lambdaDir = 'string' == typeof serverContext.lambdaDirectory ? serverContext.lambdaDirectory : external_path_default().join(apiDir, 'lambda');
109
+ if (!await utils_namespaceObject.fs.pathExists(lambdaDir)) return [];
110
+ const apiRouter = new bff_core_namespaceObject.ApiRouter({
111
+ appDir,
112
+ apiDir,
113
+ lambdaDir,
114
+ prefix: this.prefix,
115
+ httpMethodDecider: this.api.getServerConfig()?.bff?.httpMethodDecider
116
+ });
117
+ const handlerInfos = await apiRouter.getApiHandlers();
118
+ return handlerInfos.map((info)=>({
119
+ name: info.name,
120
+ httpMethod: info.httpMethod,
121
+ routePath: info.routePath,
122
+ filename: info.filename,
123
+ handler: info.handler
124
+ }));
125
+ } catch (error) {
126
+ utils_namespaceObject.logger.warn(`[BFF][Effect] Failed to derive lambda operation contracts for the cross-project policy: ${String(error)}`);
127
+ return [];
128
+ }
129
+ }
130
+ async refreshCrossProjectPolicy(mod) {
131
+ let contractSources = [];
132
+ if (mod) try {
133
+ const api = await (0, external_endpoint_contracts_js_namespaceObject.extractHttpApiFromModule)(mod, httpapi_namespaceObject.HttpApi.isHttpApi);
134
+ if (api) {
135
+ const reflect = (apiValue, handlers)=>httpapi_namespaceObject.HttpApi.reflect(apiValue, {
136
+ onGroup: handlers.onGroup ?? (()=>{}),
137
+ onEndpoint: handlers.onEndpoint
138
+ });
139
+ contractSources = (0, external_endpoint_contracts_js_namespaceObject.toOperationContractSources)((0, external_endpoint_contracts_js_namespaceObject.collectEffectEndpoints)(reflect, api, this.prefix));
140
+ }
141
+ } catch (error) {
142
+ utils_namespaceObject.logger.warn(`[BFF][Effect] Failed to reflect HttpApi endpoints for the cross-project policy: ${String(error)}`);
143
+ }
144
+ let policy = (0, crossProjectServerPolicy_js_namespaceObject.resolveAdapterCrossProjectPolicy)(this.api, contractSources);
145
+ if (policy?.enabled) {
146
+ const lambdaSources = await this.collectLambdaContractSources();
147
+ if (lambdaSources.length > 0) {
148
+ contractSources = [
149
+ ...contractSources,
150
+ ...lambdaSources
151
+ ];
152
+ policy = (0, crossProjectServerPolicy_js_namespaceObject.resolveAdapterCrossProjectPolicy)(this.api, contractSources);
153
+ }
154
+ }
155
+ this.crossProjectPolicy = policy;
156
+ if (this.crossProjectPolicy?.enabled && 0 === contractSources.length) utils_namespaceObject.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).');
157
+ }
91
158
  async loadEffectHandlerFromModule(mod) {
92
159
  return (0, external_module_js_namespaceObject.resolveEffectBffModuleHandler)(mod, {
93
160
  openapi: this.api.getServerConfig()?.bff?.effect?.openapi,
94
161
  dataPlatform: this.api.getServerConfig()?.bff?.effect?.dataPlatform,
162
+ validateRequest: (request)=>(0, crossProjectServerPolicy_js_namespaceObject.checkCrossProjectPolicyForRequest)(request, this.crossProjectPolicy),
95
163
  onWarning: (message)=>{
96
164
  utils_namespaceObject.logger.warn(message);
97
165
  }
@@ -121,6 +189,7 @@ class EffectAdapter {
121
189
  this.handler = null;
122
190
  return;
123
191
  }
192
+ await this.refreshCrossProjectPolicy(mod);
124
193
  const loaded = await this.loadEffectHandlerFromModule(mod);
125
194
  if (!loaded) {
126
195
  utils_namespaceObject.logger.warn(`[BFF][Effect] Invalid Effect entry module: ${entryFile}. Export { api, layer } or handler.`);
@@ -129,6 +198,7 @@ class EffectAdapter {
129
198
  }
130
199
  this.handler = loaded.handler;
131
200
  this.dispose = loaded.dispose || null;
201
+ this.policyEnforcedInMiddleware = !loaded.appliesRequestValidator;
132
202
  }
133
203
  async disposeCurrentHandler() {
134
204
  if (!this.dispose) return;
@@ -152,15 +222,7 @@ class EffectAdapter {
152
222
  } catch (configError) {
153
223
  utils_namespaceObject.logger.error(`Error in serverConfig.onError handler: ${configError}`);
154
224
  }
155
- const status = 'object' == typeof error && null !== error && 'status' in error && 'number' == typeof error.status ? error.status : 500;
156
- return new Response(JSON.stringify({
157
- message: error instanceof Error ? error.message : '[BFF] Internal Server Error'
158
- }), {
159
- status,
160
- headers: {
161
- 'content-type': 'application/json; charset=utf-8'
162
- }
163
- });
225
+ return (0, external_safe_failure_js_namespaceObject.createSafeFailureResponse)(error);
164
226
  }
165
227
  ensureJsonContext(c) {
166
228
  const maybeJsonContext = c;
@@ -187,6 +249,8 @@ class EffectAdapter {
187
249
  this.effectMiddleware = null;
188
250
  this.handler = null;
189
251
  this.dispose = null;
252
+ this.prefix = '/api';
253
+ this.policyEnforcedInMiddleware = false;
190
254
  this.registerMiddleware = async (options)=>{
191
255
  const { prefix, enableHandleWeb } = options;
192
256
  const { bffRuntimeFramework, middlewares: globalMiddlewares } = this.api.getServerContext();
@@ -194,6 +258,7 @@ class EffectAdapter {
194
258
  this.isEffect = false;
195
259
  return;
196
260
  }
261
+ this.prefix = prefix || this.prefix;
197
262
  await this.reloadHandler();
198
263
  this.effectMiddleware = {
199
264
  name: 'effect-bff-handler',
@@ -206,6 +271,10 @@ class EffectAdapter {
206
271
  if (enableHandleWeb) return void await next();
207
272
  return this.handleRuntimeError(new Error('[BFF][Effect] Missing Effect entry. Define api/effect/index or configure bff.effect.entry.'), c);
208
273
  }
274
+ if (this.crossProjectPolicy?.enabled && this.policyEnforcedInMiddleware && this.isApiRequestPath(c.req.path, prefix, enableHandleWeb)) {
275
+ const denial = (0, crossProjectServerPolicy_js_namespaceObject.checkCrossProjectPolicyForRequest)(c.req.raw, this.crossProjectPolicy);
276
+ if (denial) return denial;
277
+ }
209
278
  let response;
210
279
  try {
211
280
  const effectRequest = createRequestForMountedPrefix(c.req.raw, prefix);
@@ -1,5 +1,8 @@
1
1
  "use strict";
2
2
  var __webpack_modules__ = {
3
+ "../safe-failure" (module) {
4
+ module.exports = require("../safe-failure.js");
5
+ },
3
6
  "./handler" (module) {
4
7
  module.exports = require("./handler.js");
5
8
  },
@@ -57,16 +60,17 @@ function __webpack_require__(moduleId) {
57
60
  var __webpack_exports__ = {};
58
61
  (()=>{
59
62
  __webpack_require__.r(__webpack_exports__);
60
- var _module__rspack_import_0 = __webpack_require__("./module");
61
- var _operation_context__rspack_import_1 = __webpack_require__("./operation-context");
62
- var _handler__rspack_import_2 = __webpack_require__("./handler");
63
+ var _safe_failure__rspack_import_0 = __webpack_require__("../safe-failure");
64
+ var _module__rspack_import_1 = __webpack_require__("./module");
65
+ var _operation_context__rspack_import_2 = __webpack_require__("./operation-context");
66
+ var _handler__rspack_import_3 = __webpack_require__("./handler");
63
67
  var __rspack_reexport = {};
64
- for(const __rspack_import_key in _handler__rspack_import_2)if ([
68
+ for(const __rspack_import_key in _handler__rspack_import_3)if ([
65
69
  "default",
66
70
  "dispatchEffectBffRequest",
67
71
  "createEffectOperationContext",
68
72
  "createEffectBffEdgeHandler"
69
- ].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>_handler__rspack_import_2[__rspack_import_key];
73
+ ].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>_handler__rspack_import_3[__rspack_import_key];
70
74
  __webpack_require__.d(__webpack_exports__, __rspack_reexport);
71
75
  function normalizePrefix(prefix) {
72
76
  if (!prefix || '/' === prefix) return '';
@@ -97,7 +101,7 @@ var __webpack_exports__ = {};
97
101
  env: options.env || {},
98
102
  path: originalPath,
99
103
  method,
100
- operationContext: (0, _operation_context__rspack_import_1.createEffectOperationContext)({
104
+ operationContext: (0, _operation_context__rspack_import_2.createEffectOperationContext)({
101
105
  request: effectRequest,
102
106
  env: options.env || {},
103
107
  path: originalPath,
@@ -106,15 +110,7 @@ var __webpack_exports__ = {};
106
110
  };
107
111
  }
108
112
  function createRuntimeErrorResponse(error) {
109
- const status = 'object' == typeof error && null !== error && 'status' in error && 'number' == typeof error.status ? error.status : 500;
110
- return new Response(JSON.stringify({
111
- message: error instanceof Error ? error.message : '[BFF] Internal Server Error'
112
- }), {
113
- status,
114
- headers: {
115
- 'content-type': 'application/json; charset=utf-8'
116
- }
117
- });
113
+ return (0, _safe_failure__rspack_import_0.createSafeFailureResponse)(error);
118
114
  }
119
115
  async function dispatchEffectBffRequest(handler, request, options = {}) {
120
116
  const requestPathname = new URL(request.url).pathname;
@@ -137,7 +133,7 @@ var __webpack_exports__ = {};
137
133
  }
138
134
  }
139
135
  async function createEffectBffEdgeHandler(options) {
140
- const loaded = await (0, _module__rspack_import_0.resolveEffectBffModuleHandler)(options.module, {
136
+ const loaded = await (0, _module__rspack_import_1.resolveEffectBffModuleHandler)(options.module, {
141
137
  openapi: options.openapi,
142
138
  dataPlatform: options.dataPlatform,
143
139
  onWarning: options.onWarning
@@ -156,7 +152,7 @@ var __webpack_exports__ = {};
156
152
  }
157
153
  __webpack_require__.d(__webpack_exports__, {
158
154
  createEffectBffEdgeHandler: ()=>createEffectBffEdgeHandler,
159
- createEffectOperationContext: ()=>_operation_context__rspack_import_1.createEffectOperationContext,
155
+ createEffectOperationContext: ()=>_operation_context__rspack_import_2.createEffectOperationContext,
160
156
  dispatchEffectBffRequest: ()=>dispatchEffectBffRequest
161
157
  });
162
158
  })();
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, getters, values)=>{
5
+ var define = (defs, kind)=>{
6
+ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
7
+ enumerable: true,
8
+ [kind]: defs[key]
9
+ });
10
+ };
11
+ define(getters, "get");
12
+ define(values, "value");
13
+ };
14
+ })();
15
+ (()=>{
16
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
17
+ })();
18
+ (()=>{
19
+ __webpack_require__.r = (exports1)=>{
20
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
21
+ value: 'Module'
22
+ });
23
+ Object.defineProperty(exports1, '__esModule', {
24
+ value: true
25
+ });
26
+ };
27
+ })();
28
+ var __webpack_exports__ = {};
29
+ __webpack_require__.r(__webpack_exports__);
30
+ __webpack_require__.d(__webpack_exports__, {
31
+ collectEffectEndpoints: ()=>collectEffectEndpoints,
32
+ createEffectEndpointContractHash: ()=>createEffectEndpointContractHash,
33
+ createEffectOperationContractSource: ()=>createEffectOperationContractSource,
34
+ ensureLeadingSlash: ()=>ensureLeadingSlash,
35
+ extractHttpApiFromModule: ()=>extractHttpApiFromModule,
36
+ getEffectRoutePath: ()=>getEffectRoutePath,
37
+ normalizeEffectPrefix: ()=>normalizeEffectPrefix,
38
+ resolveEffectApiId: ()=>resolveEffectApiId,
39
+ toOperationContractSources: ()=>toOperationContractSources
40
+ });
41
+ const bff_core_namespaceObject = require("@modern-js/bff-core");
42
+ function ensureLeadingSlash(pathname) {
43
+ return pathname.startsWith('/') ? pathname : `/${pathname}`;
44
+ }
45
+ function normalizeEffectPrefix(prefix) {
46
+ if ('/' === prefix) return '';
47
+ return ensureLeadingSlash(prefix || '/api');
48
+ }
49
+ function getEffectRoutePath(prefix, endpointPath) {
50
+ const normalizedPrefix = normalizeEffectPrefix(prefix);
51
+ const normalizedEndpointPath = ensureLeadingSlash(endpointPath);
52
+ const finalEndpointPath = '/' === normalizedEndpointPath ? '' : endpointPath;
53
+ if (!normalizedPrefix && !finalEndpointPath) return '/';
54
+ return `${normalizedPrefix}${finalEndpointPath || ''}`;
55
+ }
56
+ function resolveEffectApiId(api) {
57
+ const fallback = 'EffectHttpApi';
58
+ if ('identifier' in api && 'string' == typeof api.identifier && api.identifier) return api.identifier;
59
+ return fallback;
60
+ }
61
+ function collectEffectEndpoints(reflect, api, prefix) {
62
+ const endpoints = [];
63
+ const apiId = resolveEffectApiId(api);
64
+ reflect(api, {
65
+ onGroup: ()=>{},
66
+ onEndpoint: ({ group, endpoint })=>{
67
+ endpoints.push({
68
+ apiId,
69
+ groupName: String(group.identifier),
70
+ endpointName: String(endpoint.name),
71
+ method: String(endpoint.method).toUpperCase(),
72
+ routePath: getEffectRoutePath(prefix, String(endpoint.path))
73
+ });
74
+ }
75
+ });
76
+ return endpoints.sort((a, b)=>{
77
+ if (a.groupName === b.groupName) return a.endpointName.localeCompare(b.endpointName);
78
+ return a.groupName.localeCompare(b.groupName);
79
+ });
80
+ }
81
+ function toOperationContractSources(endpoints) {
82
+ return endpoints.map(createEffectOperationContractSource);
83
+ }
84
+ function createEffectOperationContractSource(endpoint) {
85
+ return {
86
+ name: endpoint.endpointName,
87
+ httpMethod: endpoint.method,
88
+ routePath: endpoint.routePath
89
+ };
90
+ }
91
+ function isRecord(value) {
92
+ return 'object' == typeof value && null !== value;
93
+ }
94
+ async function extractHttpApiFromModule(mod, isHttpApi) {
95
+ if (!isRecord(mod)) return null;
96
+ if (isHttpApi(mod.api)) return mod.api;
97
+ const entry = mod.default;
98
+ if (isRecord(entry) && isHttpApi(entry.api)) return entry.api;
99
+ if ('function' == typeof entry && 0 === entry.length) {
100
+ const output = await entry();
101
+ if (isRecord(output) && isHttpApi(output.api)) return output.api;
102
+ }
103
+ return null;
104
+ }
105
+ function createEffectEndpointContractHash(endpoint, requestId) {
106
+ return (0, bff_core_namespaceObject.createOperationContractHash)(createEffectOperationContractSource(endpoint), requestId);
107
+ }
108
+ exports.collectEffectEndpoints = __webpack_exports__.collectEffectEndpoints;
109
+ exports.createEffectEndpointContractHash = __webpack_exports__.createEffectEndpointContractHash;
110
+ exports.createEffectOperationContractSource = __webpack_exports__.createEffectOperationContractSource;
111
+ exports.ensureLeadingSlash = __webpack_exports__.ensureLeadingSlash;
112
+ exports.extractHttpApiFromModule = __webpack_exports__.extractHttpApiFromModule;
113
+ exports.getEffectRoutePath = __webpack_exports__.getEffectRoutePath;
114
+ exports.normalizeEffectPrefix = __webpack_exports__.normalizeEffectPrefix;
115
+ exports.resolveEffectApiId = __webpack_exports__.resolveEffectApiId;
116
+ exports.toOperationContractSources = __webpack_exports__.toOperationContractSources;
117
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
118
+ "collectEffectEndpoints",
119
+ "createEffectEndpointContractHash",
120
+ "createEffectOperationContractSource",
121
+ "ensureLeadingSlash",
122
+ "extractHttpApiFromModule",
123
+ "getEffectRoutePath",
124
+ "normalizeEffectPrefix",
125
+ "resolveEffectApiId",
126
+ "toOperationContractSources"
127
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
128
+ Object.defineProperty(exports, '__esModule', {
129
+ value: true
130
+ });
@@ -100,6 +100,8 @@ var __webpack_exports__ = {};
100
100
  "Effect",
101
101
  "defineEffectRpcBff",
102
102
  "createHttpApiHandler",
103
+ "EFFECT_VALIDATOR_AWARE_FACTORY",
104
+ "isValidatorAwareHandlerFactory",
103
105
  "Option"
104
106
  ].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>effect_unstable_http__rspack_import_2[__rspack_import_key];
105
107
  __webpack_require__.d(__webpack_exports__, __rspack_reexport);
@@ -115,6 +117,8 @@ var __webpack_exports__ = {};
115
117
  "Effect",
116
118
  "defineEffectRpcBff",
117
119
  "createHttpApiHandler",
120
+ "EFFECT_VALIDATOR_AWARE_FACTORY",
121
+ "isValidatorAwareHandlerFactory",
118
122
  "Option"
119
123
  ].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>effect_unstable_httpapi__rspack_import_3[__rspack_import_key];
120
124
  __webpack_require__.d(__webpack_exports__, __rspack_reexport);
@@ -130,10 +134,16 @@ var __webpack_exports__ = {};
130
134
  "Effect",
131
135
  "defineEffectRpcBff",
132
136
  "createHttpApiHandler",
137
+ "EFFECT_VALIDATOR_AWARE_FACTORY",
138
+ "isValidatorAwareHandlerFactory",
133
139
  "Option"
134
140
  ].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>effect_unstable_rpc__rspack_import_4[__rspack_import_key];
135
141
  __webpack_require__.d(__webpack_exports__, __rspack_reexport);
136
142
  const emptyEffectServiceContext = effect_Context__rspack_import_0.empty();
143
+ const EFFECT_VALIDATOR_AWARE_FACTORY = Symbol.for('modernjs.effect.validatorAware');
144
+ function isValidatorAwareHandlerFactory(factory) {
145
+ return 'function' == typeof factory && true === factory[EFFECT_VALIDATOR_AWARE_FACTORY];
146
+ }
137
147
  function normalizeOpenApiPath(pathname) {
138
148
  if (!pathname.startsWith('/')) return `/${pathname}`;
139
149
  return pathname;
@@ -389,16 +399,38 @@ var __webpack_exports__ = {};
389
399
  layer: definition.layer,
390
400
  openapi: options?.openapi,
391
401
  rpc: mergedRpcOptions,
392
- dataPlatform: mergeDataPlatformOptions(definition.dataPlatform, options?.dataPlatform)
402
+ dataPlatform: mergeDataPlatformOptions(definition.dataPlatform, options?.dataPlatform),
403
+ validateRequest: options?.validateRequest
393
404
  });
394
405
  };
395
- const client = void 0;
406
+ Object.defineProperty(createHandler, EFFECT_VALIDATOR_AWARE_FACTORY, {
407
+ value: true
408
+ });
409
+ const client = createLoaderMaterializedClientPlaceholder();
396
410
  return {
397
411
  ...definition,
398
412
  createHandler,
399
413
  client
400
414
  };
401
415
  }
416
+ const LOADER_CLIENT_IGNORED_KEYS = new Set([
417
+ 'then',
418
+ 'catch',
419
+ 'finally',
420
+ 'toJSON',
421
+ '$$typeof'
422
+ ]);
423
+ function createLoaderMaterializedClientPlaceholder() {
424
+ const explain = (property)=>{
425
+ 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.`);
426
+ };
427
+ return new Proxy(Object.create(null), {
428
+ get (_target, property) {
429
+ if ('symbol' == typeof property || LOADER_CLIENT_IGNORED_KEYS.has(property)) return;
430
+ return explain(property);
431
+ }
432
+ });
433
+ }
402
434
  function defineEffectRpcBff(definition) {
403
435
  const createHandler = (options)=>createRpcApiHandler({
404
436
  ...definition,
@@ -425,6 +457,8 @@ var __webpack_exports__ = {};
425
457
  const envelopeHeader = options.dataPlatform?.envelopeHeader || _data_platform__rspack_import_5.DEFAULT_DATA_ENVELOPE_HEADER;
426
458
  const normalizedEnvelopeHeader = envelopeHeader.toLowerCase();
427
459
  const withDataPlatformValidation = async (request, context)=>{
460
+ const policyDenial = options.validateRequest?.(request);
461
+ if (policyDenial) return policyDenial;
428
462
  const validationError = validateDataPlatformRequestEnvelope(request, options.dataPlatform);
429
463
  if (validationError) return validationError;
430
464
  return httpApiHandler.handler(request, context ?? emptyEffectServiceContext);
@@ -546,7 +580,11 @@ var __webpack_exports__ = {};
546
580
  const rpcHandler = createRpcApiHandler(options.rpc);
547
581
  return {
548
582
  handler: async (request, context)=>{
549
- if (isRpcRequest(request, rpcPath)) return rpcHandler.handler(request, context ?? emptyEffectServiceContext);
583
+ if (isRpcRequest(request, rpcPath)) {
584
+ const policyDenial = options.validateRequest?.(request);
585
+ if (policyDenial) return policyDenial;
586
+ return rpcHandler.handler(request, context ?? emptyEffectServiceContext);
587
+ }
550
588
  return handleHttpApiRequest(request);
551
589
  },
552
590
  dispose: async ()=>{
@@ -567,10 +605,14 @@ var __webpack_exports__ = {};
567
605
  Schema: ()=>effect_Schema__rspack_import_9,
568
606
  createHttpApiHandler: ()=>createHttpApiHandler,
569
607
  defineEffectBff: ()=>defineEffectBff,
570
- defineEffectRpcBff: ()=>defineEffectRpcBff
608
+ defineEffectRpcBff: ()=>defineEffectRpcBff,
609
+ isValidatorAwareHandlerFactory: ()=>isValidatorAwareHandlerFactory
610
+ }, {
611
+ EFFECT_VALIDATOR_AWARE_FACTORY: EFFECT_VALIDATOR_AWARE_FACTORY
571
612
  });
572
613
  })();
573
614
  exports.Config = __webpack_exports__.Config;
615
+ exports.EFFECT_VALIDATOR_AWARE_FACTORY = __webpack_exports__.EFFECT_VALIDATOR_AWARE_FACTORY;
574
616
  exports.Effect = __webpack_exports__.Effect;
575
617
  exports.HttpApiBuilder = __webpack_exports__.HttpApiBuilder;
576
618
  exports.HttpTraceContext = __webpack_exports__.HttpTraceContext;
@@ -580,8 +622,10 @@ exports.Schema = __webpack_exports__.Schema;
580
622
  exports.createHttpApiHandler = __webpack_exports__.createHttpApiHandler;
581
623
  exports.defineEffectBff = __webpack_exports__.defineEffectBff;
582
624
  exports.defineEffectRpcBff = __webpack_exports__.defineEffectRpcBff;
625
+ exports.isValidatorAwareHandlerFactory = __webpack_exports__.isValidatorAwareHandlerFactory;
583
626
  for(var __rspack_i in __webpack_exports__)if (-1 === [
584
627
  "Config",
628
+ "EFFECT_VALIDATOR_AWARE_FACTORY",
585
629
  "Effect",
586
630
  "HttpApiBuilder",
587
631
  "HttpTraceContext",
@@ -590,7 +634,8 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
590
634
  "Schema",
591
635
  "createHttpApiHandler",
592
636
  "defineEffectBff",
593
- "defineEffectRpcBff"
637
+ "defineEffectRpcBff",
638
+ "isValidatorAwareHandlerFactory"
594
639
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
595
640
  Object.defineProperty(exports, '__esModule', {
596
641
  value: true
@@ -52,10 +52,13 @@ const emptyEffectServiceContext = Context_namespaceObject.empty();
52
52
  function callEffectBffRequestHandler(handler, request, context) {
53
53
  return void 0 === context ? handler(request) : handler(request, context);
54
54
  }
55
- function createLoadedHandler(webHandler) {
55
+ function createLoadedHandler(webHandler, appliesRequestValidator) {
56
56
  return {
57
57
  handler: (request, context)=>callEffectBffRequestHandler(webHandler.handler, request, context),
58
- dispose: webHandler.dispose
58
+ dispose: webHandler.dispose,
59
+ ...appliesRequestValidator ? {
60
+ appliesRequestValidator: true
61
+ } : {}
59
62
  };
60
63
  }
61
64
  function createLoadedHttpApiHandler(webHandler) {
@@ -64,7 +67,8 @@ function createLoadedHttpApiHandler(webHandler) {
64
67
  const effectContext = isEffectServiceContext(context) ? context : emptyEffectServiceContext;
65
68
  return webHandler.handler(request, effectContext);
66
69
  },
67
- dispose: webHandler.dispose
70
+ dispose: webHandler.dispose,
71
+ appliesRequestValidator: true
68
72
  };
69
73
  }
70
74
  function resolveNormalizedEffectBffModuleHandler(normalizedModule, options = {}) {
@@ -90,11 +94,15 @@ function resolveNormalizedEffectBffModuleHandler(normalizedModule, options = {})
90
94
  handler: normalizedModule.handler
91
95
  };
92
96
  if ('function' == typeof normalizedModule.createHandler) {
93
- const webHandler = normalizedModule.createHandler({
97
+ const factory = normalizedModule.createHandler;
98
+ const validatorAware = (0, external_handler_js_namespaceObject.isValidatorAwareHandlerFactory)(factory);
99
+ 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.');
100
+ const webHandler = factory({
94
101
  openapi: options.openapi,
95
- dataPlatform: options.dataPlatform
102
+ dataPlatform: options.dataPlatform,
103
+ validateRequest: options.validateRequest
96
104
  });
97
- return createLoadedHandler(webHandler);
105
+ return createLoadedHandler(webHandler, validatorAware);
98
106
  }
99
107
  if (isEffectApiDefinition(normalizedModule)) {
100
108
  options.onWarning?.('[BFF][Effect] Detected { api, layer } export without createHandler. Prefer `defineEffectBff(...)` from @modern-js/plugin-bff/server to avoid module instance mismatch.');
@@ -102,7 +110,8 @@ function resolveNormalizedEffectBffModuleHandler(normalizedModule, options = {})
102
110
  api: normalizedModule.api,
103
111
  layer: normalizedModule.layer,
104
112
  openapi: options.openapi,
105
- dataPlatform: options.dataPlatform
113
+ dataPlatform: options.dataPlatform,
114
+ validateRequest: options.validateRequest
106
115
  });
107
116
  return createLoadedHttpApiHandler(webHandler);
108
117
  }
@@ -31,7 +31,6 @@ __webpack_require__.d(__webpack_exports__, {
31
31
  createEffectOperationContext: ()=>createEffectOperationContext
32
32
  });
33
33
  const create_request_namespaceObject = require("@modern-js/create-request");
34
- const TRACEPARENT_REGEX = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/i;
35
34
  const readHeader = (headers, header)=>{
36
35
  const value = headers.get(header);
37
36
  return value && value.length > 0 ? value : void 0;
@@ -40,17 +39,6 @@ const copyStringField = (target, details, key)=>{
40
39
  const value = details[key];
41
40
  if ('string' == typeof value && value.length > 0) target[key] = value;
42
41
  };
43
- const parseTraceparent = (traceparent)=>{
44
- if (!traceparent) return;
45
- const match = traceparent.trim().match(TRACEPARENT_REGEX);
46
- if (!match) return;
47
- const [, traceId, spanId] = match;
48
- if (!traceId || !spanId) return;
49
- return {
50
- traceId: traceId.toLowerCase(),
51
- spanId: spanId.toLowerCase()
52
- };
53
- };
54
42
  const readOperationContextDetails = (request)=>{
55
43
  const rawDetails = readHeader(request.headers, create_request_namespaceObject.BFF_OPERATION_CONTEXT_DETAIL_HEADER);
56
44
  if (!rawDetails) return {};
@@ -78,7 +66,7 @@ const createEffectOperationContext = ({ request, path, method })=>{
78
66
  const parsedTraceparent = details.traceId && details.spanId ? {
79
67
  traceId: details.traceId,
80
68
  spanId: details.spanId
81
- } : parseTraceparent(traceparent);
69
+ } : (0, create_request_namespaceObject.parseTraceparent)(traceparent);
82
70
  const locale = readHeader(request.headers, create_request_namespaceObject.BFF_LOCALE_HEADER);
83
71
  const headerOperationId = readHeader(request.headers, create_request_namespaceObject.BFF_OPERATION_CONTEXT_HEADER);
84
72
  return {