@bleedingdev/modern-js-plugin-bff-extensions 3.9.0-ultramodern.2 → 3.9.0-ultramodern.21
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/README.md +25 -5
- package/dist/cjs/backend-federation/edge-runtime.js +3 -15
- package/dist/cjs/backend-federation/edge.js +9 -10
- package/dist/cjs/backend-federation/identity.js +26 -31
- package/dist/cjs/backend-federation/index.js +5 -5
- package/dist/cjs/backend-federation/integrity-plugin.js +112 -0
- package/dist/cjs/backend-federation/load.js +3 -13
- package/dist/cjs/backend-federation/node-shared.js +85 -0
- package/dist/cjs/backend-federation/node.js +6 -1
- package/dist/cjs/backend-federation/plugin.js +40 -21
- package/dist/cjs/backend-federation/runtime.js +16 -84
- package/dist/cjs/backend-federation/validate-loaded.js +2 -4
- package/dist/cjs/backend-federation-manifest/load.js +40 -12
- package/dist/cjs/backend-federation-manifest/metadata.js +3 -3
- package/dist/cjs/backend-federation-manifest/node.js +3 -1
- package/dist/cjs/backend-federation-manifest/remote.js +3 -9
- package/dist/cjs/backend-federation-manifest/validation.js +16 -12
- package/dist/cjs/cross-project-policy/evaluation.js +3 -3
- package/dist/cjs/cross-project-policy/node.js +11 -30
- package/dist/cjs/{backend-federation/legacy-warning.js → cross-project-policy/producer-runtime.js} +23 -5
- package/dist/cjs/{effect-adapter → effect-server}/cross-project-policy.js +5 -5
- package/dist/cjs/effect-server/entry.js +63 -0
- package/dist/cjs/{effect-adapter → effect-server}/error-response.js +4 -4
- package/dist/cjs/effect-server/index.js +199 -0
- package/dist/cjs/{client-generator → effect-source-loader}/built-entry.js +5 -6
- package/dist/cjs/{client-generator/runtime.js → effect-source-loader/http-api-runtime.js} +12 -20
- package/dist/cjs/effect-source-loader/index.js +18 -6
- package/dist/cjs/effect-source-loader/loader.js +17 -30
- package/dist/cjs/effect-source-loader/operation-contracts.js +60 -0
- package/dist/cjs/effect-source-loader/paths.js +90 -0
- package/dist/cjs/effect-source-loader/rspack-loader.js +73 -0
- package/dist/cjs/effect-source-loader/worker-runtime-source.js +71 -0
- package/dist/cjs/effect-source-loader/worker-runtime-wrapper.js +7 -50
- package/dist/cjs/hono/cross-project-policy.js +2 -1
- package/dist/cjs/{client-generator/paths.js → hono/node.js} +14 -25
- package/dist/esm/backend-federation/edge-runtime.mjs +2 -11
- package/dist/esm/backend-federation/edge.mjs +5 -6
- package/dist/esm/backend-federation/identity.mjs +21 -32
- package/dist/esm/backend-federation/index.mjs +1 -1
- package/dist/esm/backend-federation/integrity-plugin.mjs +68 -0
- package/dist/esm/backend-federation/load.mjs +2 -12
- package/dist/esm/backend-federation/node-shared.mjs +41 -0
- package/dist/esm/backend-federation/node.mjs +4 -2
- package/dist/esm/backend-federation/plugin.mjs +37 -18
- package/dist/esm/backend-federation/runtime.mjs +16 -84
- package/dist/esm/backend-federation/validate-loaded.mjs +2 -4
- package/dist/esm/backend-federation-manifest/load.mjs +40 -12
- package/dist/esm/backend-federation-manifest/metadata.mjs +1 -1
- package/dist/esm/backend-federation-manifest/node.mjs +3 -1
- package/dist/esm/backend-federation-manifest/remote.mjs +2 -8
- package/dist/esm/backend-federation-manifest/validation.mjs +15 -11
- package/dist/esm/cross-project-policy/evaluation.mjs +1 -1
- package/dist/esm/cross-project-policy/node.mjs +10 -19
- package/dist/esm/cross-project-policy/producer-runtime.mjs +20 -0
- package/dist/esm/{effect-adapter → effect-server}/cross-project-policy.mjs +3 -3
- package/dist/esm/effect-server/entry.mjs +15 -0
- package/dist/esm/{effect-adapter → effect-server}/error-response.mjs +2 -2
- package/dist/esm/effect-server/index.mjs +151 -0
- package/dist/esm/{client-generator → effect-source-loader}/built-entry.mjs +6 -7
- package/dist/esm/effect-source-loader/http-api-runtime.mjs +19 -0
- package/dist/esm/effect-source-loader/index.mjs +5 -2
- package/dist/esm/effect-source-loader/loader.mjs +17 -30
- package/dist/esm/effect-source-loader/operation-contracts.mjs +22 -0
- package/dist/esm/effect-source-loader/paths.mjs +43 -0
- package/dist/esm/effect-source-loader/rspack-loader.mjs +35 -0
- package/dist/esm/effect-source-loader/worker-runtime-source.mjs +23 -0
- package/dist/esm/effect-source-loader/worker-runtime-wrapper.mjs +5 -45
- package/dist/esm/hono/cross-project-policy.mjs +2 -1
- package/dist/esm-node/backend-federation/edge-runtime.mjs +2 -11
- package/dist/esm-node/backend-federation/edge.mjs +5 -6
- package/dist/esm-node/backend-federation/identity.mjs +21 -32
- package/dist/esm-node/backend-federation/index.mjs +1 -1
- package/dist/esm-node/backend-federation/integrity-plugin.mjs +69 -0
- package/dist/esm-node/backend-federation/load.mjs +2 -12
- package/dist/esm-node/backend-federation/node-shared.mjs +42 -0
- package/dist/esm-node/backend-federation/node.mjs +4 -2
- package/dist/esm-node/backend-federation/plugin.mjs +37 -18
- package/dist/esm-node/backend-federation/runtime.mjs +16 -84
- package/dist/esm-node/backend-federation/validate-loaded.mjs +2 -4
- package/dist/esm-node/backend-federation-manifest/load.mjs +40 -12
- package/dist/esm-node/backend-federation-manifest/metadata.mjs +1 -1
- package/dist/esm-node/backend-federation-manifest/node.mjs +3 -1
- package/dist/esm-node/backend-federation-manifest/remote.mjs +2 -8
- package/dist/esm-node/backend-federation-manifest/validation.mjs +15 -11
- package/dist/esm-node/cross-project-policy/evaluation.mjs +1 -1
- package/dist/esm-node/cross-project-policy/node.mjs +10 -19
- package/dist/esm-node/cross-project-policy/producer-runtime.mjs +21 -0
- package/dist/esm-node/{effect-adapter → effect-server}/cross-project-policy.mjs +3 -3
- package/dist/esm-node/effect-server/entry.mjs +16 -0
- package/dist/esm-node/{effect-adapter → effect-server}/error-response.mjs +2 -2
- package/dist/esm-node/effect-server/index.mjs +152 -0
- package/dist/esm-node/{client-generator → effect-source-loader}/built-entry.mjs +6 -7
- package/dist/esm-node/effect-source-loader/http-api-runtime.mjs +20 -0
- package/dist/esm-node/effect-source-loader/index.mjs +5 -2
- package/dist/esm-node/effect-source-loader/loader.mjs +17 -30
- package/dist/esm-node/effect-source-loader/operation-contracts.mjs +23 -0
- package/dist/esm-node/effect-source-loader/paths.mjs +44 -0
- package/dist/esm-node/effect-source-loader/rspack-loader.mjs +37 -0
- package/dist/esm-node/effect-source-loader/worker-runtime-source.mjs +24 -0
- package/dist/esm-node/effect-source-loader/worker-runtime-wrapper.mjs +5 -45
- package/dist/esm-node/hono/cross-project-policy.mjs +2 -1
- package/dist/esm-node/hono/node.mjs +14 -0
- package/dist/types/backend-federation/edge-runtime.d.ts +0 -5
- package/dist/types/backend-federation/edge.d.ts +5 -5
- package/dist/types/backend-federation/identity.d.ts +4 -16
- package/dist/types/backend-federation/index.d.ts +1 -1
- package/dist/types/backend-federation/integrity-plugin.d.ts +13 -0
- package/dist/types/backend-federation/load.d.ts +1 -7
- package/dist/types/backend-federation/node-shared.d.ts +20 -0
- package/dist/types/backend-federation/node.d.ts +4 -3
- package/dist/types/backend-federation/plugin.d.ts +4 -0
- package/dist/types/backend-federation/types.d.ts +3 -14
- package/dist/types/backend-federation/validate-loaded.d.ts +1 -2
- package/dist/types/backend-federation-manifest/metadata.d.ts +0 -6
- package/dist/types/backend-federation-manifest/types.d.ts +2 -1
- package/dist/types/cross-project-policy/evaluation.d.ts +1 -1
- package/dist/types/cross-project-policy/node.d.ts +3 -2
- package/dist/types/cross-project-policy/producer-runtime.d.ts +20 -0
- package/dist/types/effect-server/cross-project-policy.d.ts +5 -0
- package/dist/types/effect-server/entry.d.ts +3 -0
- package/dist/types/effect-server/error-response.d.ts +2 -0
- package/dist/types/effect-server/index.d.ts +4 -0
- package/dist/types/effect-source-loader/index.d.ts +5 -2
- package/dist/types/effect-source-loader/operation-contracts.d.ts +8 -0
- package/dist/types/effect-source-loader/paths.d.ts +23 -0
- package/dist/types/effect-source-loader/rspack-loader.d.ts +9 -0
- package/dist/types/effect-source-loader/worker-runtime-source.d.ts +4 -0
- package/dist/types/effect-source-loader/worker-runtime-wrapper.d.ts +4 -22
- package/dist/types/hono/node.d.ts +4 -0
- package/package.json +64 -36
- package/src/backend-federation/edge-runtime.ts +2 -13
- package/src/backend-federation/edge.ts +16 -21
- package/src/backend-federation/identity.ts +39 -65
- package/src/backend-federation/index.ts +1 -1
- package/src/backend-federation/integrity-plugin.ts +157 -0
- package/src/backend-federation/load.ts +7 -35
- package/src/backend-federation/node-shared.ts +76 -0
- package/src/backend-federation/node.ts +7 -14
- package/src/backend-federation/plugin.ts +73 -25
- package/src/backend-federation/runtime.ts +16 -90
- package/src/backend-federation/types.ts +3 -18
- package/src/backend-federation/validate-loaded.ts +2 -7
- package/src/backend-federation-manifest/load.ts +93 -38
- package/src/backend-federation-manifest/metadata.ts +1 -7
- package/src/backend-federation-manifest/node.ts +3 -2
- package/src/backend-federation-manifest/remote.ts +1 -7
- package/src/backend-federation-manifest/types.ts +2 -0
- package/src/backend-federation-manifest/validation.ts +29 -39
- package/src/cross-project-policy/evaluation.ts +1 -1
- package/src/cross-project-policy/node.ts +10 -41
- package/src/cross-project-policy/producer-runtime.ts +43 -0
- package/src/{effect-adapter → effect-server}/cross-project-policy.ts +8 -2
- package/src/effect-server/entry.ts +18 -0
- package/src/{effect-adapter → effect-server}/error-response.ts +1 -1
- package/src/effect-server/index.ts +242 -0
- package/src/{client-generator → effect-source-loader}/built-entry.ts +12 -12
- package/src/effect-source-loader/http-api-runtime.ts +39 -0
- package/src/effect-source-loader/index.ts +8 -5
- package/src/effect-source-loader/loader.ts +47 -52
- package/src/effect-source-loader/operation-contracts.ts +38 -0
- package/src/effect-source-loader/paths.ts +74 -0
- package/src/effect-source-loader/rspack-loader.ts +71 -0
- package/src/effect-source-loader/worker-runtime-source.ts +34 -0
- package/src/effect-source-loader/worker-runtime-wrapper.ts +5 -75
- package/src/hono/cross-project-policy.ts +2 -1
- package/src/hono/node.ts +24 -0
- package/dist/cjs/backend-federation/entry.js +0 -94
- package/dist/cjs/client-generator/generator.js +0 -73
- package/dist/cjs/client-generator/index.js +0 -54
- package/dist/cjs/client-generator/rendering.js +0 -253
- package/dist/cjs/client-generator/types.js +0 -18
- package/dist/cjs/effect-adapter/entry.js +0 -98
- package/dist/cjs/effect-adapter/index.js +0 -187
- package/dist/esm/backend-federation/entry.mjs +0 -56
- package/dist/esm/backend-federation/legacy-warning.mjs +0 -2
- package/dist/esm/client-generator/generator.mjs +0 -32
- package/dist/esm/client-generator/index.mjs +0 -3
- package/dist/esm/client-generator/paths.mjs +0 -24
- package/dist/esm/client-generator/rendering.mjs +0 -209
- package/dist/esm/client-generator/runtime.mjs +0 -27
- package/dist/esm/client-generator/types.mjs +0 -0
- package/dist/esm/effect-adapter/entry.mjs +0 -50
- package/dist/esm/effect-adapter/index.mjs +0 -139
- package/dist/esm-node/backend-federation/entry.mjs +0 -57
- package/dist/esm-node/backend-federation/legacy-warning.mjs +0 -3
- package/dist/esm-node/client-generator/generator.mjs +0 -33
- package/dist/esm-node/client-generator/index.mjs +0 -4
- package/dist/esm-node/client-generator/paths.mjs +0 -25
- package/dist/esm-node/client-generator/rendering.mjs +0 -210
- package/dist/esm-node/client-generator/runtime.mjs +0 -29
- package/dist/esm-node/client-generator/types.mjs +0 -1
- package/dist/esm-node/effect-adapter/entry.mjs +0 -51
- package/dist/esm-node/effect-adapter/index.mjs +0 -140
- package/dist/types/backend-federation/entry.d.ts +0 -2
- package/dist/types/backend-federation/legacy-warning.d.ts +0 -1
- package/dist/types/client-generator/generator.d.ts +0 -3
- package/dist/types/client-generator/index.d.ts +0 -4
- package/dist/types/client-generator/paths.d.ts +0 -5
- package/dist/types/client-generator/rendering.d.ts +0 -24
- package/dist/types/client-generator/types.d.ts +0 -33
- package/dist/types/effect-adapter/cross-project-policy.d.ts +0 -4
- package/dist/types/effect-adapter/entry.d.ts +0 -2
- package/dist/types/effect-adapter/error-response.d.ts +0 -2
- package/dist/types/effect-adapter/index.d.ts +0 -19
- package/src/backend-federation/entry.ts +0 -125
- package/src/backend-federation/legacy-warning.ts +0 -2
- package/src/client-generator/generator.ts +0 -52
- package/src/client-generator/index.ts +0 -10
- package/src/client-generator/paths.ts +0 -40
- package/src/client-generator/rendering.ts +0 -307
- package/src/client-generator/runtime.ts +0 -69
- package/src/client-generator/types.ts +0 -35
- package/src/effect-adapter/entry.ts +0 -95
- package/src/effect-adapter/index.ts +0 -246
- /package/dist/types/{client-generator → effect-source-loader}/built-entry.d.ts +0 -0
- /package/dist/types/{client-generator/runtime.d.ts → effect-source-loader/http-api-runtime.d.ts} +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
interface ProducerClientOptions {
|
|
2
|
+
requestId?: string;
|
|
3
|
+
requireEnvelope?: boolean;
|
|
4
|
+
identityBinding?: {
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
strict?: boolean;
|
|
7
|
+
protectedHeaders?: string[];
|
|
8
|
+
};
|
|
9
|
+
operationContract?: {
|
|
10
|
+
enabled?: boolean;
|
|
11
|
+
strict?: boolean;
|
|
12
|
+
requireSchemaHash?: boolean;
|
|
13
|
+
requireOperationVersion?: boolean;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Bind producer defaults once while preserving every per-call nested override. */
|
|
18
|
+
export function createProducerClient<
|
|
19
|
+
Options extends ProducerClientOptions,
|
|
20
|
+
Result,
|
|
21
|
+
>(
|
|
22
|
+
configure: (options: Options) => Result,
|
|
23
|
+
{ requestId }: { requestId: string },
|
|
24
|
+
) {
|
|
25
|
+
return (options?: Options): Result =>
|
|
26
|
+
configure({
|
|
27
|
+
requestId,
|
|
28
|
+
requireEnvelope: true,
|
|
29
|
+
...options,
|
|
30
|
+
identityBinding: {
|
|
31
|
+
enabled: true,
|
|
32
|
+
strict: true,
|
|
33
|
+
...options?.identityBinding,
|
|
34
|
+
},
|
|
35
|
+
operationContract: {
|
|
36
|
+
enabled: true,
|
|
37
|
+
strict: true,
|
|
38
|
+
requireSchemaHash: true,
|
|
39
|
+
requireOperationVersion: true,
|
|
40
|
+
...options?.operationContract,
|
|
41
|
+
},
|
|
42
|
+
} as Options);
|
|
43
|
+
}
|
|
@@ -6,16 +6,18 @@ import {
|
|
|
6
6
|
toOperationContractSources,
|
|
7
7
|
} from '@modern-js/bff-effect/effect';
|
|
8
8
|
import type { ServerPluginAPI } from '@modern-js/server-core';
|
|
9
|
+
import type { resolveOperationProducer } from '@modern-js/server-runtime-extensions/bff-policy/node';
|
|
9
10
|
import { logger } from '@modern-js/utils';
|
|
10
11
|
import { HttpApi } from 'effect/unstable/httpapi';
|
|
11
12
|
|
|
12
13
|
import { resolveAdapterCrossProjectPolicy } from '../cross-project-policy';
|
|
13
14
|
import type { ResolvedCrossProjectPolicy } from '../cross-project-policy/evaluation';
|
|
14
15
|
|
|
15
|
-
export function
|
|
16
|
+
export function resolveEffectServerCrossProjectPolicy(
|
|
16
17
|
api: ServerPluginAPI,
|
|
17
18
|
prefix: string,
|
|
18
19
|
mod: EffectApiModule | null,
|
|
20
|
+
producer?: ReturnType<typeof resolveOperationProducer>,
|
|
19
21
|
): Promise<ResolvedCrossProjectPolicy | undefined> {
|
|
20
22
|
const contractSourcesPromise =
|
|
21
23
|
mod === null
|
|
@@ -52,7 +54,11 @@ export function resolveEffectAdapterCrossProjectPolicy(
|
|
|
52
54
|
});
|
|
53
55
|
|
|
54
56
|
return contractSourcesPromise.then(contractSources => {
|
|
55
|
-
const policy = resolveAdapterCrossProjectPolicy(
|
|
57
|
+
const policy = resolveAdapterCrossProjectPolicy(
|
|
58
|
+
api,
|
|
59
|
+
contractSources,
|
|
60
|
+
producer,
|
|
61
|
+
);
|
|
56
62
|
if (policy?.enabled === true && contractSources.length === 0) {
|
|
57
63
|
logger.warn(
|
|
58
64
|
'[BFF][Effect] Cross-project policy enabled but no HttpApi endpoints could be reflected; requests fail operation-contract matching unless allowUnknownOperations is enabled.',
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// @effect-diagnostics nodeBuiltinImport:off strictBooleanExpressions:off
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import '@modern-js/server-runtime-extensions/server-config';
|
|
4
|
+
import type { ServerPluginAPI } from '@modern-js/server-core';
|
|
5
|
+
import { API_DIR, isProd } from '@modern-js/utils';
|
|
6
|
+
import { resolveEffectEntry } from '../effect-source-loader/paths';
|
|
7
|
+
|
|
8
|
+
export function resolveEffectServerEntryFile(api: ServerPluginAPI) {
|
|
9
|
+
const { appDirectory, apiDirectory, distDirectory } = api.getServerContext();
|
|
10
|
+
const appDir = path.resolve(appDirectory || process.cwd());
|
|
11
|
+
return resolveEffectEntry({
|
|
12
|
+
appDir,
|
|
13
|
+
apiDir: path.resolve(appDir, apiDirectory || API_DIR),
|
|
14
|
+
effectEntry: api.getServerConfig()?.bff?.effect?.entry,
|
|
15
|
+
distDir:
|
|
16
|
+
isProd() && distDirectory ? path.resolve(distDirectory) : undefined,
|
|
17
|
+
}).path;
|
|
18
|
+
}
|
|
@@ -2,7 +2,7 @@ import { createSafeFailureResponse } from '@modern-js/runtime-extensions/safe-fa
|
|
|
2
2
|
import type { Context, ServerPluginAPI } from '@modern-js/server-core';
|
|
3
3
|
import { logger } from '@modern-js/utils';
|
|
4
4
|
|
|
5
|
-
export function
|
|
5
|
+
export function createEffectServerRuntimeErrorResponse(
|
|
6
6
|
api: ServerPluginAPI,
|
|
7
7
|
error: unknown,
|
|
8
8
|
context: Context,
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// @effect-diagnostics anyUnknownInErrorContext:off asyncFunction:off nodeBuiltinImport:off strictBooleanExpressions:off
|
|
2
|
+
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import '@modern-js/server-runtime-extensions/server-config';
|
|
5
|
+
import {
|
|
6
|
+
dispatchEffectBffRequest,
|
|
7
|
+
type EffectApiModule,
|
|
8
|
+
type EffectBffRequestHandler,
|
|
9
|
+
resolveEffectBffModuleHandler,
|
|
10
|
+
useEffectContext,
|
|
11
|
+
} from '@modern-js/bff-effect/effect';
|
|
12
|
+
import type {
|
|
13
|
+
Context,
|
|
14
|
+
Next,
|
|
15
|
+
ServerPlugin,
|
|
16
|
+
ServerPluginAPI,
|
|
17
|
+
} from '@modern-js/server-core';
|
|
18
|
+
import { resolveOperationProducer } from '@modern-js/server-runtime-extensions/bff-policy/node';
|
|
19
|
+
import {
|
|
20
|
+
createDisposableServerRuntimeHandle,
|
|
21
|
+
type DisposableServerRuntimeHandle,
|
|
22
|
+
registerServerRuntimeDisposer,
|
|
23
|
+
} from '@modern-js/server-runtime-extensions/runtime-lifecycle';
|
|
24
|
+
import { fs, isProd, logger } from '@modern-js/utils';
|
|
25
|
+
|
|
26
|
+
import { checkCrossProjectPolicyForRequest } from '../cross-project-policy/evaluation';
|
|
27
|
+
import {
|
|
28
|
+
loadEffectBuiltModule,
|
|
29
|
+
loadEffectSourceModule,
|
|
30
|
+
} from '../effect-source-loader/loader';
|
|
31
|
+
import { resolveEffectServerCrossProjectPolicy } from './cross-project-policy';
|
|
32
|
+
import { resolveEffectServerEntryFile } from './entry';
|
|
33
|
+
import { createEffectServerRuntimeErrorResponse } from './error-response';
|
|
34
|
+
|
|
35
|
+
const EFFECT_MIDDLEWARE_BEFORE = [
|
|
36
|
+
'custom-server-hook',
|
|
37
|
+
'custom-server-middleware',
|
|
38
|
+
'render',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
export default (): ServerPlugin => ({
|
|
42
|
+
name: '@modern-js/plugin-bff-extensions/effect-server',
|
|
43
|
+
setup(api: ServerPluginAPI) {
|
|
44
|
+
const runtimeFramework = api.getServerContext().bffRuntimeFramework;
|
|
45
|
+
if (runtimeFramework !== 'effect') {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Effect BFF server plugin requires Effect; received "${runtimeFramework}".`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
let handler: DisposableServerRuntimeHandle | null = null;
|
|
51
|
+
let unregisterRuntimeDisposer: (() => void) | undefined;
|
|
52
|
+
let prefix = '/api';
|
|
53
|
+
let prefixes = ['/api'];
|
|
54
|
+
let matchingPrefixes = ['/api'];
|
|
55
|
+
let retired = false;
|
|
56
|
+
|
|
57
|
+
const dispose = async () => {
|
|
58
|
+
retired = true;
|
|
59
|
+
unregisterRuntimeDisposer?.();
|
|
60
|
+
unregisterRuntimeDisposer = undefined;
|
|
61
|
+
const previous = handler;
|
|
62
|
+
handler = null;
|
|
63
|
+
await previous?.dispose();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const loadHandler = async () => {
|
|
67
|
+
const entryFile = resolveEffectServerEntryFile(api);
|
|
68
|
+
if (!entryFile || !(await fs.pathExists(entryFile))) {
|
|
69
|
+
handler = null;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const { appDirectory } = api.getServerContext();
|
|
74
|
+
let mod: EffectApiModule;
|
|
75
|
+
try {
|
|
76
|
+
mod = (await (isProd()
|
|
77
|
+
? loadEffectBuiltModule(entryFile)
|
|
78
|
+
: loadEffectSourceModule({
|
|
79
|
+
resourcePath: entryFile,
|
|
80
|
+
appDir: appDirectory || path.dirname(entryFile),
|
|
81
|
+
}))) as EffectApiModule;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
logger.error(
|
|
84
|
+
`[BFF][Effect] Failed to load Effect entry: ${entryFile}\n${String(error)}`,
|
|
85
|
+
);
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const producer = resolveOperationProducer({
|
|
90
|
+
directories: [path.dirname(entryFile), appDirectory],
|
|
91
|
+
requestId: api.getServerConfig()?.bff?.requestId,
|
|
92
|
+
});
|
|
93
|
+
const crossProjectPolicies = new Map(
|
|
94
|
+
await Promise.all(
|
|
95
|
+
prefixes.map(
|
|
96
|
+
async prefix =>
|
|
97
|
+
[
|
|
98
|
+
prefix,
|
|
99
|
+
await resolveEffectServerCrossProjectPolicy(
|
|
100
|
+
api,
|
|
101
|
+
prefix,
|
|
102
|
+
mod,
|
|
103
|
+
producer,
|
|
104
|
+
),
|
|
105
|
+
] as const,
|
|
106
|
+
),
|
|
107
|
+
),
|
|
108
|
+
);
|
|
109
|
+
const effectConfig = api.getServerConfig()?.bff?.effect;
|
|
110
|
+
const loaded = await resolveEffectBffModuleHandler(mod, {
|
|
111
|
+
openapi: effectConfig?.openapi,
|
|
112
|
+
dataPlatform: effectConfig?.dataPlatform,
|
|
113
|
+
validateRequest: request => {
|
|
114
|
+
const { path: mountedPathname } = useEffectContext();
|
|
115
|
+
const prefix = matchingPrefixes.find(
|
|
116
|
+
item =>
|
|
117
|
+
item === '/' ||
|
|
118
|
+
mountedPathname === item ||
|
|
119
|
+
mountedPathname.startsWith(`${item}/`),
|
|
120
|
+
);
|
|
121
|
+
if (!prefix) return null;
|
|
122
|
+
const requestUrl = new URL(request.url);
|
|
123
|
+
if (prefix !== '/') {
|
|
124
|
+
requestUrl.pathname =
|
|
125
|
+
requestUrl.pathname === '/'
|
|
126
|
+
? prefix
|
|
127
|
+
: `${prefix}${requestUrl.pathname}`;
|
|
128
|
+
}
|
|
129
|
+
return checkCrossProjectPolicyForRequest(
|
|
130
|
+
new Request(requestUrl, request),
|
|
131
|
+
crossProjectPolicies.get(prefix),
|
|
132
|
+
);
|
|
133
|
+
},
|
|
134
|
+
onWarning: message => logger.warn(message),
|
|
135
|
+
});
|
|
136
|
+
if (!loaded) {
|
|
137
|
+
const error = new Error(
|
|
138
|
+
`[BFF][Effect] Invalid Effect entry module: ${entryFile}. Export defineEffectBff(...) or a { api, layer } HttpApi module.`,
|
|
139
|
+
);
|
|
140
|
+
logger.warn(error.message);
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
const candidateOwner = {};
|
|
144
|
+
if (loaded.dispose)
|
|
145
|
+
registerServerRuntimeDisposer(candidateOwner, loaded.dispose);
|
|
146
|
+
const candidate = createDisposableServerRuntimeHandle(
|
|
147
|
+
candidateOwner,
|
|
148
|
+
loaded.handler,
|
|
149
|
+
);
|
|
150
|
+
if (retired) {
|
|
151
|
+
await candidate.dispose();
|
|
152
|
+
throw new Error('Cannot initialize a retired Effect server.');
|
|
153
|
+
}
|
|
154
|
+
handler = candidate;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
api.onPrepare(async () => {
|
|
158
|
+
const { middlewares: globalMiddlewares } = api.getServerContext();
|
|
159
|
+
const configuredPrefix = api.getServerConfig()?.bff?.prefix;
|
|
160
|
+
const enableHandleWeb = api.getServerConfig()?.bff?.enableHandleWeb;
|
|
161
|
+
const configuredPrefixes = Array.isArray(configuredPrefix)
|
|
162
|
+
? configuredPrefix.filter(Boolean)
|
|
163
|
+
: [configuredPrefix || '/api'];
|
|
164
|
+
prefixes = [
|
|
165
|
+
...new Set(
|
|
166
|
+
configuredPrefixes.length > 0 ? configuredPrefixes : ['/api'],
|
|
167
|
+
),
|
|
168
|
+
];
|
|
169
|
+
matchingPrefixes = [...prefixes].sort(
|
|
170
|
+
(left, right) => right.length - left.length,
|
|
171
|
+
);
|
|
172
|
+
prefix = prefixes[0] || '/api';
|
|
173
|
+
const { serverBase } = api.getServerContext() as { serverBase?: object };
|
|
174
|
+
if (serverBase)
|
|
175
|
+
unregisterRuntimeDisposer = registerServerRuntimeDisposer(
|
|
176
|
+
serverBase,
|
|
177
|
+
dispose,
|
|
178
|
+
);
|
|
179
|
+
try {
|
|
180
|
+
await loadHandler();
|
|
181
|
+
} catch (error) {
|
|
182
|
+
await dispose();
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const middlewarePrefixes = enableHandleWeb ? [prefix] : matchingPrefixes;
|
|
187
|
+
const middlewares = middlewarePrefixes.map(middlewarePrefix => ({
|
|
188
|
+
name: 'effect-api-handler',
|
|
189
|
+
path: enableHandleWeb ? '*' : `${middlewarePrefix}/*`,
|
|
190
|
+
method: 'all' as const,
|
|
191
|
+
order: 'post' as const,
|
|
192
|
+
before: EFFECT_MIDDLEWARE_BEFORE,
|
|
193
|
+
handler: async (context: Context, next: Next) => {
|
|
194
|
+
const activeHandler = handler;
|
|
195
|
+
if (!activeHandler) {
|
|
196
|
+
if (enableHandleWeb) {
|
|
197
|
+
await next();
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
return createEffectServerRuntimeErrorResponse(
|
|
201
|
+
api,
|
|
202
|
+
new Error(
|
|
203
|
+
'[BFF][Effect] Missing Effect entry. Define api/index or configure bff.effect.entry.',
|
|
204
|
+
),
|
|
205
|
+
context,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const mountedPrefix = enableHandleWeb
|
|
210
|
+
? matchingPrefixes.find(
|
|
211
|
+
item =>
|
|
212
|
+
item === '/' ||
|
|
213
|
+
context.req.path === item ||
|
|
214
|
+
context.req.path.startsWith(`${item}/`),
|
|
215
|
+
) || prefix
|
|
216
|
+
: middlewarePrefix;
|
|
217
|
+
const response = await dispatchEffectBffRequest(
|
|
218
|
+
activeHandler as EffectBffRequestHandler,
|
|
219
|
+
context.req.raw,
|
|
220
|
+
{
|
|
221
|
+
prefix: mountedPrefix,
|
|
222
|
+
env: context.env as Record<string, unknown>,
|
|
223
|
+
path: context.req.path,
|
|
224
|
+
method: context.req.method,
|
|
225
|
+
onError: error =>
|
|
226
|
+
createEffectServerRuntimeErrorResponse(api, error, context),
|
|
227
|
+
},
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
if (response.status === 404 && enableHandleWeb) {
|
|
231
|
+
await next();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return new Response(response.body, response);
|
|
236
|
+
},
|
|
237
|
+
}));
|
|
238
|
+
|
|
239
|
+
globalMiddlewares.push(...middlewares);
|
|
240
|
+
});
|
|
241
|
+
},
|
|
242
|
+
});
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { fs, upath as path } from '@modern-js/utils';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import {
|
|
4
|
+
emittedEffectEntry,
|
|
5
|
+
relativeEffectAppPath,
|
|
6
|
+
resolveEffectEntryFile,
|
|
7
|
+
} from './paths';
|
|
6
8
|
|
|
7
9
|
export function resolveBuiltEffectEntry(
|
|
8
10
|
appDirectory: string,
|
|
@@ -12,15 +14,15 @@ export function resolveBuiltEffectEntry(
|
|
|
12
14
|
if (sourceEntry === undefined || sourceEntry.length === 0) {
|
|
13
15
|
return undefined;
|
|
14
16
|
}
|
|
15
|
-
const relativeEntry =
|
|
16
|
-
if (relativeEntry ===
|
|
17
|
+
const relativeEntry = relativeEffectAppPath(appDirectory, sourceEntry);
|
|
18
|
+
if (relativeEntry === undefined) {
|
|
17
19
|
throw new Error(
|
|
18
20
|
`Effect BFF entry must be inside the application directory: ${sourceEntry}`,
|
|
19
21
|
);
|
|
20
22
|
}
|
|
21
|
-
const builtEntry =
|
|
22
|
-
.resolve(distDirectory, relativeEntry)
|
|
23
|
-
|
|
23
|
+
const builtEntry = emittedEffectEntry(
|
|
24
|
+
path.resolve(distDirectory, relativeEntry),
|
|
25
|
+
);
|
|
24
26
|
return fs.existsSync(builtEntry) ? builtEntry : undefined;
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -41,9 +43,7 @@ export async function bundleBuiltEffectEntryForNode(options: {
|
|
|
41
43
|
}`,
|
|
42
44
|
);
|
|
43
45
|
}
|
|
44
|
-
const { bundleEffectEntryForNode } = await import(
|
|
45
|
-
'../effect-source-loader/loader'
|
|
46
|
-
);
|
|
46
|
+
const { bundleEffectEntryForNode } = await import('./loader');
|
|
47
47
|
return bundleEffectEntryForNode({
|
|
48
48
|
appDir,
|
|
49
49
|
entryPath: builtEntry,
|
|
@@ -64,7 +64,7 @@ export function resolveEffectEntryPaths(options: {
|
|
|
64
64
|
});
|
|
65
65
|
const relativeEffectEntry =
|
|
66
66
|
sourceEffectEntry !== undefined && sourceEffectEntry.length > 0
|
|
67
|
-
? path.relative(appDir, sourceEffectEntry)
|
|
67
|
+
? emittedEffectEntry(path.relative(appDir, sourceEffectEntry))
|
|
68
68
|
: '';
|
|
69
69
|
return { sourceEffectEntry, relativeEffectEntry };
|
|
70
70
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extractHttpApiFromModule,
|
|
3
|
+
type HttpApiLike,
|
|
4
|
+
type HttpApiReflect,
|
|
5
|
+
} from '@modern-js/bff-effect/effect';
|
|
6
|
+
import { HttpApi } from 'effect/unstable/httpapi';
|
|
7
|
+
import { loadEffectSourceModule } from './loader';
|
|
8
|
+
|
|
9
|
+
type HttpApiRuntime = {
|
|
10
|
+
isHttpApi: (value: unknown) => boolean;
|
|
11
|
+
reflect: HttpApiReflect;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function getHttpApiRuntime(): Promise<HttpApiRuntime> {
|
|
15
|
+
return Promise.resolve({
|
|
16
|
+
isHttpApi: HttpApi.isHttpApi,
|
|
17
|
+
reflect: (api, handlers) => {
|
|
18
|
+
if (!HttpApi.isHttpApi(api)) {
|
|
19
|
+
throw new TypeError('[BFF][Effect] Expected an HttpApi contract.');
|
|
20
|
+
}
|
|
21
|
+
HttpApi.reflect(api, {
|
|
22
|
+
onGroup: handlers.onGroup ?? (() => {}),
|
|
23
|
+
onEndpoint: handlers.onEndpoint,
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function loadEffectApi(options: {
|
|
30
|
+
appDir: string;
|
|
31
|
+
resourcePath: string;
|
|
32
|
+
onDependency?: (dependency: string) => void;
|
|
33
|
+
}): Promise<HttpApiLike | null> {
|
|
34
|
+
return getHttpApiRuntime().then(httpApiRuntime =>
|
|
35
|
+
loadEffectSourceModule(options).then(mod =>
|
|
36
|
+
extractHttpApiFromModule(mod, httpApiRuntime.isHttpApi),
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
export { resolveEffectEntryFile } from '../client-generator/paths';
|
|
2
|
-
export { bundleEffectEntryForNode } from './loader';
|
|
3
1
|
export {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from './
|
|
2
|
+
bundleBuiltEffectEntryForNode,
|
|
3
|
+
resolveEffectEntryPaths,
|
|
4
|
+
} from './built-entry';
|
|
5
|
+
export { bundleEffectEntryForNode } from './loader';
|
|
6
|
+
export { resolveEffectOperationContracts } from './operation-contracts';
|
|
7
|
+
export { resolveEffectEntryFile } from './paths';
|
|
8
|
+
export { bundleEffectWorkerRuntimeSource } from './worker-runtime-source';
|
|
9
|
+
export { generateEffectWorkerRuntimeWrapper } from './worker-runtime-wrapper';
|
|
@@ -39,31 +39,37 @@ function preserveSourceModuleSemantics(): Plugin {
|
|
|
39
39
|
return {
|
|
40
40
|
name: 'modern-js-effect-source-semantics',
|
|
41
41
|
setup(buildApi) {
|
|
42
|
-
buildApi.onLoad(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
buildApi.onLoad(
|
|
43
|
+
{ filter: /\.[cm]?[jt]sx?$/, namespace: 'file' },
|
|
44
|
+
async args => {
|
|
45
|
+
const loader = sourceLoader(args);
|
|
46
|
+
if (!loader) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
50
|
+
const source = await fs.promises.readFile(args.path, 'utf8');
|
|
51
|
+
const canonicalSourcePath = await fs.promises.realpath(args.path);
|
|
52
|
+
const compiled = await transform(source, {
|
|
53
|
+
define: {
|
|
54
|
+
__dirname: JSON.stringify(path.dirname(canonicalSourcePath)),
|
|
55
|
+
__filename: JSON.stringify(canonicalSourcePath),
|
|
56
|
+
'import.meta.url': JSON.stringify(
|
|
57
|
+
pathToFileURL(canonicalSourcePath).href,
|
|
58
|
+
),
|
|
59
|
+
},
|
|
60
|
+
jsx: 'automatic',
|
|
61
|
+
loader,
|
|
62
|
+
sourcefile: args.path,
|
|
63
|
+
target: NODE_TARGET,
|
|
64
|
+
});
|
|
60
65
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
return {
|
|
67
|
+
contents: compiled.code,
|
|
68
|
+
loader: 'js',
|
|
69
|
+
resolveDir: path.dirname(args.path),
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
);
|
|
67
73
|
},
|
|
68
74
|
};
|
|
69
75
|
}
|
|
@@ -73,6 +79,9 @@ const nodeBuiltins = new Set([
|
|
|
73
79
|
...builtinModules.map(name => `node:${name}`),
|
|
74
80
|
]);
|
|
75
81
|
const dependencyResolutionMarker = 'modern-js-effect-dependency-resolution';
|
|
82
|
+
// The BFF server runs lambdas inside these packages' Effect context storage.
|
|
83
|
+
// A bundled copy would own a second storage that the server never enters.
|
|
84
|
+
const BFF_RUNTIME_PACKAGE = /^@modern-js\/(?:bff-effect|plugin-bff)(?:\/|$)/;
|
|
76
85
|
|
|
77
86
|
async function isEsmOnlyFile(filename: string) {
|
|
78
87
|
const extension = path.extname(filename).toLowerCase();
|
|
@@ -119,22 +128,20 @@ function isBareSpecifier(specifier: string) {
|
|
|
119
128
|
}
|
|
120
129
|
|
|
121
130
|
/**
|
|
122
|
-
* Keep installed
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* generated entry is relocatable and directly executable by Node 26.7.
|
|
131
|
+
* Keep installed executable dependencies external to preserve runtime identity.
|
|
132
|
+
* Source requiring compilation is bundled even when pnpm injects a workspace
|
|
133
|
+
* package into node_modules: Node does not strip types inside that directory.
|
|
126
134
|
*/
|
|
127
135
|
function externalizeInstalledDependencies(options: {
|
|
128
|
-
format: 'cjs' | 'esm';
|
|
129
136
|
runtimeResolveDir: string;
|
|
130
137
|
}): Plugin {
|
|
131
|
-
const runtimeRequire = createRequire(
|
|
132
|
-
path.join(options.runtimeResolveDir, '__modern_effect_entry.cjs'),
|
|
133
|
-
);
|
|
134
138
|
return {
|
|
135
139
|
name: 'modern-js-effect-installed-dependencies',
|
|
136
140
|
setup(buildApi) {
|
|
137
141
|
buildApi.onResolve({ filter: /.*/ }, async args => {
|
|
142
|
+
if (BFF_RUNTIME_PACKAGE.test(args.path)) {
|
|
143
|
+
return { external: true, path: args.path };
|
|
144
|
+
}
|
|
138
145
|
if (
|
|
139
146
|
!isBareSpecifier(args.path) ||
|
|
140
147
|
nodeBuiltins.has(args.path) ||
|
|
@@ -165,7 +172,13 @@ function externalizeInstalledDependencies(options: {
|
|
|
165
172
|
const realPath = await fs.promises
|
|
166
173
|
.realpath(resolution.path)
|
|
167
174
|
.catch(() => resolution.path);
|
|
168
|
-
|
|
175
|
+
const loader = SOURCE_LOADERS.get(path.extname(realPath).toLowerCase());
|
|
176
|
+
if (
|
|
177
|
+
realPath.includes(`${path.sep}node_modules${path.sep}`) &&
|
|
178
|
+
loader !== 'ts' &&
|
|
179
|
+
loader !== 'tsx' &&
|
|
180
|
+
loader !== 'jsx'
|
|
181
|
+
) {
|
|
169
182
|
const runtimeResolution = await buildApi.resolve(args.path, {
|
|
170
183
|
importer: '',
|
|
171
184
|
kind: args.kind,
|
|
@@ -176,24 +189,8 @@ function externalizeInstalledDependencies(options: {
|
|
|
176
189
|
resolveDir: options.runtimeResolveDir,
|
|
177
190
|
});
|
|
178
191
|
if (runtimeResolution.errors.length === 0 && runtimeResolution.path) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
requiredPath = runtimeRequire.resolve(args.path);
|
|
183
|
-
} catch {
|
|
184
|
-
requiredPath = runtimeResolution.path;
|
|
185
|
-
}
|
|
186
|
-
if (await isEsmOnlyFile(requiredPath)) {
|
|
187
|
-
return {
|
|
188
|
-
namespace: resolution.namespace,
|
|
189
|
-
path: resolution.path,
|
|
190
|
-
pluginData: resolution.pluginData,
|
|
191
|
-
sideEffects: resolution.sideEffects,
|
|
192
|
-
suffix: resolution.suffix,
|
|
193
|
-
warnings: resolution.warnings,
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
}
|
|
192
|
+
// Node 26 loads installed ESM from CommonJS natively. Rebundling
|
|
193
|
+
// it here would split dependency identity from the BFF runtime.
|
|
197
194
|
return { external: true, path: args.path };
|
|
198
195
|
}
|
|
199
196
|
}
|
|
@@ -227,7 +224,6 @@ export async function bundleEffectEntryForNode(options: {
|
|
|
227
224
|
platform: 'node',
|
|
228
225
|
plugins: [
|
|
229
226
|
externalizeInstalledDependencies({
|
|
230
|
-
format: options.format,
|
|
231
227
|
runtimeResolveDir: options.appDir,
|
|
232
228
|
}),
|
|
233
229
|
preserveSourceModuleSemantics(),
|
|
@@ -365,7 +361,6 @@ export async function loadEffectSourceModule(
|
|
|
365
361
|
platform: 'node',
|
|
366
362
|
plugins: [
|
|
367
363
|
externalizeInstalledDependencies({
|
|
368
|
-
format: 'esm',
|
|
369
364
|
runtimeResolveDir: appDir,
|
|
370
365
|
}),
|
|
371
366
|
preserveSourceModuleSemantics(),
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectEffectEndpoints,
|
|
3
|
+
toOperationContractSources,
|
|
4
|
+
} from '@modern-js/bff-effect/effect';
|
|
5
|
+
import {
|
|
6
|
+
buildOperationContractMap,
|
|
7
|
+
resolveOperationProducer,
|
|
8
|
+
} from '@modern-js/server-runtime-extensions/bff-policy/node';
|
|
9
|
+
import { upath as path } from '@modern-js/utils';
|
|
10
|
+
import { getHttpApiRuntime, loadEffectApi } from './http-api-runtime';
|
|
11
|
+
|
|
12
|
+
/** Derives server policy metadata without emitting or typing a client. */
|
|
13
|
+
export async function resolveEffectOperationContracts(options: {
|
|
14
|
+
appDir: string;
|
|
15
|
+
resourcePath: string;
|
|
16
|
+
prefix: string;
|
|
17
|
+
requestId?: string;
|
|
18
|
+
onDependency?: (dependency: string) => void;
|
|
19
|
+
}) {
|
|
20
|
+
const api = await loadEffectApi(options);
|
|
21
|
+
if (api === null) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const runtime = await getHttpApiRuntime();
|
|
25
|
+
const endpoints = collectEffectEndpoints(
|
|
26
|
+
runtime.reflect,
|
|
27
|
+
api,
|
|
28
|
+
options.prefix,
|
|
29
|
+
);
|
|
30
|
+
return buildOperationContractMap({
|
|
31
|
+
handlers: toOperationContractSources(endpoints),
|
|
32
|
+
...resolveOperationProducer({
|
|
33
|
+
directories: [path.dirname(options.resourcePath), options.appDir],
|
|
34
|
+
requestId: options.requestId,
|
|
35
|
+
onDependency: options.onDependency,
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
}
|