@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.
- package/dist/cjs/runtime/data-platform/index.js +2 -13
- package/dist/cjs/runtime/effect/adapter.js +78 -9
- package/dist/cjs/runtime/effect/edge.js +13 -17
- package/dist/cjs/runtime/effect/endpoint-contracts.js +130 -0
- package/dist/cjs/runtime/effect/handler.js +50 -5
- package/dist/cjs/runtime/effect/module.js +16 -7
- package/dist/cjs/runtime/effect/operation-context.js +1 -13
- package/dist/cjs/runtime/effect-client/runtime.js +266 -0
- package/dist/cjs/runtime/hono/adapter.js +21 -9
- package/dist/cjs/runtime/safe-failure.js +83 -0
- package/dist/cjs/utils/clientGenerator.js +4 -4
- package/dist/cjs/utils/crossProjectServerPolicy.js +104 -0
- package/dist/cjs/utils/effectClientGenerator.js +90 -483
- package/dist/esm/runtime/data-platform/index.mjs +2 -13
- package/dist/esm/runtime/effect/adapter.mjs +78 -9
- package/dist/esm/runtime/effect/edge.mjs +2 -9
- package/dist/esm/runtime/effect/endpoint-contracts.mjs +68 -0
- package/dist/esm/runtime/effect/handler.mjs +36 -4
- package/dist/esm/runtime/effect/module.mjs +17 -8
- package/dist/esm/runtime/effect/operation-context.mjs +1 -13
- package/dist/esm/runtime/effect-client/runtime.mjs +228 -0
- package/dist/esm/runtime/hono/adapter.mjs +21 -9
- package/dist/esm/runtime/safe-failure.mjs +45 -0
- package/dist/esm/utils/clientGenerator.mjs +5 -5
- package/dist/esm/utils/crossProjectServerPolicy.mjs +50 -0
- package/dist/esm/utils/effectClientGenerator.mjs +88 -484
- package/dist/esm-node/runtime/data-platform/index.mjs +2 -13
- package/dist/esm-node/runtime/effect/adapter.mjs +78 -9
- package/dist/esm-node/runtime/effect/edge.mjs +2 -9
- package/dist/esm-node/runtime/effect/endpoint-contracts.mjs +69 -0
- package/dist/esm-node/runtime/effect/handler.mjs +36 -4
- package/dist/esm-node/runtime/effect/module.mjs +17 -8
- package/dist/esm-node/runtime/effect/operation-context.mjs +1 -13
- package/dist/esm-node/runtime/effect-client/runtime.mjs +229 -0
- package/dist/esm-node/runtime/hono/adapter.mjs +21 -9
- package/dist/esm-node/runtime/safe-failure.mjs +46 -0
- package/dist/esm-node/utils/clientGenerator.mjs +5 -5
- package/dist/esm-node/utils/crossProjectServerPolicy.mjs +52 -0
- package/dist/esm-node/utils/effectClientGenerator.mjs +88 -484
- package/dist/types/runtime/effect/adapter.d.ts +25 -0
- package/dist/types/runtime/effect/endpoint-contracts.d.ts +62 -0
- package/dist/types/runtime/effect/handler.d.ts +30 -0
- package/dist/types/runtime/effect/module.d.ts +21 -1
- package/dist/types/runtime/effect-client/runtime.d.ts +71 -0
- package/dist/types/runtime/hono/adapter.d.ts +3 -0
- package/dist/types/runtime/safe-failure.d.ts +1 -0
- package/dist/types/utils/crossProjectServerPolicy.d.ts +35 -0
- package/dist/types/utils/effectClientGenerator.d.ts +15 -1
- package/package.json +24 -12
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
+
import { parseTraceparent } from "@modern-js/create-request";
|
|
2
3
|
import { trace as api_trace } from "@opentelemetry/api";
|
|
3
4
|
const DATA_BATCH_TRANSPORT_OTEL_EVENT = 'modernjs.data.batch';
|
|
4
5
|
function createDataBatchTransportTelemetryAttributes(event) {
|
|
@@ -24,7 +25,6 @@ function emitDataBatchTransportEvent(onEvent, event) {
|
|
|
24
25
|
const DEFAULT_DATA_ENVELOPE_HEADER = 'x-modernjs-data-envelope';
|
|
25
26
|
const DEFAULT_DATA_BATCH_ENDPOINT = '/_data/batch';
|
|
26
27
|
const DEFAULT_DATA_BATCH_HEADER = 'x-modernjs-data-batch';
|
|
27
|
-
const TRACEPARENT_REGEX = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/i;
|
|
28
28
|
function isPlainObject(value) {
|
|
29
29
|
if ('object' != typeof value || null === value || Array.isArray(value)) return false;
|
|
30
30
|
const proto = Object.getPrototypeOf(value);
|
|
@@ -121,18 +121,7 @@ function isValidHex(value, length) {
|
|
|
121
121
|
return value.length === length && /^[0-9a-f]+$/.test(value);
|
|
122
122
|
}
|
|
123
123
|
function parseTraceparentHeader(header) {
|
|
124
|
-
|
|
125
|
-
if (!match) return null;
|
|
126
|
-
const traceId = match[1].toLowerCase();
|
|
127
|
-
const spanId = match[2].toLowerCase();
|
|
128
|
-
const flags = match[3].toLowerCase();
|
|
129
|
-
if (isAllZeroHex(traceId) || isAllZeroHex(spanId)) return null;
|
|
130
|
-
const sampled = (0x1 & Number.parseInt(flags, 16)) === 1;
|
|
131
|
-
return {
|
|
132
|
-
traceId,
|
|
133
|
-
spanId,
|
|
134
|
-
sampled
|
|
135
|
-
};
|
|
124
|
+
return parseTraceparent(header) ?? null;
|
|
136
125
|
}
|
|
137
126
|
function formatTraceparentHeader(trace) {
|
|
138
127
|
const traceId = trace.traceId.toLowerCase();
|
|
@@ -1,8 +1,13 @@
|
|
|
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";
|
|
3
4
|
import { API_DIR, compatibleRequire, findExists, fs, isProd, logger } from "@modern-js/utils";
|
|
5
|
+
import { HttpApi } from "effect/unstable/httpapi";
|
|
4
6
|
import path from "path";
|
|
7
|
+
import { checkCrossProjectPolicyForRequest, resolveAdapterCrossProjectPolicy } from "../../utils/crossProjectServerPolicy.mjs";
|
|
8
|
+
import { createSafeFailureResponse } from "../safe-failure.mjs";
|
|
5
9
|
import { createEffectOperationContext, runWithEffectContext } from "./context.mjs";
|
|
10
|
+
import { collectEffectEndpoints, extractHttpApiFromModule, toOperationContractSources } from "./endpoint-contracts.mjs";
|
|
6
11
|
import { resolveEffectBffModuleHandler } from "./module.mjs";
|
|
7
12
|
const before = [
|
|
8
13
|
'custom-server-hook',
|
|
@@ -48,10 +53,73 @@ class EffectAdapter {
|
|
|
48
53
|
const entryWithoutExt = configuredEntry ? path.isAbsolute(configuredEntry) ? configuredEntry : path.resolve(appDirectory || process.cwd(), configuredEntry) : defaultEntry;
|
|
49
54
|
return findExists(JS_OR_TS_EXTS.map((ext)=>`${entryWithoutExt}${ext}`));
|
|
50
55
|
}
|
|
56
|
+
isApiRequestPath(requestPath, prefix, enableHandleWeb) {
|
|
57
|
+
if (!enableHandleWeb) return true;
|
|
58
|
+
const normalized = normalizePrefix(prefix);
|
|
59
|
+
if (!normalized) return true;
|
|
60
|
+
return requestPath === normalized || requestPath.startsWith(`${normalized}/`);
|
|
61
|
+
}
|
|
62
|
+
async collectLambdaContractSources() {
|
|
63
|
+
try {
|
|
64
|
+
const serverContext = this.api.getServerContext();
|
|
65
|
+
const appDir = serverContext.distDirectory || serverContext.appDirectory;
|
|
66
|
+
if (!appDir) return [];
|
|
67
|
+
const apiDir = 'string' == typeof serverContext.apiDirectory ? serverContext.apiDirectory : path.resolve(appDir, API_DIR);
|
|
68
|
+
const lambdaDir = 'string' == typeof serverContext.lambdaDirectory ? serverContext.lambdaDirectory : path.join(apiDir, 'lambda');
|
|
69
|
+
if (!await fs.pathExists(lambdaDir)) return [];
|
|
70
|
+
const apiRouter = new ApiRouter({
|
|
71
|
+
appDir,
|
|
72
|
+
apiDir,
|
|
73
|
+
lambdaDir,
|
|
74
|
+
prefix: this.prefix,
|
|
75
|
+
httpMethodDecider: this.api.getServerConfig()?.bff?.httpMethodDecider
|
|
76
|
+
});
|
|
77
|
+
const handlerInfos = await apiRouter.getApiHandlers();
|
|
78
|
+
return handlerInfos.map((info)=>({
|
|
79
|
+
name: info.name,
|
|
80
|
+
httpMethod: info.httpMethod,
|
|
81
|
+
routePath: info.routePath,
|
|
82
|
+
filename: info.filename,
|
|
83
|
+
handler: info.handler
|
|
84
|
+
}));
|
|
85
|
+
} catch (error) {
|
|
86
|
+
logger.warn(`[BFF][Effect] Failed to derive lambda operation contracts for the cross-project policy: ${String(error)}`);
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async refreshCrossProjectPolicy(mod) {
|
|
91
|
+
let contractSources = [];
|
|
92
|
+
if (mod) try {
|
|
93
|
+
const api = await extractHttpApiFromModule(mod, HttpApi.isHttpApi);
|
|
94
|
+
if (api) {
|
|
95
|
+
const reflect = (apiValue, handlers)=>HttpApi.reflect(apiValue, {
|
|
96
|
+
onGroup: handlers.onGroup ?? (()=>{}),
|
|
97
|
+
onEndpoint: handlers.onEndpoint
|
|
98
|
+
});
|
|
99
|
+
contractSources = toOperationContractSources(collectEffectEndpoints(reflect, api, this.prefix));
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
logger.warn(`[BFF][Effect] Failed to reflect HttpApi endpoints for the cross-project policy: ${String(error)}`);
|
|
103
|
+
}
|
|
104
|
+
let policy = resolveAdapterCrossProjectPolicy(this.api, contractSources);
|
|
105
|
+
if (policy?.enabled) {
|
|
106
|
+
const lambdaSources = await this.collectLambdaContractSources();
|
|
107
|
+
if (lambdaSources.length > 0) {
|
|
108
|
+
contractSources = [
|
|
109
|
+
...contractSources,
|
|
110
|
+
...lambdaSources
|
|
111
|
+
];
|
|
112
|
+
policy = resolveAdapterCrossProjectPolicy(this.api, contractSources);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
this.crossProjectPolicy = policy;
|
|
116
|
+
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).');
|
|
117
|
+
}
|
|
51
118
|
async loadEffectHandlerFromModule(mod) {
|
|
52
119
|
return resolveEffectBffModuleHandler(mod, {
|
|
53
120
|
openapi: this.api.getServerConfig()?.bff?.effect?.openapi,
|
|
54
121
|
dataPlatform: this.api.getServerConfig()?.bff?.effect?.dataPlatform,
|
|
122
|
+
validateRequest: (request)=>checkCrossProjectPolicyForRequest(request, this.crossProjectPolicy),
|
|
55
123
|
onWarning: (message)=>{
|
|
56
124
|
logger.warn(message);
|
|
57
125
|
}
|
|
@@ -81,6 +149,7 @@ class EffectAdapter {
|
|
|
81
149
|
this.handler = null;
|
|
82
150
|
return;
|
|
83
151
|
}
|
|
152
|
+
await this.refreshCrossProjectPolicy(mod);
|
|
84
153
|
const loaded = await this.loadEffectHandlerFromModule(mod);
|
|
85
154
|
if (!loaded) {
|
|
86
155
|
logger.warn(`[BFF][Effect] Invalid Effect entry module: ${entryFile}. Export { api, layer } or handler.`);
|
|
@@ -89,6 +158,7 @@ class EffectAdapter {
|
|
|
89
158
|
}
|
|
90
159
|
this.handler = loaded.handler;
|
|
91
160
|
this.dispose = loaded.dispose || null;
|
|
161
|
+
this.policyEnforcedInMiddleware = !loaded.appliesRequestValidator;
|
|
92
162
|
}
|
|
93
163
|
async disposeCurrentHandler() {
|
|
94
164
|
if (!this.dispose) return;
|
|
@@ -112,15 +182,7 @@ class EffectAdapter {
|
|
|
112
182
|
} catch (configError) {
|
|
113
183
|
logger.error(`Error in serverConfig.onError handler: ${configError}`);
|
|
114
184
|
}
|
|
115
|
-
|
|
116
|
-
return new Response(JSON.stringify({
|
|
117
|
-
message: error instanceof Error ? error.message : '[BFF] Internal Server Error'
|
|
118
|
-
}), {
|
|
119
|
-
status,
|
|
120
|
-
headers: {
|
|
121
|
-
'content-type': 'application/json; charset=utf-8'
|
|
122
|
-
}
|
|
123
|
-
});
|
|
185
|
+
return createSafeFailureResponse(error);
|
|
124
186
|
}
|
|
125
187
|
ensureJsonContext(c) {
|
|
126
188
|
const maybeJsonContext = c;
|
|
@@ -147,6 +209,8 @@ class EffectAdapter {
|
|
|
147
209
|
this.effectMiddleware = null;
|
|
148
210
|
this.handler = null;
|
|
149
211
|
this.dispose = null;
|
|
212
|
+
this.prefix = '/api';
|
|
213
|
+
this.policyEnforcedInMiddleware = false;
|
|
150
214
|
this.registerMiddleware = async (options)=>{
|
|
151
215
|
const { prefix, enableHandleWeb } = options;
|
|
152
216
|
const { bffRuntimeFramework, middlewares: globalMiddlewares } = this.api.getServerContext();
|
|
@@ -154,6 +218,7 @@ class EffectAdapter {
|
|
|
154
218
|
this.isEffect = false;
|
|
155
219
|
return;
|
|
156
220
|
}
|
|
221
|
+
this.prefix = prefix || this.prefix;
|
|
157
222
|
await this.reloadHandler();
|
|
158
223
|
this.effectMiddleware = {
|
|
159
224
|
name: 'effect-bff-handler',
|
|
@@ -166,6 +231,10 @@ class EffectAdapter {
|
|
|
166
231
|
if (enableHandleWeb) return void await next();
|
|
167
232
|
return this.handleRuntimeError(new Error('[BFF][Effect] Missing Effect entry. Define api/effect/index or configure bff.effect.entry.'), c);
|
|
168
233
|
}
|
|
234
|
+
if (this.crossProjectPolicy?.enabled && this.policyEnforcedInMiddleware && this.isApiRequestPath(c.req.path, prefix, enableHandleWeb)) {
|
|
235
|
+
const denial = checkCrossProjectPolicyForRequest(c.req.raw, this.crossProjectPolicy);
|
|
236
|
+
if (denial) return denial;
|
|
237
|
+
}
|
|
169
238
|
let response;
|
|
170
239
|
try {
|
|
171
240
|
const effectRequest = createRequestForMountedPrefix(c.req.raw, prefix);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
+
import { createSafeFailureResponse } from "../safe-failure.mjs";
|
|
2
3
|
import { resolveEffectBffModuleHandler } from "./module.mjs";
|
|
3
4
|
import { createEffectOperationContext } from "./operation-context.mjs";
|
|
4
5
|
export * from "./handler.mjs";
|
|
@@ -40,15 +41,7 @@ function createEdgeEffectContext(originalRequest, effectRequest, options) {
|
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
43
|
function createRuntimeErrorResponse(error) {
|
|
43
|
-
|
|
44
|
-
return new Response(JSON.stringify({
|
|
45
|
-
message: error instanceof Error ? error.message : '[BFF] Internal Server Error'
|
|
46
|
-
}), {
|
|
47
|
-
status,
|
|
48
|
-
headers: {
|
|
49
|
-
'content-type': 'application/json; charset=utf-8'
|
|
50
|
-
}
|
|
51
|
-
});
|
|
44
|
+
return createSafeFailureResponse(error);
|
|
52
45
|
}
|
|
53
46
|
async function dispatchEffectBffRequest(handler, request, options = {}) {
|
|
54
47
|
const requestPathname = new URL(request.url).pathname;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { createOperationContractHash } from "@modern-js/bff-core";
|
|
3
|
+
function ensureLeadingSlash(pathname) {
|
|
4
|
+
return pathname.startsWith('/') ? pathname : `/${pathname}`;
|
|
5
|
+
}
|
|
6
|
+
function normalizeEffectPrefix(prefix) {
|
|
7
|
+
if ('/' === prefix) return '';
|
|
8
|
+
return ensureLeadingSlash(prefix || '/api');
|
|
9
|
+
}
|
|
10
|
+
function getEffectRoutePath(prefix, endpointPath) {
|
|
11
|
+
const normalizedPrefix = normalizeEffectPrefix(prefix);
|
|
12
|
+
const normalizedEndpointPath = ensureLeadingSlash(endpointPath);
|
|
13
|
+
const finalEndpointPath = '/' === normalizedEndpointPath ? '' : endpointPath;
|
|
14
|
+
if (!normalizedPrefix && !finalEndpointPath) return '/';
|
|
15
|
+
return `${normalizedPrefix}${finalEndpointPath || ''}`;
|
|
16
|
+
}
|
|
17
|
+
function resolveEffectApiId(api) {
|
|
18
|
+
const fallback = 'EffectHttpApi';
|
|
19
|
+
if ('identifier' in api && 'string' == typeof api.identifier && api.identifier) return api.identifier;
|
|
20
|
+
return fallback;
|
|
21
|
+
}
|
|
22
|
+
function collectEffectEndpoints(reflect, api, prefix) {
|
|
23
|
+
const endpoints = [];
|
|
24
|
+
const apiId = resolveEffectApiId(api);
|
|
25
|
+
reflect(api, {
|
|
26
|
+
onGroup: ()=>{},
|
|
27
|
+
onEndpoint: ({ group, endpoint })=>{
|
|
28
|
+
endpoints.push({
|
|
29
|
+
apiId,
|
|
30
|
+
groupName: String(group.identifier),
|
|
31
|
+
endpointName: String(endpoint.name),
|
|
32
|
+
method: String(endpoint.method).toUpperCase(),
|
|
33
|
+
routePath: getEffectRoutePath(prefix, String(endpoint.path))
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return endpoints.sort((a, b)=>{
|
|
38
|
+
if (a.groupName === b.groupName) return a.endpointName.localeCompare(b.endpointName);
|
|
39
|
+
return a.groupName.localeCompare(b.groupName);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function toOperationContractSources(endpoints) {
|
|
43
|
+
return endpoints.map(createEffectOperationContractSource);
|
|
44
|
+
}
|
|
45
|
+
function createEffectOperationContractSource(endpoint) {
|
|
46
|
+
return {
|
|
47
|
+
name: endpoint.endpointName,
|
|
48
|
+
httpMethod: endpoint.method,
|
|
49
|
+
routePath: endpoint.routePath
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function isRecord(value) {
|
|
53
|
+
return 'object' == typeof value && null !== value;
|
|
54
|
+
}
|
|
55
|
+
async function extractHttpApiFromModule(mod, isHttpApi) {
|
|
56
|
+
if (!isRecord(mod)) return null;
|
|
57
|
+
if (isHttpApi(mod.api)) return mod.api;
|
|
58
|
+
const entry = mod.default;
|
|
59
|
+
if (isRecord(entry) && isHttpApi(entry.api)) return entry.api;
|
|
60
|
+
if ('function' == typeof entry && 0 === entry.length) {
|
|
61
|
+
const output = await entry();
|
|
62
|
+
if (isRecord(output) && isHttpApi(output.api)) return output.api;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
function createEffectEndpointContractHash(endpoint, requestId) {
|
|
67
|
+
return createOperationContractHash(createEffectOperationContractSource(endpoint), requestId);
|
|
68
|
+
}
|
|
69
|
+
export { collectEffectEndpoints, createEffectEndpointContractHash, createEffectOperationContractSource, ensureLeadingSlash, extractHttpApiFromModule, getEffectRoutePath, normalizeEffectPrefix, resolveEffectApiId, toOperationContractSources };
|
|
@@ -13,6 +13,10 @@ export * from "effect/unstable/httpapi";
|
|
|
13
13
|
export * from "effect/unstable/rpc";
|
|
14
14
|
import * as __rspack_external_effect_Context_f1289ca3 from "effect/Context";
|
|
15
15
|
const emptyEffectServiceContext = __rspack_external_effect_Context_f1289ca3.empty();
|
|
16
|
+
const EFFECT_VALIDATOR_AWARE_FACTORY = Symbol.for('modernjs.effect.validatorAware');
|
|
17
|
+
function isValidatorAwareHandlerFactory(factory) {
|
|
18
|
+
return 'function' == typeof factory && true === factory[EFFECT_VALIDATOR_AWARE_FACTORY];
|
|
19
|
+
}
|
|
16
20
|
function normalizeOpenApiPath(pathname) {
|
|
17
21
|
if (!pathname.startsWith('/')) return `/${pathname}`;
|
|
18
22
|
return pathname;
|
|
@@ -268,16 +272,38 @@ function defineEffectBff(definition) {
|
|
|
268
272
|
layer: definition.layer,
|
|
269
273
|
openapi: options?.openapi,
|
|
270
274
|
rpc: mergedRpcOptions,
|
|
271
|
-
dataPlatform: mergeDataPlatformOptions(definition.dataPlatform, options?.dataPlatform)
|
|
275
|
+
dataPlatform: mergeDataPlatformOptions(definition.dataPlatform, options?.dataPlatform),
|
|
276
|
+
validateRequest: options?.validateRequest
|
|
272
277
|
});
|
|
273
278
|
};
|
|
274
|
-
|
|
279
|
+
Object.defineProperty(createHandler, EFFECT_VALIDATOR_AWARE_FACTORY, {
|
|
280
|
+
value: true
|
|
281
|
+
});
|
|
282
|
+
const client = createLoaderMaterializedClientPlaceholder();
|
|
275
283
|
return {
|
|
276
284
|
...definition,
|
|
277
285
|
createHandler,
|
|
278
286
|
client
|
|
279
287
|
};
|
|
280
288
|
}
|
|
289
|
+
const LOADER_CLIENT_IGNORED_KEYS = new Set([
|
|
290
|
+
'then',
|
|
291
|
+
'catch',
|
|
292
|
+
'finally',
|
|
293
|
+
'toJSON',
|
|
294
|
+
'$$typeof'
|
|
295
|
+
]);
|
|
296
|
+
function createLoaderMaterializedClientPlaceholder() {
|
|
297
|
+
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.`);
|
|
299
|
+
};
|
|
300
|
+
return new Proxy(Object.create(null), {
|
|
301
|
+
get (_target, property) {
|
|
302
|
+
if ('symbol' == typeof property || LOADER_CLIENT_IGNORED_KEYS.has(property)) return;
|
|
303
|
+
return explain(property);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
281
307
|
function defineEffectRpcBff(definition) {
|
|
282
308
|
const createHandler = (options)=>createRpcApiHandler({
|
|
283
309
|
...definition,
|
|
@@ -304,6 +330,8 @@ function createHttpApiHandler(options) {
|
|
|
304
330
|
const envelopeHeader = options.dataPlatform?.envelopeHeader || DEFAULT_DATA_ENVELOPE_HEADER;
|
|
305
331
|
const normalizedEnvelopeHeader = envelopeHeader.toLowerCase();
|
|
306
332
|
const withDataPlatformValidation = async (request, context)=>{
|
|
333
|
+
const policyDenial = options.validateRequest?.(request);
|
|
334
|
+
if (policyDenial) return policyDenial;
|
|
307
335
|
const validationError = validateDataPlatformRequestEnvelope(request, options.dataPlatform);
|
|
308
336
|
if (validationError) return validationError;
|
|
309
337
|
return httpApiHandler.handler(request, context ?? emptyEffectServiceContext);
|
|
@@ -425,7 +453,11 @@ function createHttpApiHandler(options) {
|
|
|
425
453
|
const rpcHandler = createRpcApiHandler(options.rpc);
|
|
426
454
|
return {
|
|
427
455
|
handler: async (request, context)=>{
|
|
428
|
-
if (isRpcRequest(request, rpcPath))
|
|
456
|
+
if (isRpcRequest(request, rpcPath)) {
|
|
457
|
+
const policyDenial = options.validateRequest?.(request);
|
|
458
|
+
if (policyDenial) return policyDenial;
|
|
459
|
+
return rpcHandler.handler(request, context ?? emptyEffectServiceContext);
|
|
460
|
+
}
|
|
429
461
|
return handleHttpApiRequest(request);
|
|
430
462
|
},
|
|
431
463
|
dispose: async ()=>{
|
|
@@ -436,4 +468,4 @@ function createHttpApiHandler(options) {
|
|
|
436
468
|
}
|
|
437
469
|
};
|
|
438
470
|
}
|
|
439
|
-
export { HttpApiBuilder, HttpTraceContext, __rspack_external_effect_Config_29be8a92 as Config, __rspack_external_effect_Effect_194ac36c as Effect, __rspack_external_effect_Layer_16f7a8fc as Layer, __rspack_external_effect_Option_4d691636 as Option, __rspack_external_effect_Schema_f8472650 as Schema, createHttpApiHandler, defineEffectBff, defineEffectRpcBff };
|
|
471
|
+
export { EFFECT_VALIDATOR_AWARE_FACTORY, HttpApiBuilder, HttpTraceContext, __rspack_external_effect_Config_29be8a92 as Config, __rspack_external_effect_Effect_194ac36c as Effect, __rspack_external_effect_Layer_16f7a8fc as Layer, __rspack_external_effect_Option_4d691636 as Option, __rspack_external_effect_Schema_f8472650 as Schema, createHttpApiHandler, defineEffectBff, defineEffectRpcBff, isValidatorAwareHandlerFactory };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import { HttpApi } from "effect/unstable/httpapi";
|
|
3
|
-
import { createHttpApiHandler } from "./handler.mjs";
|
|
3
|
+
import { createHttpApiHandler, isValidatorAwareHandlerFactory } from "./handler.mjs";
|
|
4
4
|
import * as __rspack_external_effect_Context_f1289ca3 from "effect/Context";
|
|
5
5
|
function isRecord(value) {
|
|
6
6
|
return 'object' == typeof value && null !== value;
|
|
@@ -21,10 +21,13 @@ const emptyEffectServiceContext = __rspack_external_effect_Context_f1289ca3.empt
|
|
|
21
21
|
function callEffectBffRequestHandler(handler, request, context) {
|
|
22
22
|
return void 0 === context ? handler(request) : handler(request, context);
|
|
23
23
|
}
|
|
24
|
-
function createLoadedHandler(webHandler) {
|
|
24
|
+
function createLoadedHandler(webHandler, appliesRequestValidator) {
|
|
25
25
|
return {
|
|
26
26
|
handler: (request, context)=>callEffectBffRequestHandler(webHandler.handler, request, context),
|
|
27
|
-
dispose: webHandler.dispose
|
|
27
|
+
dispose: webHandler.dispose,
|
|
28
|
+
...appliesRequestValidator ? {
|
|
29
|
+
appliesRequestValidator: true
|
|
30
|
+
} : {}
|
|
28
31
|
};
|
|
29
32
|
}
|
|
30
33
|
function createLoadedHttpApiHandler(webHandler) {
|
|
@@ -33,7 +36,8 @@ function createLoadedHttpApiHandler(webHandler) {
|
|
|
33
36
|
const effectContext = isEffectServiceContext(context) ? context : emptyEffectServiceContext;
|
|
34
37
|
return webHandler.handler(request, effectContext);
|
|
35
38
|
},
|
|
36
|
-
dispose: webHandler.dispose
|
|
39
|
+
dispose: webHandler.dispose,
|
|
40
|
+
appliesRequestValidator: true
|
|
37
41
|
};
|
|
38
42
|
}
|
|
39
43
|
function resolveNormalizedEffectBffModuleHandler(normalizedModule, options = {}) {
|
|
@@ -59,11 +63,15 @@ function resolveNormalizedEffectBffModuleHandler(normalizedModule, options = {})
|
|
|
59
63
|
handler: normalizedModule.handler
|
|
60
64
|
};
|
|
61
65
|
if ('function' == typeof normalizedModule.createHandler) {
|
|
62
|
-
const
|
|
66
|
+
const factory = normalizedModule.createHandler;
|
|
67
|
+
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.');
|
|
69
|
+
const webHandler = factory({
|
|
63
70
|
openapi: options.openapi,
|
|
64
|
-
dataPlatform: options.dataPlatform
|
|
71
|
+
dataPlatform: options.dataPlatform,
|
|
72
|
+
validateRequest: options.validateRequest
|
|
65
73
|
});
|
|
66
|
-
return createLoadedHandler(webHandler);
|
|
74
|
+
return createLoadedHandler(webHandler, validatorAware);
|
|
67
75
|
}
|
|
68
76
|
if (isEffectApiDefinition(normalizedModule)) {
|
|
69
77
|
options.onWarning?.('[BFF][Effect] Detected { api, layer } export without createHandler. Prefer `defineEffectBff(...)` from @modern-js/plugin-bff/server to avoid module instance mismatch.');
|
|
@@ -71,7 +79,8 @@ function resolveNormalizedEffectBffModuleHandler(normalizedModule, options = {})
|
|
|
71
79
|
api: normalizedModule.api,
|
|
72
80
|
layer: normalizedModule.layer,
|
|
73
81
|
openapi: options.openapi,
|
|
74
|
-
dataPlatform: options.dataPlatform
|
|
82
|
+
dataPlatform: options.dataPlatform,
|
|
83
|
+
validateRequest: options.validateRequest
|
|
75
84
|
});
|
|
76
85
|
return createLoadedHttpApiHandler(webHandler);
|
|
77
86
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
-
import { BFF_LOCALE_HEADER, BFF_OPERATION_CONTEXT_DETAIL_HEADER, BFF_OPERATION_CONTEXT_HEADER, BFF_TRACEPARENT_HEADER } from "@modern-js/create-request";
|
|
3
|
-
const TRACEPARENT_REGEX = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/i;
|
|
2
|
+
import { BFF_LOCALE_HEADER, BFF_OPERATION_CONTEXT_DETAIL_HEADER, BFF_OPERATION_CONTEXT_HEADER, BFF_TRACEPARENT_HEADER, parseTraceparent } from "@modern-js/create-request";
|
|
4
3
|
const readHeader = (headers, header)=>{
|
|
5
4
|
const value = headers.get(header);
|
|
6
5
|
return value && value.length > 0 ? value : void 0;
|
|
@@ -9,17 +8,6 @@ const copyStringField = (target, details, key)=>{
|
|
|
9
8
|
const value = details[key];
|
|
10
9
|
if ('string' == typeof value && value.length > 0) target[key] = value;
|
|
11
10
|
};
|
|
12
|
-
const parseTraceparent = (traceparent)=>{
|
|
13
|
-
if (!traceparent) return;
|
|
14
|
-
const match = traceparent.trim().match(TRACEPARENT_REGEX);
|
|
15
|
-
if (!match) return;
|
|
16
|
-
const [, traceId, spanId] = match;
|
|
17
|
-
if (!traceId || !spanId) return;
|
|
18
|
-
return {
|
|
19
|
-
traceId: traceId.toLowerCase(),
|
|
20
|
-
spanId: spanId.toLowerCase()
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
11
|
const readOperationContextDetails = (request)=>{
|
|
24
12
|
const rawDetails = readHeader(request.headers, BFF_OPERATION_CONTEXT_DETAIL_HEADER);
|
|
25
13
|
if (!rawDetails) return {};
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { DEFAULT_DATA_BATCH_HEADER, DEFAULT_DATA_ENVELOPE_HEADER, createDataBatchTransport, createRequestEnvelope, encodeRequestEnvelopeHeader } from "../data-platform/index.mjs";
|
|
3
|
+
const METHODS_WITHOUT_BODY = new Set([
|
|
4
|
+
'GET',
|
|
5
|
+
'DELETE',
|
|
6
|
+
'HEAD',
|
|
7
|
+
'OPTIONS'
|
|
8
|
+
]);
|
|
9
|
+
const DATA_REQUEST_MODES = new Set([
|
|
10
|
+
'cache-first',
|
|
11
|
+
'stale-while-revalidate',
|
|
12
|
+
'network-only'
|
|
13
|
+
]);
|
|
14
|
+
const DATA_MUTATION_MODES = new Set([
|
|
15
|
+
'optimistic',
|
|
16
|
+
'pessimistic',
|
|
17
|
+
'fire-and-forget'
|
|
18
|
+
]);
|
|
19
|
+
const isRecord = (value)=>'object' == typeof value && null !== value;
|
|
20
|
+
const stringOrUndefined = (value)=>'string' == typeof value && value.length > 0 ? value : void 0;
|
|
21
|
+
const isDataRequestMode = (value)=>'string' == typeof value && DATA_REQUEST_MODES.has(value);
|
|
22
|
+
const isDataMutationMode = (value)=>'string' == typeof value && DATA_MUTATION_MODES.has(value);
|
|
23
|
+
const normalizeOrigin = (value)=>{
|
|
24
|
+
if ('string' != typeof value || 0 === value.length) return;
|
|
25
|
+
try {
|
|
26
|
+
return new URL(value).origin;
|
|
27
|
+
} catch {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const resolveRuntimeFetch = ()=>'function' == typeof fetch ? fetch.bind(globalThis) : void 0;
|
|
32
|
+
const resolveOrigin = (defaultOrigin)=>{
|
|
33
|
+
if ("u" > typeof window && window.location && 'string' == typeof window.location.origin && window.location.origin) return window.location.origin;
|
|
34
|
+
const globalLocation = globalThis?.location;
|
|
35
|
+
if (globalLocation && 'string' == typeof globalLocation.origin && globalLocation.origin) return globalLocation.origin;
|
|
36
|
+
return defaultOrigin;
|
|
37
|
+
};
|
|
38
|
+
const normalizeRequest = (method, request)=>{
|
|
39
|
+
if (!isRecord(request)) return {};
|
|
40
|
+
const payload = {
|
|
41
|
+
...request
|
|
42
|
+
};
|
|
43
|
+
if (isRecord(request.path) && !isRecord(payload.params)) payload.params = request.path;
|
|
44
|
+
if (isRecord(request.urlParams) && !isRecord(payload.query)) payload.query = request.urlParams;
|
|
45
|
+
if (isRecord(request.headers) && !isRecord(payload.headers)) payload.headers = request.headers;
|
|
46
|
+
if ('payload' in request && void 0 !== request.payload) if ("u" > typeof FormData && request.payload instanceof FormData && !('formData' in payload)) payload.formData = request.payload;
|
|
47
|
+
else if (METHODS_WITHOUT_BODY.has(method)) {
|
|
48
|
+
if (isRecord(request.payload)) payload.query = isRecord(payload.query) ? {
|
|
49
|
+
...payload.query,
|
|
50
|
+
...request.payload
|
|
51
|
+
} : request.payload;
|
|
52
|
+
else if (!('body' in payload)) payload.body = request.payload;
|
|
53
|
+
} else if (!isRecord(request.payload) || 'data' in payload) {
|
|
54
|
+
if (!('body' in payload)) payload.body = request.payload;
|
|
55
|
+
} else payload.data = request.payload;
|
|
56
|
+
return payload;
|
|
57
|
+
};
|
|
58
|
+
const resolveTargetOrigin = (dataPlatform, defaultOrigin)=>{
|
|
59
|
+
const explicitTargetOrigin = stringOrUndefined(dataPlatform.targetOrigin) || stringOrUndefined(dataPlatform.endpointOrigin);
|
|
60
|
+
if (explicitTargetOrigin) return explicitTargetOrigin;
|
|
61
|
+
return defaultOrigin;
|
|
62
|
+
};
|
|
63
|
+
const shouldAttachEnvelopeHeader = (dataPlatform, defaultOrigin)=>{
|
|
64
|
+
if (true === dataPlatform.allowCrossOriginEnvelope) return true;
|
|
65
|
+
const currentOrigin = normalizeOrigin(resolveOrigin(defaultOrigin));
|
|
66
|
+
const targetOrigin = normalizeOrigin(resolveTargetOrigin(dataPlatform, defaultOrigin));
|
|
67
|
+
if (!currentOrigin || !targetOrigin) return true;
|
|
68
|
+
return currentOrigin === targetOrigin;
|
|
69
|
+
};
|
|
70
|
+
const toEnvelopeInput = (normalizedRequest)=>{
|
|
71
|
+
const payload = {};
|
|
72
|
+
if (isRecord(normalizedRequest.params)) payload.path = normalizedRequest.params;
|
|
73
|
+
if (isRecord(normalizedRequest.query)) payload.query = normalizedRequest.query;
|
|
74
|
+
if ('data' in normalizedRequest && void 0 !== normalizedRequest.data) payload.data = normalizedRequest.data;
|
|
75
|
+
if ('body' in normalizedRequest && void 0 !== normalizedRequest.body) payload.body = normalizedRequest.body;
|
|
76
|
+
if ("u" > typeof FormData && normalizedRequest.formData instanceof FormData) payload.formData = Array.from(normalizedRequest.formData.entries()).map(([key, value])=>[
|
|
77
|
+
key,
|
|
78
|
+
String(value)
|
|
79
|
+
]);
|
|
80
|
+
if ("u" > typeof URLSearchParams && normalizedRequest.formUrlencoded instanceof URLSearchParams) payload.formUrlencoded = normalizedRequest.formUrlencoded.toString();
|
|
81
|
+
return payload;
|
|
82
|
+
};
|
|
83
|
+
const createGeneratedEffectClient = (manifest, config, requestRuntime)=>{
|
|
84
|
+
const createRequest = requestRuntime.createRequest;
|
|
85
|
+
const configureRequest = 'function' == typeof requestRuntime.configure ? requestRuntime.configure : void 0;
|
|
86
|
+
const createRequestContextHeaders = 'function' == typeof requestRuntime.createRequestContextHeaders ? requestRuntime.createRequestContextHeaders : void 0;
|
|
87
|
+
const defaultOrigin = config.defaultOrigin;
|
|
88
|
+
const httpMethodDecider = config.httpMethodDecider || 'functionName';
|
|
89
|
+
const port = config.useEnvPort && "u" > typeof process && process.env && process.env.PORT ? process.env.PORT : config.port;
|
|
90
|
+
if (config.requestId && configureRequest) {
|
|
91
|
+
const configurePayload = {
|
|
92
|
+
requestId: config.requestId,
|
|
93
|
+
requireEnvelope: true,
|
|
94
|
+
identityBinding: {
|
|
95
|
+
enabled: true,
|
|
96
|
+
strict: true
|
|
97
|
+
},
|
|
98
|
+
operationContract: {
|
|
99
|
+
enabled: true,
|
|
100
|
+
strict: true,
|
|
101
|
+
requireSchemaHash: true,
|
|
102
|
+
requireOperationVersion: true
|
|
103
|
+
},
|
|
104
|
+
setDomain: ()=>resolveOrigin(defaultOrigin)
|
|
105
|
+
};
|
|
106
|
+
const runtimeFetch = resolveRuntimeFetch();
|
|
107
|
+
if (false !== config.batch.enabled && runtimeFetch) configurePayload.request = createDataBatchTransport({
|
|
108
|
+
fetch: runtimeFetch,
|
|
109
|
+
endpoint: config.batch.endpoint,
|
|
110
|
+
flushIntervalMs: config.batch.flushIntervalMs,
|
|
111
|
+
maxBatchSize: config.batch.maxBatchSize,
|
|
112
|
+
maxBatchBytes: config.batch.maxBatchBytes,
|
|
113
|
+
requestTimeoutMs: config.batch.requestTimeoutMs,
|
|
114
|
+
allowedMethods: config.batch.allowedMethods
|
|
115
|
+
});
|
|
116
|
+
configureRequest(configurePayload);
|
|
117
|
+
}
|
|
118
|
+
const createEffectRequestContext = (requestContext)=>{
|
|
119
|
+
if (!isRecord(requestContext)) return {};
|
|
120
|
+
const headers = createRequestContextHeaders ? createRequestContextHeaders(requestContext) : {};
|
|
121
|
+
return {
|
|
122
|
+
...requestContext,
|
|
123
|
+
headers
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
const applyRequestContext = (normalizedRequest, request)=>{
|
|
127
|
+
if (!isRecord(request) || !isRecord(request.requestContext)) return normalizedRequest;
|
|
128
|
+
const requestContext = createEffectRequestContext(request.requestContext);
|
|
129
|
+
const requestHeaders = isRecord(requestContext.headers) ? requestContext.headers : {};
|
|
130
|
+
if (0 === Object.keys(requestHeaders).length) return normalizedRequest;
|
|
131
|
+
return {
|
|
132
|
+
...normalizedRequest,
|
|
133
|
+
headers: {
|
|
134
|
+
...requestHeaders,
|
|
135
|
+
...isRecord(normalizedRequest.headers) ? normalizedRequest.headers : {}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
const prepareEffectRequest = (endpoint, operation, request)=>{
|
|
140
|
+
const normalizedRequest = applyRequestContext(normalizeRequest(endpoint.method, request), request);
|
|
141
|
+
const dataPlatform = isRecord(request) && isRecord(request.dataPlatform) ? request.dataPlatform : {};
|
|
142
|
+
const strictEnvelope = true === dataPlatform.requireEnvelope || true === dataPlatform.strict;
|
|
143
|
+
if (!strictEnvelope && !shouldAttachEnvelopeHeader(dataPlatform, defaultOrigin)) return normalizedRequest;
|
|
144
|
+
try {
|
|
145
|
+
const namespace = stringOrUndefined(dataPlatform.appNamespace) || config.appNamespace;
|
|
146
|
+
const origin = stringOrUndefined(dataPlatform.origin) || resolveOrigin(defaultOrigin);
|
|
147
|
+
const envelope = createRequestEnvelope({
|
|
148
|
+
operation: {
|
|
149
|
+
...operation,
|
|
150
|
+
appNamespace: namespace
|
|
151
|
+
},
|
|
152
|
+
scope: {
|
|
153
|
+
appNamespace: namespace,
|
|
154
|
+
origin,
|
|
155
|
+
tenantId: stringOrUndefined(dataPlatform.tenantId),
|
|
156
|
+
userId: stringOrUndefined(dataPlatform.userId),
|
|
157
|
+
sessionId: stringOrUndefined(dataPlatform.sessionId)
|
|
158
|
+
},
|
|
159
|
+
requestInput: {
|
|
160
|
+
method: endpoint.method,
|
|
161
|
+
routePath: endpoint.routePath,
|
|
162
|
+
payload: toEnvelopeInput(normalizedRequest)
|
|
163
|
+
},
|
|
164
|
+
requestMode: isDataRequestMode(dataPlatform.requestMode) ? dataPlatform.requestMode : void 0,
|
|
165
|
+
mutationMode: isDataMutationMode(dataPlatform.mutationMode) ? dataPlatform.mutationMode : void 0,
|
|
166
|
+
selectionPlan: isRecord(dataPlatform.selectionPlan) ? dataPlatform.selectionPlan : void 0,
|
|
167
|
+
traceContext: isRecord(dataPlatform.traceContext) ? dataPlatform.traceContext : void 0,
|
|
168
|
+
requireTraceContext: true === dataPlatform.requireTraceContext
|
|
169
|
+
});
|
|
170
|
+
const headerName = stringOrUndefined(dataPlatform.envelopeHeader) || DEFAULT_DATA_ENVELOPE_HEADER;
|
|
171
|
+
const headers = isRecord(normalizedRequest.headers) ? {
|
|
172
|
+
...normalizedRequest.headers
|
|
173
|
+
} : {};
|
|
174
|
+
if (false === dataPlatform.batch) headers[DEFAULT_DATA_BATCH_HEADER] = 'off';
|
|
175
|
+
headers[headerName] = encodeRequestEnvelopeHeader(envelope);
|
|
176
|
+
return {
|
|
177
|
+
...normalizedRequest,
|
|
178
|
+
headers
|
|
179
|
+
};
|
|
180
|
+
} catch (error) {
|
|
181
|
+
if (strictEnvelope) throw error;
|
|
182
|
+
return normalizedRequest;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
const client = {};
|
|
186
|
+
const operationManifest = {};
|
|
187
|
+
for (const endpoint of manifest.endpoints){
|
|
188
|
+
const operationId = `${endpoint.method}:${endpoint.routePath}`;
|
|
189
|
+
const operation = {
|
|
190
|
+
appNamespace: config.appNamespace,
|
|
191
|
+
apiId: endpoint.apiId,
|
|
192
|
+
group: endpoint.group,
|
|
193
|
+
endpoint: endpoint.endpoint,
|
|
194
|
+
operationId,
|
|
195
|
+
routePath: endpoint.routePath,
|
|
196
|
+
method: endpoint.method,
|
|
197
|
+
operationVersion: endpoint.operationVersion,
|
|
198
|
+
schemaHash: endpoint.schemaHash,
|
|
199
|
+
version: endpoint.operationVersion
|
|
200
|
+
};
|
|
201
|
+
const sender = createRequest({
|
|
202
|
+
path: endpoint.routePath,
|
|
203
|
+
method: endpoint.method,
|
|
204
|
+
port,
|
|
205
|
+
operationContext: {
|
|
206
|
+
operationId,
|
|
207
|
+
routePath: endpoint.routePath,
|
|
208
|
+
method: endpoint.method,
|
|
209
|
+
schemaHash: endpoint.schemaHash,
|
|
210
|
+
operationVersion: endpoint.operationVersion
|
|
211
|
+
},
|
|
212
|
+
httpMethodDecider,
|
|
213
|
+
...config.requestId ? {
|
|
214
|
+
requestId: config.requestId
|
|
215
|
+
} : {}
|
|
216
|
+
});
|
|
217
|
+
const call = (request = {})=>sender(prepareEffectRequest(endpoint, operation, request));
|
|
218
|
+
client[endpoint.group] ??= {};
|
|
219
|
+
client[endpoint.group][endpoint.endpoint] = call;
|
|
220
|
+
operationManifest[endpoint.group] ??= {};
|
|
221
|
+
operationManifest[endpoint.group][endpoint.endpoint] = operation;
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
client,
|
|
225
|
+
operationManifest,
|
|
226
|
+
createEffectRequestContext
|
|
227
|
+
};
|
|
228
|
+
};
|
|
229
|
+
export { createGeneratedEffectClient };
|