@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.
- package/dist/cjs/runtime/effect/adapter.js +12 -59
- package/dist/cjs/runtime/effect/edge.js +1 -1
- package/dist/cjs/runtime/effect/handler.js +40 -3
- package/dist/cjs/runtime/effect/module.js +46 -27
- package/dist/cjs/runtime/effect-client/index.js +4 -0
- package/dist/cjs/utils/effectClientGenerator.js +13 -8
- package/dist/esm/runtime/effect/adapter.mjs +12 -59
- package/dist/esm/runtime/effect/edge.mjs +1 -1
- package/dist/esm/runtime/effect/handler.mjs +40 -3
- package/dist/esm/runtime/effect/module.mjs +46 -27
- package/dist/esm/runtime/effect-client/index.mjs +2 -1
- package/dist/esm/utils/effectClientGenerator.mjs +13 -8
- package/dist/esm-node/runtime/effect/adapter.mjs +12 -59
- package/dist/esm-node/runtime/effect/edge.mjs +1 -1
- package/dist/esm-node/runtime/effect/handler.mjs +40 -3
- package/dist/esm-node/runtime/effect/module.mjs +46 -27
- package/dist/esm-node/runtime/effect-client/index.mjs +2 -1
- package/dist/esm-node/utils/effectClientGenerator.mjs +13 -8
- package/dist/types/runtime/effect/adapter.d.ts +3 -19
- package/dist/types/runtime/effect/handler.d.ts +2 -5
- package/dist/types/runtime/effect/module.d.ts +3 -16
- package/dist/types/runtime/effect-client/index.d.ts +2 -3
- package/package.json +13 -13
|
@@ -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
|
|
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))
|
|
44
|
-
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))
|
|
48
|
-
handler
|
|
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))
|
|
62
|
-
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 &&
|
|
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
|
|
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))
|
|
98
|
-
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))
|
|
102
|
-
handler
|
|
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))
|
|
106
|
-
handler
|
|
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
|
|
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
|
|
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
|
|
210
|
+
declare const apiModule: {
|
|
211
211
|
client: GeneratedEffectClient;
|
|
212
212
|
createEffectRequestContext: typeof createEffectRequestContext;
|
|
213
213
|
operationManifest: GeneratedEffectOperationManifest;
|
|
214
214
|
};
|
|
215
215
|
|
|
216
|
-
export default
|
|
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
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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:
|
|
126
|
-
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 }
|
|
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-
|
|
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/
|
|
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 }
|
|
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]
|
|
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
|
|
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(
|
|
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
|
|
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))
|
|
45
|
-
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))
|
|
49
|
-
handler
|
|
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))
|
|
63
|
-
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 &&
|
|
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
|
|
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))
|
|
99
|
-
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))
|
|
103
|
-
handler
|
|
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))
|
|
107
|
-
handler
|
|
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
|
|
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
|
|
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
|
|
212
|
+
declare const apiModule: {
|
|
213
213
|
client: GeneratedEffectClient;
|
|
214
214
|
createEffectRequestContext: typeof createEffectRequestContext;
|
|
215
215
|
operationManifest: GeneratedEffectOperationManifest;
|
|
216
216
|
};
|
|
217
217
|
|
|
218
|
-
export default
|
|
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
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
*
|
|
37
|
-
*
|
|
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
|
|
148
|
-
*
|
|
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
|
*/
|