@bleedingdev/modern-js-plugin-bff 3.5.0-ultramodern.98 → 3.8.1-ultramodern.1
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/cli/compress.js +1 -1
- package/dist/cjs/cli/generator.js +106 -97
- package/dist/cjs/cli/serverGlobalVars.js +43 -57
- package/dist/cjs/cli.js +6 -2
- package/dist/cjs/loader.js +6 -5
- package/dist/cjs/runtime/effect/adapter/cross-project-policy.js +17 -12
- package/dist/cjs/runtime/effect/adapter/entry.js +13 -5
- package/dist/cjs/runtime/effect/adapter/error-response.js +11 -9
- package/dist/cjs/runtime/effect/adapter.js +2 -2
- package/dist/cjs/runtime/effect/backend-federation/load.js +26 -17
- package/dist/cjs/runtime/effect/backend-federation/plugin.js +12 -2
- package/dist/cjs/runtime/effect/backend-federation/remotes.js +5 -4
- package/dist/cjs/runtime/effect/backend-federation/runtime.js +80 -16
- package/dist/cjs/runtime/effect/edge-dispatcher.js +148 -0
- package/dist/cjs/runtime/effect/edge.js +19 -100
- package/dist/cjs/runtime/effect/endpoint-contracts.js +6 -1
- package/dist/cjs/runtime/effect/handler/definition.js +1 -0
- package/dist/cjs/runtime/effect/handler/http.js +17 -6
- package/dist/cjs/runtime/hono/adapter.js +32 -79
- package/dist/cjs/server.js +19 -30
- package/dist/cjs/utils/client-generator/write-package.js +1 -1
- package/dist/cjs/utils/crossProjectApiPlugin.js +21 -2
- package/dist/cjs/utils/effect-client-generator/generator.js +17 -16
- package/dist/cjs/utils/effect-client-generator/paths.js +4 -15
- package/dist/cjs/utils/effect-client-generator/rendering.js +8 -19
- package/dist/cjs/utils/effect-client-generator/runtime.js +15 -32
- package/dist/cjs/utils/effectSourceLoader.js +70 -10
- package/dist/cjs/utils/pluginGenerator.js +4 -4
- package/dist/esm/cli/compress.mjs +1 -1
- package/dist/esm/cli/generator.mjs +108 -98
- package/dist/esm/cli/serverGlobalVars.mjs +44 -48
- package/dist/esm/cli.mjs +2 -0
- package/dist/esm/loader.mjs +6 -5
- package/dist/esm/runtime/effect/adapter/cross-project-policy.mjs +17 -12
- package/dist/esm/runtime/effect/adapter/entry.mjs +13 -5
- package/dist/esm/runtime/effect/adapter/error-response.mjs +11 -9
- package/dist/esm/runtime/effect/adapter.mjs +3 -3
- package/dist/esm/runtime/effect/backend-federation/load.mjs +26 -17
- package/dist/esm/runtime/effect/backend-federation/plugin.mjs +12 -2
- package/dist/esm/runtime/effect/backend-federation/remotes.mjs +5 -4
- package/dist/esm/runtime/effect/backend-federation/runtime.mjs +80 -16
- package/dist/esm/runtime/effect/edge-dispatcher.mjs +38 -0
- package/dist/esm/runtime/effect/edge.mjs +1 -38
- package/dist/esm/runtime/effect/endpoint-contracts.mjs +6 -1
- package/dist/esm/runtime/effect/handler/definition.mjs +1 -0
- package/dist/esm/runtime/effect/handler/http.mjs +17 -6
- package/dist/esm/runtime/hono/adapter.mjs +33 -80
- package/dist/esm/server.mjs +18 -29
- package/dist/esm/utils/client-generator/write-package.mjs +1 -1
- package/dist/esm/utils/crossProjectApiPlugin.mjs +16 -3
- package/dist/esm/utils/effect-client-generator/generator.mjs +17 -16
- package/dist/esm/utils/effect-client-generator/paths.mjs +5 -6
- package/dist/esm/utils/effect-client-generator/rendering.mjs +9 -10
- package/dist/esm/utils/effect-client-generator/runtime.mjs +16 -23
- package/dist/esm/utils/effectSourceLoader.mjs +69 -12
- package/dist/esm/utils/pluginGenerator.mjs +5 -5
- package/dist/esm-node/cli/compress.mjs +1 -1
- package/dist/esm-node/cli/generator.mjs +108 -98
- package/dist/esm-node/cli/serverGlobalVars.mjs +44 -48
- package/dist/esm-node/cli.mjs +2 -0
- package/dist/esm-node/loader.mjs +6 -5
- package/dist/esm-node/runtime/effect/adapter/cross-project-policy.mjs +17 -12
- package/dist/esm-node/runtime/effect/adapter/entry.mjs +13 -5
- package/dist/esm-node/runtime/effect/adapter/error-response.mjs +11 -9
- package/dist/esm-node/runtime/effect/adapter.mjs +3 -3
- package/dist/esm-node/runtime/effect/backend-federation/load.mjs +26 -17
- package/dist/esm-node/runtime/effect/backend-federation/plugin.mjs +12 -2
- package/dist/esm-node/runtime/effect/backend-federation/remotes.mjs +5 -4
- package/dist/esm-node/runtime/effect/backend-federation/runtime.mjs +80 -16
- package/dist/esm-node/runtime/effect/edge-dispatcher.mjs +39 -0
- package/dist/esm-node/runtime/effect/edge.mjs +1 -38
- package/dist/esm-node/runtime/effect/endpoint-contracts.mjs +6 -1
- package/dist/esm-node/runtime/effect/handler/definition.mjs +1 -0
- package/dist/esm-node/runtime/effect/handler/http.mjs +17 -6
- package/dist/esm-node/runtime/hono/adapter.mjs +33 -80
- package/dist/esm-node/server.mjs +18 -29
- package/dist/esm-node/utils/client-generator/write-package.mjs +1 -1
- package/dist/esm-node/utils/crossProjectApiPlugin.mjs +16 -3
- package/dist/esm-node/utils/effect-client-generator/generator.mjs +17 -16
- package/dist/esm-node/utils/effect-client-generator/paths.mjs +5 -6
- package/dist/esm-node/utils/effect-client-generator/rendering.mjs +9 -10
- package/dist/esm-node/utils/effect-client-generator/runtime.mjs +16 -23
- package/dist/esm-node/utils/effectSourceLoader.mjs +69 -12
- package/dist/esm-node/utils/pluginGenerator.mjs +5 -5
- package/dist/types/cli/generator.d.ts +2 -2
- package/dist/types/cli/serverGlobalVars.d.ts +4 -2
- package/dist/types/cli.d.ts +1 -0
- package/dist/types/runtime/effect/adapter/cross-project-policy.d.ts +1 -1
- package/dist/types/runtime/effect/backend-federation/runtime.d.ts +1 -1
- package/dist/types/runtime/effect/edge-dispatcher.d.ts +26 -0
- package/dist/types/runtime/effect/edge.d.ts +1 -26
- package/dist/types/runtime/effect/endpoint-contracts.d.ts +6 -0
- package/dist/types/runtime/effect/handler/definition.d.ts +2 -12
- package/dist/types/runtime/effect/handler/http.d.ts +3 -2
- package/dist/types/runtime/effect/handler/openapi.d.ts +1 -1
- package/dist/types/runtime/effect/handler/types.d.ts +10 -5
- package/dist/types/runtime/effect-client/index.d.ts +1 -1
- package/dist/types/runtime/hono/adapter.d.ts +14 -8
- package/dist/types/utils/crossProjectApiPlugin.d.ts +2 -0
- package/dist/types/utils/effectSourceLoader.d.ts +10 -0
- package/dist/types/utils/pluginGenerator.d.ts +3 -1
- package/package.json +44 -16
|
@@ -3,10 +3,11 @@ function collectRemotes(options) {
|
|
|
3
3
|
const remotes = [
|
|
4
4
|
...options.remotes ?? []
|
|
5
5
|
];
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const configuredRemote = options.remote;
|
|
7
|
+
if (void 0 !== configuredRemote) {
|
|
8
|
+
const existingIndex = remotes.findIndex((remote)=>remote.name === configuredRemote.name);
|
|
9
|
+
if (existingIndex >= 0) remotes[existingIndex] = configuredRemote;
|
|
10
|
+
else remotes.push(configuredRemote);
|
|
10
11
|
}
|
|
11
12
|
return remotes;
|
|
12
13
|
}
|
|
@@ -1,27 +1,91 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
+
import { ModuleFederation } from "@module-federation/runtime";
|
|
2
3
|
import { loadBackendFederationExpose } from "./entry.mjs";
|
|
4
|
+
import { createBackendFederationLoadEntryPlugin } from "./plugin.mjs";
|
|
3
5
|
import { collectRemotes } from "./remotes.mjs";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
return `./${normalizeExpose(expose)}`;
|
|
6
|
+
function _check_private_redeclaration(obj, privateCollection) {
|
|
7
|
+
if (privateCollection.has(obj)) throw new TypeError("Cannot initialize the same private elements twice on an object");
|
|
7
8
|
}
|
|
9
|
+
function _class_apply_descriptor_get(receiver, descriptor) {
|
|
10
|
+
if (descriptor.get) return descriptor.get.call(receiver);
|
|
11
|
+
return descriptor.value;
|
|
12
|
+
}
|
|
13
|
+
function _class_apply_descriptor_set(receiver, descriptor, value) {
|
|
14
|
+
if (descriptor.set) descriptor.set.call(receiver, value);
|
|
15
|
+
else {
|
|
16
|
+
if (!descriptor.writable) throw new TypeError("attempted to set read only private field");
|
|
17
|
+
descriptor.value = value;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function _class_extract_field_descriptor(receiver, privateMap, action) {
|
|
21
|
+
if (!privateMap.has(receiver)) throw new TypeError("attempted to " + action + " private field on non-instance");
|
|
22
|
+
return privateMap.get(receiver);
|
|
23
|
+
}
|
|
24
|
+
function _class_private_field_get(receiver, privateMap) {
|
|
25
|
+
var descriptor = _class_extract_field_descriptor(receiver, privateMap, "get");
|
|
26
|
+
return _class_apply_descriptor_get(receiver, descriptor);
|
|
27
|
+
}
|
|
28
|
+
function _class_private_field_init(obj, privateMap, value) {
|
|
29
|
+
_check_private_redeclaration(obj, privateMap);
|
|
30
|
+
privateMap.set(obj, value);
|
|
31
|
+
}
|
|
32
|
+
function _class_private_field_set(receiver, privateMap, value) {
|
|
33
|
+
var descriptor = _class_extract_field_descriptor(receiver, privateMap, "set");
|
|
34
|
+
_class_apply_descriptor_set(receiver, descriptor, value);
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
let nextRuntimeId = 0;
|
|
8
38
|
function parseRemoteRequest(request) {
|
|
9
39
|
const [remoteName, ...exposeParts] = request.split('/');
|
|
10
|
-
if (
|
|
11
|
-
return
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
40
|
+
if (void 0 === remoteName || 0 === remoteName.length || 0 === exposeParts.length) return;
|
|
41
|
+
return remoteName;
|
|
42
|
+
}
|
|
43
|
+
function createLocalEntryPlugin(options, remotes) {
|
|
44
|
+
return createBackendFederationLoadEntryPlugin({
|
|
45
|
+
resolveEntry (resolvedRemote) {
|
|
46
|
+
const remote = remotes.find((candidate)=>candidate.name === resolvedRemote.name);
|
|
47
|
+
if (void 0 === remote) return;
|
|
48
|
+
return {
|
|
49
|
+
get (expose) {
|
|
50
|
+
return ()=>loadBackendFederationExpose(remote, expose, options);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function isolateRegisteredRemoteEntries(remotes) {
|
|
57
|
+
const runtimeId = nextRuntimeId;
|
|
58
|
+
nextRuntimeId += 1;
|
|
59
|
+
return remotes.map((remote)=>({
|
|
60
|
+
...remote,
|
|
61
|
+
entry: `${remote.entry}#modernjs-backend-runtime-${runtimeId}`
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
var _remoteNames = /*#__PURE__*/ new WeakMap();
|
|
65
|
+
class BackendFederationRuntime extends ModuleFederation {
|
|
66
|
+
loadRemote(request, loadOptions) {
|
|
67
|
+
const remoteName = parseRemoteRequest(request);
|
|
68
|
+
if (void 0 === remoteName) return Promise.reject(new Error(`[BFF][Effect] Invalid backend federation request ${request}.`));
|
|
69
|
+
if (!_class_private_field_get(this, _remoteNames).has(remoteName)) return Promise.reject(new Error(`[BFF][Effect] Missing backend federation remote ${remoteName}.`));
|
|
70
|
+
return super.loadRemote(request, loadOptions);
|
|
71
|
+
}
|
|
72
|
+
constructor(options, remotes){
|
|
73
|
+
super({
|
|
74
|
+
name: options.hostName,
|
|
75
|
+
remotes: isolateRegisteredRemoteEntries(remotes),
|
|
76
|
+
shared: {},
|
|
77
|
+
plugins: [
|
|
78
|
+
createLocalEntryPlugin(options, remotes)
|
|
79
|
+
]
|
|
80
|
+
}), _class_private_field_init(this, _remoteNames, {
|
|
81
|
+
writable: true,
|
|
82
|
+
value: void 0
|
|
83
|
+
});
|
|
84
|
+
_class_private_field_set(this, _remoteNames, new Set(remotes.map((remote)=>remote.name)));
|
|
85
|
+
}
|
|
15
86
|
}
|
|
16
87
|
function createBackendFederationRuntime(options) {
|
|
17
88
|
const remotes = collectRemotes(options);
|
|
18
|
-
return
|
|
19
|
-
async loadRemote (request) {
|
|
20
|
-
const { remoteName, expose } = parseRemoteRequest(request);
|
|
21
|
-
const remote = remotes.find((candidate)=>candidate.name === remoteName);
|
|
22
|
-
if (!remote) throw new Error(`[BFF][Effect] Missing backend federation remote ${remoteName}.`);
|
|
23
|
-
return loadBackendFederationExpose(remote, expose, options);
|
|
24
|
-
}
|
|
25
|
-
};
|
|
89
|
+
return new BackendFederationRuntime(options, remotes);
|
|
26
90
|
}
|
|
27
91
|
export { createBackendFederationRuntime };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { dispatchEffectBffRequestWithContext } from "./dispatch.mjs";
|
|
3
|
+
import { runWithEffectContext, useEffectContext, useOperationContext } from "./edge-context.mjs";
|
|
4
|
+
import { resolveEffectBffModuleHandler } from "./module.mjs";
|
|
5
|
+
export * from "./handler.mjs";
|
|
6
|
+
async function dispatchEffectBffRequest(handler, request, options = {}) {
|
|
7
|
+
return dispatchEffectBffRequestWithContext(handler, request, {
|
|
8
|
+
...options,
|
|
9
|
+
runWithEffectContext: runWithEffectContext
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
async function createEffectBffEdgeDispatcher(options) {
|
|
13
|
+
const loaded = await resolveEffectBffModuleHandler(options.module, {
|
|
14
|
+
openapi: options.openapi,
|
|
15
|
+
dataPlatform: options.dataPlatform,
|
|
16
|
+
onWarning: options.onWarning
|
|
17
|
+
});
|
|
18
|
+
if (!loaded) throw new Error('[BFF][Effect] Invalid Effect edge module. Export defineEffectBff(...) or a { api, layer } HttpApi module.');
|
|
19
|
+
return {
|
|
20
|
+
dispatch: (request, dispatchOptions = {})=>dispatchEffectBffRequest(loaded.handler, request, {
|
|
21
|
+
...dispatchOptions,
|
|
22
|
+
prefix: options.prefix,
|
|
23
|
+
onError: options.onError
|
|
24
|
+
}),
|
|
25
|
+
dispose: async ()=>{
|
|
26
|
+
await loaded.dispose?.();
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const createEffectBffTestHandler = createEffectBffEdgeHandler;
|
|
31
|
+
async function createEffectBffEdgeHandler(options) {
|
|
32
|
+
const dispatcher = await createEffectBffEdgeDispatcher(options);
|
|
33
|
+
return {
|
|
34
|
+
handler: dispatcher.dispatch,
|
|
35
|
+
dispose: dispatcher.dispose
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export { createEffectOperationContext } from "./operation-context.mjs";
|
|
39
|
+
export { createEffectBffEdgeDispatcher, createEffectBffEdgeHandler, createEffectBffTestHandler, dispatchEffectBffRequest, runWithEffectContext, useEffectContext, useOperationContext };
|
|
@@ -1,40 +1,3 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
-
|
|
3
|
-
import { runWithEffectContext, useEffectContext, useOperationContext } from "./edge-context.mjs";
|
|
4
|
-
import { resolveEffectBffModuleHandler } from "./module.mjs";
|
|
5
|
-
export * from "./handler.mjs";
|
|
6
|
-
async function dispatchEffectBffRequest(handler, request, options = {}) {
|
|
7
|
-
return dispatchEffectBffRequestWithContext(handler, request, {
|
|
8
|
-
...options,
|
|
9
|
-
runWithEffectContext: runWithEffectContext
|
|
10
|
-
});
|
|
11
|
-
}
|
|
12
|
-
async function createEffectBffEdgeDispatcher(options) {
|
|
13
|
-
const loaded = await resolveEffectBffModuleHandler(options.module, {
|
|
14
|
-
openapi: options.openapi,
|
|
15
|
-
dataPlatform: options.dataPlatform,
|
|
16
|
-
onWarning: options.onWarning
|
|
17
|
-
});
|
|
18
|
-
if (!loaded) throw new Error('[BFF][Effect] Invalid Effect edge module. Export defineEffectBff(...) or a { api, layer } HttpApi module.');
|
|
19
|
-
return {
|
|
20
|
-
dispatch: (request, dispatchOptions = {})=>dispatchEffectBffRequest(loaded.handler, request, {
|
|
21
|
-
...dispatchOptions,
|
|
22
|
-
prefix: options.prefix,
|
|
23
|
-
onError: options.onError
|
|
24
|
-
}),
|
|
25
|
-
dispose: async ()=>{
|
|
26
|
-
await loaded.dispose?.();
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
const createEffectBffTestHandler = createEffectBffEdgeHandler;
|
|
31
|
-
async function createEffectBffEdgeHandler(options) {
|
|
32
|
-
const dispatcher = await createEffectBffEdgeDispatcher(options);
|
|
33
|
-
return {
|
|
34
|
-
handler: dispatcher.dispatch,
|
|
35
|
-
dispose: dispatcher.dispose
|
|
36
|
-
};
|
|
37
|
-
}
|
|
2
|
+
export * from "./edge-dispatcher.mjs";
|
|
38
3
|
export { BACKEND_FEDERATION_CONTRACT_VERSION, BACKEND_FEDERATION_EFFECT_EXPOSE, BACKEND_FEDERATION_MANIFEST_FILE, BACKEND_FEDERATION_NODE_ADAPTER_VERSION, createBackendFederationLoadEntryPlugin, createBackendFederationRuntime, loadBackendFederatedEffectApi, validateExpectedBackendFederationIdentity } from "./backend-federation.mjs";
|
|
39
|
-
export { createEffectOperationContext } from "./operation-context.mjs";
|
|
40
|
-
export { createEffectBffEdgeDispatcher, createEffectBffEdgeHandler, createEffectBffTestHandler, dispatchEffectBffRequest, runWithEffectContext, useEffectContext, useOperationContext };
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import { createOperationContractHash } from "@modern-js/bff-core";
|
|
3
3
|
import { classifyEffectBffEntryModule, isValidatorAwareHandlerFactory } from "./entry-shape.mjs";
|
|
4
|
+
function resolveEffectEndpointName(endpoint) {
|
|
5
|
+
if ('string' == typeof endpoint.identifier && endpoint.identifier) return endpoint.identifier;
|
|
6
|
+
if ('string' == typeof endpoint.name && endpoint.name) return endpoint.name;
|
|
7
|
+
return '';
|
|
8
|
+
}
|
|
4
9
|
function ensureLeadingSlash(pathname) {
|
|
5
10
|
return pathname.startsWith('/') ? pathname : `/${pathname}`;
|
|
6
11
|
}
|
|
@@ -29,7 +34,7 @@ function collectEffectEndpoints(reflect, api, prefix) {
|
|
|
29
34
|
endpoints.push({
|
|
30
35
|
apiId,
|
|
31
36
|
groupName: String(group.identifier),
|
|
32
|
-
endpointName:
|
|
37
|
+
endpointName: resolveEffectEndpointName(endpoint),
|
|
33
38
|
method: String(endpoint.method).toUpperCase(),
|
|
34
39
|
routePath: getEffectRoutePath(prefix, String(endpoint.path))
|
|
35
40
|
});
|
|
@@ -17,6 +17,7 @@ function defineEffectBff(definition) {
|
|
|
17
17
|
openapi: options?.openapi,
|
|
18
18
|
rpc: mergedRpcOptions,
|
|
19
19
|
dataPlatform: mergeDataPlatformOptions(definition.dataPlatform, options?.dataPlatform),
|
|
20
|
+
interceptRequest: definition.interceptRequest,
|
|
20
21
|
validateRequest: options?.validateRequest
|
|
21
22
|
});
|
|
22
23
|
};
|
|
@@ -13,23 +13,30 @@ function createHttpApiHandler(options) {
|
|
|
13
13
|
const openApiLayer = createOpenApiLayer(options.api, options.openapi);
|
|
14
14
|
const mergedLayer = openApiLayer ? __rspack_external_effect_Layer_69c46453.mergeAll(apiLayer, openApiLayer) : apiLayer;
|
|
15
15
|
const httpApiHandler = HttpRouter.toWebHandler(mergedLayer);
|
|
16
|
-
const
|
|
17
|
-
const policyDenial = options.validateRequest?.(request);
|
|
18
|
-
if (policyDenial) return policyDenial;
|
|
16
|
+
const dispatchHttpApiRequest = async (request, context)=>{
|
|
19
17
|
const preparedRequest = await prepareJsonRequestBody(request);
|
|
20
18
|
if (preparedRequest instanceof Response) return preparedRequest;
|
|
21
19
|
const validationError = validateDataPlatformRequestEnvelope(preparedRequest, options.dataPlatform);
|
|
22
20
|
if (validationError) return validationError;
|
|
23
21
|
return httpApiHandler.handler(preparedRequest, toEffectServiceContext(context));
|
|
24
22
|
};
|
|
23
|
+
const withRequestPolicy = async (request, context)=>{
|
|
24
|
+
const policyDenial = options.validateRequest?.(request);
|
|
25
|
+
if (policyDenial) return policyDenial;
|
|
26
|
+
const next = ()=>dispatchHttpApiRequest(request, context);
|
|
27
|
+
return options.interceptRequest ? options.interceptRequest({
|
|
28
|
+
request,
|
|
29
|
+
next
|
|
30
|
+
}) : next();
|
|
31
|
+
};
|
|
25
32
|
const batchHandler = createDataPlatformBatchRequestHandler({
|
|
26
33
|
dataPlatform: options.dataPlatform,
|
|
27
|
-
handleItem:
|
|
34
|
+
handleItem: withRequestPolicy
|
|
28
35
|
});
|
|
29
36
|
const handleHttpApiRequest = async (request, context)=>{
|
|
30
37
|
const pathname = getRequestPathname(request);
|
|
31
38
|
if (batchHandler.enabled && pathname === batchHandler.path) return batchHandler.handle(request, context);
|
|
32
|
-
return
|
|
39
|
+
return withRequestPolicy(request, context);
|
|
33
40
|
};
|
|
34
41
|
if (!options.rpc) return {
|
|
35
42
|
handler: handleHttpApiRequest,
|
|
@@ -44,7 +51,11 @@ function createHttpApiHandler(options) {
|
|
|
44
51
|
if (isRpcRequest(request, rpcPath)) {
|
|
45
52
|
const policyDenial = options.validateRequest?.(request);
|
|
46
53
|
if (policyDenial) return policyDenial;
|
|
47
|
-
|
|
54
|
+
const next = ()=>rpcHandler.handler(request, toEffectServiceContext(context));
|
|
55
|
+
return options.interceptRequest ? options.interceptRequest({
|
|
56
|
+
request,
|
|
57
|
+
next
|
|
58
|
+
}) : next();
|
|
48
59
|
}
|
|
49
60
|
return handleHttpApiRequest(request, context);
|
|
50
61
|
},
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
-
import {
|
|
3
|
-
import { isProd, logger } from "@modern-js/utils";
|
|
2
|
+
import { logger } from "@modern-js/utils";
|
|
4
3
|
import createHonoRoutes from "../../utils/createHonoRoutes.mjs";
|
|
5
4
|
import { checkCrossProjectPolicyResponse, resolveAdapterCrossProjectPolicy } from "../../utils/crossProjectServerPolicy.mjs";
|
|
6
5
|
import { createSafeFailureResponse } from "../safe-failure.mjs";
|
|
@@ -9,17 +8,9 @@ const before = [
|
|
|
9
8
|
'custom-server-middleware',
|
|
10
9
|
'render'
|
|
11
10
|
];
|
|
12
|
-
const kParentHonoVars = Symbol.for('modernjs.hono.parentVars');
|
|
13
11
|
class HonoAdapter {
|
|
14
|
-
wrapInArray(handler) {
|
|
15
|
-
if (Array.isArray(handler)) return handler;
|
|
16
|
-
return [
|
|
17
|
-
handler
|
|
18
|
-
];
|
|
19
|
-
}
|
|
20
12
|
constructor(api){
|
|
21
13
|
this.apiMiddleware = [];
|
|
22
|
-
this.apiServer = null;
|
|
23
14
|
this.isHono = true;
|
|
24
15
|
this.prefix = '/api';
|
|
25
16
|
this.setHandlers = async ()=>{
|
|
@@ -30,7 +21,7 @@ class HonoAdapter {
|
|
|
30
21
|
name: 'hono-bff-api',
|
|
31
22
|
path,
|
|
32
23
|
method,
|
|
33
|
-
handler,
|
|
24
|
+
handler: this.withErrorBoundary(handler),
|
|
34
25
|
order: 'post',
|
|
35
26
|
before: before
|
|
36
27
|
}));
|
|
@@ -51,85 +42,47 @@ class HonoAdapter {
|
|
|
51
42
|
});
|
|
52
43
|
}
|
|
53
44
|
};
|
|
54
|
-
this.
|
|
55
|
-
if (!this.isHono) return;
|
|
56
|
-
this.apiServer = new Hono();
|
|
57
|
-
this.apiServer.use('*', run);
|
|
58
|
-
this.apiServer.use('*', async (c, next)=>{
|
|
59
|
-
const nodeReq = c.env?.node?.req;
|
|
60
|
-
const parentVars = nodeReq?.[kParentHonoVars];
|
|
61
|
-
if (parentVars && 'object' == typeof parentVars) {
|
|
62
|
-
delete nodeReq[kParentHonoVars];
|
|
63
|
-
for (const [key, value] of Object.entries(parentVars))if (void 0 === c.get(key)) c.set(key, value);
|
|
64
|
-
}
|
|
65
|
-
await next();
|
|
66
|
-
});
|
|
67
|
-
this.apiMiddleware.forEach(({ path = '*', method = 'all', handler })=>{
|
|
68
|
-
const handlers = this.wrapInArray(handler);
|
|
69
|
-
if (0 === handlers.length) return;
|
|
70
|
-
const firstHandler = handlers[0];
|
|
71
|
-
const restHandlers = handlers.slice(1);
|
|
72
|
-
const m = method;
|
|
73
|
-
const server = this.apiServer;
|
|
74
|
-
if (!server) return;
|
|
75
|
-
const register = server[m];
|
|
76
|
-
register.call(server, path, firstHandler, ...restHandlers);
|
|
77
|
-
});
|
|
78
|
-
this.apiServer.onError(async (err, c)=>{
|
|
79
|
-
try {
|
|
80
|
-
const serverConfig = this.api.getServerConfig();
|
|
81
|
-
const onErrorHandler = serverConfig?.onError;
|
|
82
|
-
if (onErrorHandler) {
|
|
83
|
-
const result = await onErrorHandler(err, c);
|
|
84
|
-
if (result instanceof Response) return result;
|
|
85
|
-
} else logger.error(err);
|
|
86
|
-
} catch (configError) {
|
|
87
|
-
logger.error(`Error in serverConfig.onError handler: ${configError}`);
|
|
88
|
-
}
|
|
89
|
-
return createSafeFailureResponse(err);
|
|
90
|
-
});
|
|
91
|
-
};
|
|
92
|
-
this.registerMiddleware = async (options)=>{
|
|
93
|
-
const { prefix } = options;
|
|
45
|
+
this.registerMiddleware = async (options = {})=>{
|
|
94
46
|
const { bffRuntimeFramework } = this.api.getServerContext();
|
|
95
47
|
if ('hono' !== bffRuntimeFramework) {
|
|
96
48
|
this.isHono = false;
|
|
97
49
|
return;
|
|
98
50
|
}
|
|
51
|
+
const { prefix } = options;
|
|
99
52
|
this.prefix = prefix || this.prefix;
|
|
100
53
|
const { middlewares: globalMiddlewares } = this.api.getServerContext();
|
|
101
54
|
await this.setHandlers();
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const nodeReq = c.env?.node?.req;
|
|
114
|
-
if (nodeReq) {
|
|
115
|
-
const parentVars = c?.var;
|
|
116
|
-
nodeReq[kParentHonoVars] = parentVars && 'object' == typeof parentVars ? {
|
|
117
|
-
...parentVars
|
|
118
|
-
} : parentVars;
|
|
119
|
-
}
|
|
120
|
-
const response = await this.apiServer.fetch(c.req.raw, c.env);
|
|
121
|
-
if (404 !== response.status) return new Response(response.body, response);
|
|
122
|
-
}
|
|
123
|
-
await next();
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
globalMiddlewares.push(dynamicApiMiddleware);
|
|
55
|
+
globalMiddlewares.push(...this.apiMiddleware);
|
|
56
|
+
};
|
|
57
|
+
this.handleRouteError = async (error, c)=>{
|
|
58
|
+
try {
|
|
59
|
+
const onErrorHandler = this.api.getServerConfig()?.onError;
|
|
60
|
+
if (onErrorHandler) {
|
|
61
|
+
const result = await onErrorHandler(error, c);
|
|
62
|
+
if (result instanceof Response) return result;
|
|
63
|
+
} else logger.error(error);
|
|
64
|
+
} catch (configError) {
|
|
65
|
+
logger.error(`Error in serverConfig.onError handler: ${configError}`);
|
|
127
66
|
}
|
|
67
|
+
return createSafeFailureResponse(error);
|
|
128
68
|
};
|
|
129
|
-
this.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
69
|
+
this.withErrorBoundary = (handler)=>{
|
|
70
|
+
const handlers = Array.isArray(handler) ? handler : [
|
|
71
|
+
handler
|
|
72
|
+
];
|
|
73
|
+
const [firstHandler, ...restHandlers] = handlers;
|
|
74
|
+
if (!firstHandler) return handler;
|
|
75
|
+
const boundary = async (c, next)=>{
|
|
76
|
+
try {
|
|
77
|
+
return await firstHandler(c, next);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return this.handleRouteError(error, c);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
return Array.isArray(handler) ? [
|
|
83
|
+
boundary,
|
|
84
|
+
...restHandlers
|
|
85
|
+
] : boundary;
|
|
133
86
|
};
|
|
134
87
|
this.api = api;
|
|
135
88
|
}
|
package/dist/esm-node/server.mjs
CHANGED
|
@@ -2,15 +2,17 @@ import "node:module";
|
|
|
2
2
|
import { ApiRouter } from "@modern-js/bff-core";
|
|
3
3
|
import { API_DIR, isFunction, isWebOnly } from "@modern-js/utils";
|
|
4
4
|
import path from "path";
|
|
5
|
-
import { EffectAdapter } from "./runtime/effect/adapter.mjs";
|
|
6
5
|
import { HonoAdapter } from "./runtime/hono/adapter.mjs";
|
|
7
|
-
const
|
|
8
|
-
hono: [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
effect:
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
const RUNTIME_ADAPTER_LOADERS = {
|
|
7
|
+
hono: async (api)=>[
|
|
8
|
+
new HonoAdapter(api)
|
|
9
|
+
],
|
|
10
|
+
effect: async (api)=>{
|
|
11
|
+
const { EffectAdapter } = await import("./runtime/effect/adapter.mjs");
|
|
12
|
+
return [
|
|
13
|
+
new EffectAdapter(api)
|
|
14
|
+
];
|
|
15
|
+
}
|
|
14
16
|
};
|
|
15
17
|
const normalizePrefixList = (prefix)=>{
|
|
16
18
|
if (Array.isArray(prefix)) return prefix.filter(Boolean);
|
|
@@ -23,9 +25,6 @@ function resolveRuntimeFramework(runtimeFramework) {
|
|
|
23
25
|
return 'hono' === runtimeFramework ? 'hono' : 'effect';
|
|
24
26
|
}
|
|
25
27
|
class Storage {
|
|
26
|
-
reset() {
|
|
27
|
-
this.middlewares = [];
|
|
28
|
-
}
|
|
29
28
|
constructor(){
|
|
30
29
|
this.middlewares = [];
|
|
31
30
|
}
|
|
@@ -37,7 +36,6 @@ const server = ()=>({
|
|
|
37
36
|
let apiRouter = null;
|
|
38
37
|
const appContext = api.getServerContext();
|
|
39
38
|
const runtimeFramework = resolveRuntimeFramework(appContext.bffRuntimeFramework);
|
|
40
|
-
const runtimeAdapters = RUNTIME_ADAPTER_FACTORIES[runtimeFramework].map((createAdapter)=>createAdapter(api));
|
|
41
39
|
api.onPrepare(async ()=>{
|
|
42
40
|
const appContext = api.getServerContext();
|
|
43
41
|
const { render } = appContext;
|
|
@@ -83,29 +81,20 @@ const server = ()=>({
|
|
|
83
81
|
]
|
|
84
82
|
});
|
|
85
83
|
}
|
|
84
|
+
const runtimeAdapters = await RUNTIME_ADAPTER_LOADERS[runtimeFramework](api);
|
|
86
85
|
await Promise.all(runtimeAdapters.map((adapter)=>adapter.registerMiddleware({
|
|
87
86
|
prefix,
|
|
88
87
|
enableHandleWeb
|
|
89
88
|
})));
|
|
90
89
|
});
|
|
91
90
|
api.onReset(async ({ event })=>{
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if ('file-change' === event.type) {
|
|
100
|
-
if ('hono' === runtimeFramework && apiRouter) {
|
|
101
|
-
const apiHandlerInfos = await apiRouter.getApiHandlers();
|
|
102
|
-
const appContext = api.getServerContext();
|
|
103
|
-
api.updateServerContext({
|
|
104
|
-
...appContext,
|
|
105
|
-
apiHandlerInfos
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
await Promise.all(runtimeAdapters.map((adapter)=>adapter.onApiHandlersUpdated?.()));
|
|
91
|
+
if ('file-change' === event.type && apiRouter) {
|
|
92
|
+
const appContext = api.getServerContext();
|
|
93
|
+
const apiHandlerInfos = await apiRouter.getApiHandlers();
|
|
94
|
+
api.updateServerContext({
|
|
95
|
+
...appContext,
|
|
96
|
+
apiHandlerInfos
|
|
97
|
+
});
|
|
109
98
|
}
|
|
110
99
|
});
|
|
111
100
|
const prepareApiServer = async (input, next)=>{
|
|
@@ -70,6 +70,6 @@ async function setPackage(files, appDirectory, relativeDistPath) {
|
|
|
70
70
|
}
|
|
71
71
|
});
|
|
72
72
|
mergePackageJson(packageJson, addFiles, typesVersions, exports, relativeDistPath);
|
|
73
|
-
await fs.promises.writeFile(packagePath, JSON.stringify(packageJson, null, 2));
|
|
73
|
+
await fs.promises.writeFile(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
74
74
|
}
|
|
75
75
|
export { setPackage, writeClientModuleBoundary };
|
|
@@ -6,6 +6,8 @@ const API_DIR = '{apiDirectory}';
|
|
|
6
6
|
const LAMBDA_DIR = '{lambdaDirectory}';
|
|
7
7
|
const DIST_DIR = '{distDirectory}';
|
|
8
8
|
const RUNTIME_FRAMEWORK = '{runtimeFramework}';
|
|
9
|
+
const EFFECT_ENTRY = '{effectEntry}';
|
|
10
|
+
const OPERATION_CONTRACTS_JSON = '{operationContracts}';
|
|
9
11
|
const NODE_MODULES = 'node_modules';
|
|
10
12
|
const crossProjectApiPlugin = ()=>({
|
|
11
13
|
name: '@modern-js/plugin-independent-bff',
|
|
@@ -17,7 +19,8 @@ const crossProjectApiPlugin = ()=>({
|
|
|
17
19
|
const { appDirectory: originAppDirectory } = api.getAppContext();
|
|
18
20
|
const sdkPath = path.join(originAppDirectory, NODE_MODULES, PACKAGE_NAME);
|
|
19
21
|
const sdkDistPath = path.join(sdkPath, DIST_DIR);
|
|
20
|
-
const
|
|
22
|
+
const effectEntry = EFFECT_ENTRY ? path.join(sdkDistPath, EFFECT_ENTRY) : void 0;
|
|
23
|
+
const apiDirectory = effectEntry ? path.dirname(effectEntry) : path.join(sdkDistPath, API_DIR);
|
|
21
24
|
const lambdaDirectory = path.resolve(sdkDistPath, LAMBDA_DIR);
|
|
22
25
|
api.updateAppContext({
|
|
23
26
|
apiDirectory,
|
|
@@ -32,10 +35,16 @@ const crossProjectApiPlugin = ()=>({
|
|
|
32
35
|
}
|
|
33
36
|
const configuredRuntimeFramework = config?.bff?.runtimeFramework;
|
|
34
37
|
if (configuredRuntimeFramework && configuredRuntimeFramework !== RUNTIME_FRAMEWORK) throw new Error(`[${PACKAGE_NAME}] Runtime framework mismatch for cross-project BFF. Detected "${configuredRuntimeFramework}", but producer SDK requires "${RUNTIME_FRAMEWORK}".`);
|
|
38
|
+
resolvedConfig.bff ??= {};
|
|
35
39
|
resolvedConfig.bff.prefix = PREFIX;
|
|
36
40
|
resolvedConfig.bff.runtimeFramework = RUNTIME_FRAMEWORK;
|
|
41
|
+
if (EFFECT_ENTRY) resolvedConfig.bff.effect = {
|
|
42
|
+
...resolvedConfig.bff.effect,
|
|
43
|
+
entry: effectEntry
|
|
44
|
+
};
|
|
37
45
|
resolvedConfig.bff.isCrossProjectServer = true;
|
|
38
46
|
resolvedConfig.bff.requestId = resolvedConfig.bff.requestId || config?.bff?.requestId || PACKAGE_NAME || 'default';
|
|
47
|
+
const generatedOperationContracts = "{operationContracts}" === OPERATION_CONTRACTS_JSON ? {} : JSON.parse(OPERATION_CONTRACTS_JSON);
|
|
39
48
|
resolvedConfig.bff.crossProjectPolicy = {
|
|
40
49
|
...resolvedConfig.bff.crossProjectPolicy || {},
|
|
41
50
|
enabled: resolvedConfig.bff.crossProjectPolicy?.enabled ?? true,
|
|
@@ -44,7 +53,11 @@ const crossProjectApiPlugin = ()=>({
|
|
|
44
53
|
requireOperationContextDetails: resolvedConfig.bff.crossProjectPolicy?.requireOperationContextDetails ?? true,
|
|
45
54
|
requireOperationSchemaHash: resolvedConfig.bff.crossProjectPolicy?.requireOperationSchemaHash ?? true,
|
|
46
55
|
requireOperationVersion: resolvedConfig.bff.crossProjectPolicy?.requireOperationVersion ?? true,
|
|
47
|
-
allowUnknownOperations: resolvedConfig.bff.crossProjectPolicy?.allowUnknownOperations ?? false
|
|
56
|
+
allowUnknownOperations: resolvedConfig.bff.crossProjectPolicy?.allowUnknownOperations ?? false,
|
|
57
|
+
expectedOperationContracts: {
|
|
58
|
+
...resolvedConfig.bff.crossProjectPolicy?.expectedOperationContracts || {},
|
|
59
|
+
...generatedOperationContracts
|
|
60
|
+
}
|
|
48
61
|
};
|
|
49
62
|
return resolvedConfig;
|
|
50
63
|
});
|
|
@@ -52,4 +65,4 @@ const crossProjectApiPlugin = ()=>({
|
|
|
52
65
|
});
|
|
53
66
|
const utils_crossProjectApiPlugin = crossProjectApiPlugin;
|
|
54
67
|
export default utils_crossProjectApiPlugin;
|
|
55
|
-
export { API_DIR, DIST_DIR, LAMBDA_DIR, PACKAGE_NAME, PREFIX, RUNTIME_FRAMEWORK, crossProjectApiPlugin };
|
|
68
|
+
export { API_DIR, DIST_DIR, EFFECT_ENTRY, LAMBDA_DIR, OPERATION_CONTRACTS_JSON, PACKAGE_NAME, PREFIX, RUNTIME_FRAMEWORK, crossProjectApiPlugin };
|
|
@@ -3,26 +3,27 @@ import { logger } from "@modern-js/utils";
|
|
|
3
3
|
import { collectEffectEndpoints } from "../../runtime/effect/endpoint-contracts.mjs";
|
|
4
4
|
import { renderEffectClientCode, renderEffectClientDeclaration } from "./rendering.mjs";
|
|
5
5
|
import { getHttpApiRuntime, loadEffectApi } from "./runtime.mjs";
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
function generateEffectClient(options) {
|
|
7
|
+
return loadEffectApi({
|
|
8
8
|
appDir: options.appDir,
|
|
9
9
|
resourcePath: options.resourcePath,
|
|
10
10
|
onDependency: options.onDependency
|
|
11
|
+
}).then((api)=>{
|
|
12
|
+
if (null === api) {
|
|
13
|
+
logger.warn(`[BFF][Effect] Failed to generate client for ${options.resourcePath}: unable to resolve exported HttpApi.`);
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return getHttpApiRuntime().then((httpApiRuntime)=>{
|
|
17
|
+
const endpoints = collectEffectEndpoints(httpApiRuntime.reflect, api, options.prefix);
|
|
18
|
+
return {
|
|
19
|
+
code: renderEffectClientCode(endpoints, options),
|
|
20
|
+
declaration: renderEffectClientDeclaration(endpoints),
|
|
21
|
+
endpoints
|
|
22
|
+
};
|
|
23
|
+
});
|
|
11
24
|
});
|
|
12
|
-
if (!api) {
|
|
13
|
-
logger.warn(`[BFF][Effect] Failed to generate client for ${options.resourcePath}: unable to resolve exported HttpApi.`);
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
const httpApiRuntime = await getHttpApiRuntime();
|
|
17
|
-
const endpoints = collectEffectEndpoints(httpApiRuntime.reflect, api, options.prefix);
|
|
18
|
-
return {
|
|
19
|
-
code: renderEffectClientCode(endpoints, options),
|
|
20
|
-
declaration: renderEffectClientDeclaration(endpoints),
|
|
21
|
-
endpoints
|
|
22
|
-
};
|
|
23
25
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return artifacts ? artifacts.code : null;
|
|
26
|
+
function generateEffectClientCode(options) {
|
|
27
|
+
return generateEffectClient(options).then((artifacts)=>null === artifacts ? null : artifacts.code);
|
|
27
28
|
}
|
|
28
29
|
export { generateEffectClient, generateEffectClientCode };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
-
import { findExists, fs } from "@modern-js/utils";
|
|
3
|
-
import path from "path";
|
|
2
|
+
import { findExists, fs, upath } from "@modern-js/utils";
|
|
4
3
|
const JS_OR_TS_EXTS = [
|
|
5
4
|
'.js',
|
|
6
5
|
'.jsx',
|
|
@@ -14,13 +13,13 @@ const JS_OR_TS_EXTS = [
|
|
|
14
13
|
function resolveEffectEntryFile(options) {
|
|
15
14
|
const { appDir, apiDir, effectEntry } = options;
|
|
16
15
|
const resolveEntry = (entryWithoutExt)=>{
|
|
17
|
-
if (
|
|
16
|
+
if ('' !== upath.extname(entryWithoutExt)) return fs.existsSync(entryWithoutExt) ? entryWithoutExt : void 0;
|
|
18
17
|
return findExists(JS_OR_TS_EXTS.map((ext)=>`${entryWithoutExt}${ext}`));
|
|
19
18
|
};
|
|
20
|
-
if (effectEntry) {
|
|
21
|
-
const entryWithoutExt =
|
|
19
|
+
if (void 0 !== effectEntry && '' !== effectEntry) {
|
|
20
|
+
const entryWithoutExt = upath.isAbsolute(effectEntry) ? effectEntry : upath.resolve(appDir, effectEntry);
|
|
22
21
|
return resolveEntry(entryWithoutExt);
|
|
23
22
|
}
|
|
24
|
-
return resolveEntry(
|
|
23
|
+
return resolveEntry(upath.resolve(apiDir, 'index'));
|
|
25
24
|
}
|
|
26
25
|
export { resolveEffectEntryFile };
|