@bleedingdev/modern-js-bff-effect 3.9.0-ultramodern.5 → 3.9.0-ultramodern.7
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 +20 -4
- package/dist/cjs/effect/endpoint-contracts.js +1 -2
- package/dist/cjs/effect/entry-shape.js +2 -2
- package/dist/cjs/effect/handler/definition.js +1 -21
- package/dist/cjs/effect/module.js +4 -4
- package/dist/cjs/effect-client/cross-project.js +92 -0
- package/dist/cjs/effect-client/index.js +3 -1
- package/dist/esm/effect/endpoint-contracts.mjs +1 -2
- package/dist/esm/effect/entry-shape.mjs +2 -2
- package/dist/esm/effect/handler/definition.mjs +1 -21
- package/dist/esm/effect/module.mjs +4 -4
- package/dist/esm/effect-client/cross-project.mjs +54 -0
- package/dist/esm/effect-client/index.mjs +3 -1
- package/dist/esm-node/effect/endpoint-contracts.mjs +1 -2
- package/dist/esm-node/effect/entry-shape.mjs +2 -2
- package/dist/esm-node/effect/handler/definition.mjs +1 -21
- package/dist/esm-node/effect/module.mjs +4 -4
- package/dist/esm-node/effect-client/cross-project.mjs +55 -0
- package/dist/esm-node/effect-client/index.mjs +3 -1
- package/dist/types/effect/edge-dispatcher.d.ts +1 -1
- package/dist/types/effect/endpoint-contracts.d.ts +1 -19
- package/dist/types/effect/entry-shape.d.ts +2 -2
- package/dist/types/effect/handler/types.d.ts +0 -15
- package/dist/types/effect/handler.d.ts +1 -1
- package/dist/types/effect-client/cross-project.d.ts +10 -0
- package/dist/types/effect-client/index.d.ts +3 -0
- package/package.json +5 -18
- package/src/effect/edge-dispatcher.ts +0 -1
- package/src/effect/endpoint-contracts.ts +2 -23
- package/src/effect/entry-shape.ts +6 -4
- package/src/effect/handler/definition.ts +0 -44
- package/src/effect/handler/http.ts +2 -2
- package/src/effect/handler/types.ts +0 -27
- package/src/effect/handler.ts +0 -1
- package/src/effect/module.ts +4 -4
- package/src/effect-client/cross-project.ts +109 -0
- package/src/effect-client/index.ts +12 -2
- package/dist/cjs/effect-client/batch.js +0 -73
- package/dist/cjs/effect-client/envelope.js +0 -145
- package/dist/cjs/effect-client/guards.js +0 -44
- package/dist/cjs/effect-client/request-context.js +0 -64
- package/dist/cjs/effect-client/request.js +0 -66
- package/dist/cjs/effect-client/runtime.js +0 -100
- package/dist/cjs/effect-client/types.js +0 -18
- package/dist/esm/effect-client/batch.mjs +0 -35
- package/dist/esm/effect-client/envelope.mjs +0 -104
- package/dist/esm/effect-client/guards.mjs +0 -3
- package/dist/esm/effect-client/request-context.mjs +0 -23
- package/dist/esm/effect-client/request.mjs +0 -28
- package/dist/esm/effect-client/runtime.mjs +0 -62
- package/dist/esm/effect-client/types.mjs +0 -0
- package/dist/esm-node/effect-client/batch.mjs +0 -36
- package/dist/esm-node/effect-client/envelope.mjs +0 -105
- package/dist/esm-node/effect-client/guards.mjs +0 -4
- package/dist/esm-node/effect-client/request-context.mjs +0 -24
- package/dist/esm-node/effect-client/request.mjs +0 -29
- package/dist/esm-node/effect-client/runtime.mjs +0 -63
- package/dist/esm-node/effect-client/types.mjs +0 -1
- package/dist/types/effect-client/batch.d.ts +0 -2
- package/dist/types/effect-client/envelope.d.ts +0 -15
- package/dist/types/effect-client/guards.d.ts +0 -2
- package/dist/types/effect-client/request-context.d.ts +0 -3
- package/dist/types/effect-client/request.d.ts +0 -1
- package/dist/types/effect-client/runtime.d.ts +0 -5
- package/dist/types/effect-client/types.d.ts +0 -68
- package/src/effect-client/batch.ts +0 -50
- package/src/effect-client/envelope.ts +0 -238
- package/src/effect-client/guards.ts +0 -6
- package/src/effect-client/request-context.ts +0 -54
- package/src/effect-client/request.ts +0 -51
- package/src/effect-client/runtime.ts +0 -122
- package/src/effect-client/types.ts +0 -84
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @modern-js/bff-effect
|
|
2
2
|
|
|
3
3
|
Fork-owned Effect primitives for UltraModern.js BFF runtimes. The package
|
|
4
|
-
keeps the portable Effect handler, data-platform,
|
|
4
|
+
keeps the portable Effect handler, data-platform, native inferred clients,
|
|
5
5
|
and edge dispatcher behind standalone package entry points rather than growing
|
|
6
6
|
the upstream-owned `@modern-js/plugin-bff` implementation.
|
|
7
7
|
|
|
@@ -16,10 +16,26 @@ upstream Modern.js project.
|
|
|
16
16
|
- `@modern-js/bff-effect/effect-edge` exposes the edge-safe dispatcher without
|
|
17
17
|
a static Node built-in import.
|
|
18
18
|
- `@modern-js/bff-effect/effect-client` exposes the Effect client runtime.
|
|
19
|
-
- `@modern-js/bff-effect/effect-client-runtime` exposes the generated-client
|
|
20
|
-
runtime helpers.
|
|
21
19
|
- `@modern-js/bff-effect/data-platform` exposes batching, envelope, tracing,
|
|
22
20
|
validation, and invalidation primitives.
|
|
23
21
|
|
|
24
|
-
Adapter lifecycle integration, Hono integration, source
|
|
22
|
+
Adapter lifecycle integration, Hono integration, server source loading, and
|
|
25
23
|
backend federation are intentionally owned by their respective packages.
|
|
24
|
+
|
|
25
|
+
## HTTP clients
|
|
26
|
+
|
|
27
|
+
Keep the `HttpApi` contract in a shared module and pass it to Effect's
|
|
28
|
+
`HttpApiClient.make` or `makeEffectHttpApiClient` from `/effect-client`.
|
|
29
|
+
Requests, responses, and declared errors are inferred from that contract.
|
|
30
|
+
No SDK generation or backend-entry import is required.
|
|
31
|
+
|
|
32
|
+
`defineEffectBff` creates server handlers. Its former `client` placeholder,
|
|
33
|
+
the `@api/index` client transform, and `/effect-client-runtime` have been
|
|
34
|
+
removed. Move consumers to the shared contract and execute native client
|
|
35
|
+
operations with Effect.
|
|
36
|
+
|
|
37
|
+
For APIs hosted through a cross-project plugin, pass `crossProject` to the
|
|
38
|
+
native client helper: `{ requestId: 'catalog', operationVersion: 2, prefix: '/api' }`.
|
|
39
|
+
Use the producer's request ID, package major version, and server mount path.
|
|
40
|
+
Effect HTTP middleware derives operation headers from the shared contract;
|
|
41
|
+
the server continues to reject missing or stale contract headers.
|
|
@@ -39,7 +39,6 @@ const node_namespaceObject = require("@modern-js/server-runtime-extensions/bff-p
|
|
|
39
39
|
const external_entry_shape_js_namespaceObject = require("./entry-shape.js");
|
|
40
40
|
function resolveEffectEndpointName(endpoint) {
|
|
41
41
|
if ('string' == typeof endpoint.identifier && endpoint.identifier) return endpoint.identifier;
|
|
42
|
-
if ('string' == typeof endpoint.name && endpoint.name) return endpoint.name;
|
|
43
42
|
return '';
|
|
44
43
|
}
|
|
45
44
|
function ensureLeadingSlash(pathname) {
|
|
@@ -97,7 +96,7 @@ async function extractHttpApiFromModule(mod, isHttpApi) {
|
|
|
97
96
|
isValidatorAwareHandlerFactory: external_entry_shape_js_namespaceObject.isValidatorAwareHandlerFactory,
|
|
98
97
|
isHttpApi
|
|
99
98
|
});
|
|
100
|
-
return !facts?.
|
|
99
|
+
return !facts?.unsupportedShape && facts?.api !== void 0 && facts.hasRuntimeLayer && (void 0 === facts.createHandler || facts.createHandlerValidatorAware) ? facts.api : null;
|
|
101
100
|
}
|
|
102
101
|
function createEffectEndpointContractHash(endpoint, requestId) {
|
|
103
102
|
return (0, node_namespaceObject.createOperationContractHash)(createEffectOperationContractSource(endpoint), requestId);
|
|
@@ -77,13 +77,13 @@ function classifyEffectBffEntryModule(value, predicates) {
|
|
|
77
77
|
if (predicates.isRequestHandler(module.handler)) return createEntryShapeFacts(module, predicates, '`handler` export');
|
|
78
78
|
return createEntryShapeFacts(module, predicates);
|
|
79
79
|
}
|
|
80
|
-
function createEntryShapeFacts(module, predicates,
|
|
80
|
+
function createEntryShapeFacts(module, predicates, unsupportedShape) {
|
|
81
81
|
const createHandler = 'function' == typeof module.createHandler ? module.createHandler : void 0;
|
|
82
82
|
const api = predicates.isHttpApi(module.api) ? module.api : void 0;
|
|
83
83
|
const hasRuntimeLayer = void 0 !== module.layer;
|
|
84
84
|
return {
|
|
85
85
|
module,
|
|
86
|
-
|
|
86
|
+
unsupportedShape,
|
|
87
87
|
createHandler,
|
|
88
88
|
createHandlerValidatorAware: void 0 !== createHandler && predicates.isValidatorAwareHandlerFactory(createHandler),
|
|
89
89
|
api,
|
|
@@ -53,30 +53,10 @@ function defineEffectBff(definition) {
|
|
|
53
53
|
validateRequest: options?.validateRequest
|
|
54
54
|
});
|
|
55
55
|
});
|
|
56
|
-
const client = createLoaderMaterializedClientPlaceholder();
|
|
57
56
|
return {
|
|
58
57
|
...definition,
|
|
59
|
-
createHandler
|
|
60
|
-
client
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
const LOADER_CLIENT_IGNORED_KEYS = new Set([
|
|
64
|
-
'then',
|
|
65
|
-
'catch',
|
|
66
|
-
'finally',
|
|
67
|
-
'toJSON',
|
|
68
|
-
'$$typeof'
|
|
69
|
-
]);
|
|
70
|
-
function createLoaderMaterializedClientPlaceholder() {
|
|
71
|
-
const explain = (property)=>{
|
|
72
|
-
throw new Error(`[BFF][Effect] client.${String(property)} is not available here: the typed client only exists when the API entry is imported through the "@api/index" transformed path (the BFF loader replaces it with generated client code). On the server, use HttpApiClient or call the Effect layer directly.`);
|
|
58
|
+
createHandler
|
|
73
59
|
};
|
|
74
|
-
return new Proxy(Object.create(null), {
|
|
75
|
-
get (_target, property) {
|
|
76
|
-
if ('symbol' == typeof property || LOADER_CLIENT_IGNORED_KEYS.has(property)) return;
|
|
77
|
-
return explain(property);
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
60
|
}
|
|
81
61
|
function defineEffectRpcBff(definition) {
|
|
82
62
|
const createHandler = (options)=>(0, external_rpc_js_namespaceObject.createRpcApiHandler)({
|
|
@@ -43,7 +43,7 @@ function shareDispose(dispose) {
|
|
|
43
43
|
return disposePromise;
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
-
function
|
|
46
|
+
function rejectUnsupportedEffectModuleShape(options, shape) {
|
|
47
47
|
options.onWarning?.(`${external_entry_shape_js_namespaceObject.strictEffectApproachMessage} Rejected ${shape}.`);
|
|
48
48
|
return true;
|
|
49
49
|
}
|
|
@@ -71,8 +71,8 @@ function classifyEffectBffRuntimeEntryShape(module) {
|
|
|
71
71
|
}
|
|
72
72
|
function resolveClassifiedEffectBffModuleHandler(facts, options = {}) {
|
|
73
73
|
if (!facts) return null;
|
|
74
|
-
if (facts.
|
|
75
|
-
if (
|
|
74
|
+
if (facts.unsupportedShape) {
|
|
75
|
+
if (rejectUnsupportedEffectModuleShape(options, facts.unsupportedShape)) return null;
|
|
76
76
|
}
|
|
77
77
|
if ('function' == typeof facts.createHandler) {
|
|
78
78
|
if (facts.createHandlerValidatorAware) {
|
|
@@ -85,7 +85,7 @@ function resolveClassifiedEffectBffModuleHandler(facts, options = {}) {
|
|
|
85
85
|
return createLoadedHandler(webHandler);
|
|
86
86
|
}
|
|
87
87
|
if (void 0 === facts.api || !facts.hasRuntimeLayer) {
|
|
88
|
-
|
|
88
|
+
rejectUnsupportedEffectModuleShape(options, 'unbranded `createHandler` export');
|
|
89
89
|
return null;
|
|
90
90
|
}
|
|
91
91
|
options.onWarning?.(`${external_entry_shape_js_namespaceObject.strictEffectApproachMessage} Ignored unbranded \`createHandler\` export and rebuilt the handler from its { api, layer } exports.`);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.d = (exports1, getters, values)=>{
|
|
5
|
+
var define = (defs, kind)=>{
|
|
6
|
+
for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
[kind]: defs[key]
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
define(getters, "get");
|
|
12
|
+
define(values, "value");
|
|
13
|
+
};
|
|
14
|
+
})();
|
|
15
|
+
(()=>{
|
|
16
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
17
|
+
})();
|
|
18
|
+
(()=>{
|
|
19
|
+
__webpack_require__.r = (exports1)=>{
|
|
20
|
+
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
21
|
+
value: 'Module'
|
|
22
|
+
});
|
|
23
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
24
|
+
value: true
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
})();
|
|
28
|
+
var __webpack_exports__ = {};
|
|
29
|
+
__webpack_require__.r(__webpack_exports__);
|
|
30
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
31
|
+
withCrossProjectPolicy: ()=>withCrossProjectPolicy
|
|
32
|
+
});
|
|
33
|
+
const bff_policy_namespaceObject = require("@modern-js/server-runtime-extensions/bff-policy");
|
|
34
|
+
const Effect_namespaceObject = require("effect/Effect");
|
|
35
|
+
const Schema_namespaceObject = require("effect/Schema");
|
|
36
|
+
const http_namespaceObject = require("effect/unstable/http");
|
|
37
|
+
const httpapi_namespaceObject = require("effect/unstable/httpapi");
|
|
38
|
+
const encodeJson = Schema_namespaceObject.encodeSync(Schema_namespaceObject.fromJsonString(Schema_namespaceObject.Unknown));
|
|
39
|
+
function withCrossProjectPolicy(api, options, client) {
|
|
40
|
+
const contracts = {};
|
|
41
|
+
const prefix = options.prefix.replace(/\/$/, '');
|
|
42
|
+
httpapi_namespaceObject.HttpApi.reflect(api, {
|
|
43
|
+
onGroup: ()=>{},
|
|
44
|
+
onEndpoint: ({ endpoint })=>{
|
|
45
|
+
const routePath = `${prefix}${'/' === endpoint.path ? '' : endpoint.path}` || '/';
|
|
46
|
+
const name = endpoint.identifier;
|
|
47
|
+
contracts[`${endpoint.method}:${routePath}`] = {
|
|
48
|
+
operationId: `${options.requestId}:${name}`,
|
|
49
|
+
name,
|
|
50
|
+
method: endpoint.method,
|
|
51
|
+
routePath
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
return client.pipe(http_namespaceObject.HttpClient.mapRequestEffect((request)=>{
|
|
56
|
+
const observed = (0, bff_policy_namespaceObject.resolveCrossProjectRequestObservation)({
|
|
57
|
+
method: request.method,
|
|
58
|
+
pathname: new URL(request.url, 'http://effect.invalid').pathname
|
|
59
|
+
}, {
|
|
60
|
+
expectedOperationContracts: contracts
|
|
61
|
+
});
|
|
62
|
+
const contract = observed && contracts[`${observed.method}:${observed.routePath}`];
|
|
63
|
+
if (void 0 === contract) return Effect_namespaceObject.die(new Error('[BFF][Effect] Request has no matching shared operation contract.'));
|
|
64
|
+
return Effect_namespaceObject.gen(function*() {
|
|
65
|
+
const input = encodeJson({
|
|
66
|
+
httpMethod: contract.method,
|
|
67
|
+
name: contract.name,
|
|
68
|
+
requestId: options.requestId,
|
|
69
|
+
routePath: contract.routePath
|
|
70
|
+
});
|
|
71
|
+
const digest = yield* Effect_namespaceObject.promise(()=>globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(input)));
|
|
72
|
+
const schemaHash = Array.from(new Uint8Array(digest), (value)=>value.toString(16).padStart(2, '0')).join('');
|
|
73
|
+
return request.pipe(http_namespaceObject.HttpClientRequest.setHeader(bff_policy_namespaceObject.BFF_ENVELOPE_HEADER, encodeJson({
|
|
74
|
+
requestId: options.requestId
|
|
75
|
+
})), http_namespaceObject.HttpClientRequest.setHeader(bff_policy_namespaceObject.BFF_OPERATION_CONTEXT_HEADER, contract.operationId), http_namespaceObject.HttpClientRequest.setHeader(bff_policy_namespaceObject.BFF_OPERATION_CONTEXT_DETAIL_HEADER, encodeJson({
|
|
76
|
+
requestId: options.requestId,
|
|
77
|
+
operationId: contract.operationId,
|
|
78
|
+
method: contract.method,
|
|
79
|
+
routePath: contract.routePath,
|
|
80
|
+
schemaHash,
|
|
81
|
+
operationVersion: options.operationVersion
|
|
82
|
+
})));
|
|
83
|
+
});
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
exports.withCrossProjectPolicy = __webpack_exports__.withCrossProjectPolicy;
|
|
87
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
88
|
+
"withCrossProjectPolicy"
|
|
89
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
90
|
+
Object.defineProperty(exports, '__esModule', {
|
|
91
|
+
value: true
|
|
92
|
+
});
|
|
@@ -61,6 +61,7 @@ const http_namespaceObject = require("effect/unstable/http");
|
|
|
61
61
|
const httpapi_namespaceObject = require("effect/unstable/httpapi");
|
|
62
62
|
const rpc_namespaceObject = require("effect/unstable/rpc");
|
|
63
63
|
const rpcSerialization_js_namespaceObject = require("../effect/rpcSerialization.js");
|
|
64
|
+
const external_cross_project_js_namespaceObject = require("./cross-project.js");
|
|
64
65
|
const Schema_namespaceObject = require("effect/Schema");
|
|
65
66
|
const HttpClientError_namespaceObject = require("effect/unstable/http/HttpClientError");
|
|
66
67
|
class EffectRpcClientError extends Data_namespaceObject.TaggedError('EffectRpcClientError') {
|
|
@@ -98,7 +99,8 @@ function makeEffectHttpApiClient(api, options) {
|
|
|
98
99
|
for (const [header, value] of Object.entries(requestContextHeaders))if (void 0 === nextRequest.headers[header.toLowerCase()]) nextRequest = http_namespaceObject.HttpClientRequest.setHeader(nextRequest, header, value);
|
|
99
100
|
return nextRequest;
|
|
100
101
|
}));
|
|
101
|
-
|
|
102
|
+
const policyClient = options?.crossProject !== void 0 ? (0, external_cross_project_js_namespaceObject.withCrossProjectPolicy)(api, options.crossProject, contextClient) : contextClient;
|
|
103
|
+
return 'function' == typeof options?.transformClient ? options.transformClient(policyClient) : policyClient;
|
|
102
104
|
};
|
|
103
105
|
return httpapi_namespaceObject.HttpApiClient.make(api, {
|
|
104
106
|
baseUrl: options?.baseUrl,
|
|
@@ -2,7 +2,6 @@ import { createOperationContractHash } from "@modern-js/server-runtime-extension
|
|
|
2
2
|
import { classifyEffectBffEntryModule, isValidatorAwareHandlerFactory } from "./entry-shape.mjs";
|
|
3
3
|
function resolveEffectEndpointName(endpoint) {
|
|
4
4
|
if ('string' == typeof endpoint.identifier && endpoint.identifier) return endpoint.identifier;
|
|
5
|
-
if ('string' == typeof endpoint.name && endpoint.name) return endpoint.name;
|
|
6
5
|
return '';
|
|
7
6
|
}
|
|
8
7
|
function ensureLeadingSlash(pathname) {
|
|
@@ -60,7 +59,7 @@ async function extractHttpApiFromModule(mod, isHttpApi) {
|
|
|
60
59
|
isValidatorAwareHandlerFactory: isValidatorAwareHandlerFactory,
|
|
61
60
|
isHttpApi
|
|
62
61
|
});
|
|
63
|
-
return !facts?.
|
|
62
|
+
return !facts?.unsupportedShape && facts?.api !== void 0 && facts.hasRuntimeLayer && (void 0 === facts.createHandler || facts.createHandlerValidatorAware) ? facts.api : null;
|
|
64
63
|
}
|
|
65
64
|
function createEffectEndpointContractHash(endpoint, requestId) {
|
|
66
65
|
return createOperationContractHash(createEffectOperationContractSource(endpoint), requestId);
|
|
@@ -34,13 +34,13 @@ function classifyEffectBffEntryModule(value, predicates) {
|
|
|
34
34
|
if (predicates.isRequestHandler(module.handler)) return createEntryShapeFacts(module, predicates, '`handler` export');
|
|
35
35
|
return createEntryShapeFacts(module, predicates);
|
|
36
36
|
}
|
|
37
|
-
function createEntryShapeFacts(module, predicates,
|
|
37
|
+
function createEntryShapeFacts(module, predicates, unsupportedShape) {
|
|
38
38
|
const createHandler = 'function' == typeof module.createHandler ? module.createHandler : void 0;
|
|
39
39
|
const api = predicates.isHttpApi(module.api) ? module.api : void 0;
|
|
40
40
|
const hasRuntimeLayer = void 0 !== module.layer;
|
|
41
41
|
return {
|
|
42
42
|
module,
|
|
43
|
-
|
|
43
|
+
unsupportedShape,
|
|
44
44
|
createHandler,
|
|
45
45
|
createHandlerValidatorAware: void 0 !== createHandler && predicates.isValidatorAwareHandlerFactory(createHandler),
|
|
46
46
|
api,
|
|
@@ -20,30 +20,10 @@ function defineEffectBff(definition) {
|
|
|
20
20
|
validateRequest: options?.validateRequest
|
|
21
21
|
});
|
|
22
22
|
});
|
|
23
|
-
const client = createLoaderMaterializedClientPlaceholder();
|
|
24
23
|
return {
|
|
25
24
|
...definition,
|
|
26
|
-
createHandler
|
|
27
|
-
client
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
const LOADER_CLIENT_IGNORED_KEYS = new Set([
|
|
31
|
-
'then',
|
|
32
|
-
'catch',
|
|
33
|
-
'finally',
|
|
34
|
-
'toJSON',
|
|
35
|
-
'$$typeof'
|
|
36
|
-
]);
|
|
37
|
-
function createLoaderMaterializedClientPlaceholder() {
|
|
38
|
-
const explain = (property)=>{
|
|
39
|
-
throw new Error(`[BFF][Effect] client.${String(property)} is not available here: the typed client only exists when the API entry is imported through the "@api/index" transformed path (the BFF loader replaces it with generated client code). On the server, use HttpApiClient or call the Effect layer directly.`);
|
|
25
|
+
createHandler
|
|
40
26
|
};
|
|
41
|
-
return new Proxy(Object.create(null), {
|
|
42
|
-
get (_target, property) {
|
|
43
|
-
if ('symbol' == typeof property || LOADER_CLIENT_IGNORED_KEYS.has(property)) return;
|
|
44
|
-
return explain(property);
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
27
|
}
|
|
48
28
|
function defineEffectRpcBff(definition) {
|
|
49
29
|
const createHandler = (options)=>createRpcApiHandler({
|
|
@@ -11,7 +11,7 @@ function shareDispose(dispose) {
|
|
|
11
11
|
return disposePromise;
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
-
function
|
|
14
|
+
function rejectUnsupportedEffectModuleShape(options, shape) {
|
|
15
15
|
options.onWarning?.(`${strictEffectApproachMessage} Rejected ${shape}.`);
|
|
16
16
|
return true;
|
|
17
17
|
}
|
|
@@ -39,8 +39,8 @@ function classifyEffectBffRuntimeEntryShape(module) {
|
|
|
39
39
|
}
|
|
40
40
|
function resolveClassifiedEffectBffModuleHandler(facts, options = {}) {
|
|
41
41
|
if (!facts) return null;
|
|
42
|
-
if (facts.
|
|
43
|
-
if (
|
|
42
|
+
if (facts.unsupportedShape) {
|
|
43
|
+
if (rejectUnsupportedEffectModuleShape(options, facts.unsupportedShape)) return null;
|
|
44
44
|
}
|
|
45
45
|
if ('function' == typeof facts.createHandler) {
|
|
46
46
|
if (facts.createHandlerValidatorAware) {
|
|
@@ -53,7 +53,7 @@ function resolveClassifiedEffectBffModuleHandler(facts, options = {}) {
|
|
|
53
53
|
return createLoadedHandler(webHandler);
|
|
54
54
|
}
|
|
55
55
|
if (void 0 === facts.api || !facts.hasRuntimeLayer) {
|
|
56
|
-
|
|
56
|
+
rejectUnsupportedEffectModuleShape(options, 'unbranded `createHandler` export');
|
|
57
57
|
return null;
|
|
58
58
|
}
|
|
59
59
|
options.onWarning?.(`${strictEffectApproachMessage} Ignored unbranded \`createHandler\` export and rebuilt the handler from its { api, layer } exports.`);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { BFF_ENVELOPE_HEADER, BFF_OPERATION_CONTEXT_DETAIL_HEADER, BFF_OPERATION_CONTEXT_HEADER, resolveCrossProjectRequestObservation } from "@modern-js/server-runtime-extensions/bff-policy";
|
|
2
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
3
|
+
import { HttpApi } from "effect/unstable/httpapi";
|
|
4
|
+
import * as __rspack_external_effect_Effect_41060786 from "effect/Effect";
|
|
5
|
+
import * as __rspack_external_effect_Schema_6065f72d from "effect/Schema";
|
|
6
|
+
const encodeJson = __rspack_external_effect_Schema_6065f72d.encodeSync(__rspack_external_effect_Schema_6065f72d.fromJsonString(__rspack_external_effect_Schema_6065f72d.Unknown));
|
|
7
|
+
function withCrossProjectPolicy(api, options, client) {
|
|
8
|
+
const contracts = {};
|
|
9
|
+
const prefix = options.prefix.replace(/\/$/, '');
|
|
10
|
+
HttpApi.reflect(api, {
|
|
11
|
+
onGroup: ()=>{},
|
|
12
|
+
onEndpoint: ({ endpoint })=>{
|
|
13
|
+
const routePath = `${prefix}${'/' === endpoint.path ? '' : endpoint.path}` || '/';
|
|
14
|
+
const name = endpoint.identifier;
|
|
15
|
+
contracts[`${endpoint.method}:${routePath}`] = {
|
|
16
|
+
operationId: `${options.requestId}:${name}`,
|
|
17
|
+
name,
|
|
18
|
+
method: endpoint.method,
|
|
19
|
+
routePath
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
return client.pipe(HttpClient.mapRequestEffect((request)=>{
|
|
24
|
+
const observed = resolveCrossProjectRequestObservation({
|
|
25
|
+
method: request.method,
|
|
26
|
+
pathname: new URL(request.url, 'http://effect.invalid').pathname
|
|
27
|
+
}, {
|
|
28
|
+
expectedOperationContracts: contracts
|
|
29
|
+
});
|
|
30
|
+
const contract = observed && contracts[`${observed.method}:${observed.routePath}`];
|
|
31
|
+
if (void 0 === contract) return __rspack_external_effect_Effect_41060786.die(new Error('[BFF][Effect] Request has no matching shared operation contract.'));
|
|
32
|
+
return __rspack_external_effect_Effect_41060786.gen(function*() {
|
|
33
|
+
const input = encodeJson({
|
|
34
|
+
httpMethod: contract.method,
|
|
35
|
+
name: contract.name,
|
|
36
|
+
requestId: options.requestId,
|
|
37
|
+
routePath: contract.routePath
|
|
38
|
+
});
|
|
39
|
+
const digest = yield* __rspack_external_effect_Effect_41060786.promise(()=>globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(input)));
|
|
40
|
+
const schemaHash = Array.from(new Uint8Array(digest), (value)=>value.toString(16).padStart(2, '0')).join('');
|
|
41
|
+
return request.pipe(HttpClientRequest.setHeader(BFF_ENVELOPE_HEADER, encodeJson({
|
|
42
|
+
requestId: options.requestId
|
|
43
|
+
})), HttpClientRequest.setHeader(BFF_OPERATION_CONTEXT_HEADER, contract.operationId), HttpClientRequest.setHeader(BFF_OPERATION_CONTEXT_DETAIL_HEADER, encodeJson({
|
|
44
|
+
requestId: options.requestId,
|
|
45
|
+
operationId: contract.operationId,
|
|
46
|
+
method: contract.method,
|
|
47
|
+
routePath: contract.routePath,
|
|
48
|
+
schemaHash,
|
|
49
|
+
operationVersion: options.operationVersion
|
|
50
|
+
})));
|
|
51
|
+
});
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
export { withCrossProjectPolicy };
|
|
@@ -5,6 +5,7 @@ import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/
|
|
|
5
5
|
import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "effect/unstable/httpapi";
|
|
6
6
|
import { Rpc, RpcClient, RpcGroup, RpcSchema, RpcSerialization } from "effect/unstable/rpc";
|
|
7
7
|
import { getRpcSerializationLayer } from "../effect/rpcSerialization.mjs";
|
|
8
|
+
import { withCrossProjectPolicy } from "./cross-project.mjs";
|
|
8
9
|
import * as __rspack_external_effect_Schema_6065f72d from "effect/Schema";
|
|
9
10
|
import * as __rspack_external_effect_unstable_http_HttpClientError_4b9eaee6 from "effect/unstable/http/HttpClientError";
|
|
10
11
|
import * as __rspack_external_effect_Data_2b3f4efb from "effect/Data";
|
|
@@ -46,7 +47,8 @@ function makeEffectHttpApiClient(api, options) {
|
|
|
46
47
|
for (const [header, value] of Object.entries(requestContextHeaders))if (void 0 === nextRequest.headers[header.toLowerCase()]) nextRequest = HttpClientRequest.setHeader(nextRequest, header, value);
|
|
47
48
|
return nextRequest;
|
|
48
49
|
}));
|
|
49
|
-
|
|
50
|
+
const policyClient = options?.crossProject !== void 0 ? withCrossProjectPolicy(api, options.crossProject, contextClient) : contextClient;
|
|
51
|
+
return 'function' == typeof options?.transformClient ? options.transformClient(policyClient) : policyClient;
|
|
50
52
|
};
|
|
51
53
|
return HttpApiClient.make(api, {
|
|
52
54
|
baseUrl: options?.baseUrl,
|
|
@@ -3,7 +3,6 @@ import { createOperationContractHash } from "@modern-js/server-runtime-extension
|
|
|
3
3
|
import { classifyEffectBffEntryModule, isValidatorAwareHandlerFactory } from "./entry-shape.mjs";
|
|
4
4
|
function resolveEffectEndpointName(endpoint) {
|
|
5
5
|
if ('string' == typeof endpoint.identifier && endpoint.identifier) return endpoint.identifier;
|
|
6
|
-
if ('string' == typeof endpoint.name && endpoint.name) return endpoint.name;
|
|
7
6
|
return '';
|
|
8
7
|
}
|
|
9
8
|
function ensureLeadingSlash(pathname) {
|
|
@@ -61,7 +60,7 @@ async function extractHttpApiFromModule(mod, isHttpApi) {
|
|
|
61
60
|
isValidatorAwareHandlerFactory: isValidatorAwareHandlerFactory,
|
|
62
61
|
isHttpApi
|
|
63
62
|
});
|
|
64
|
-
return !facts?.
|
|
63
|
+
return !facts?.unsupportedShape && facts?.api !== void 0 && facts.hasRuntimeLayer && (void 0 === facts.createHandler || facts.createHandlerValidatorAware) ? facts.api : null;
|
|
65
64
|
}
|
|
66
65
|
function createEffectEndpointContractHash(endpoint, requestId) {
|
|
67
66
|
return createOperationContractHash(createEffectOperationContractSource(endpoint), requestId);
|
|
@@ -46,13 +46,13 @@ function classifyEffectBffEntryModule(value, predicates) {
|
|
|
46
46
|
if (predicates.isRequestHandler(module.handler)) return createEntryShapeFacts(module, predicates, '`handler` export');
|
|
47
47
|
return createEntryShapeFacts(module, predicates);
|
|
48
48
|
}
|
|
49
|
-
function createEntryShapeFacts(module, predicates,
|
|
49
|
+
function createEntryShapeFacts(module, predicates, unsupportedShape) {
|
|
50
50
|
const createHandler = 'function' == typeof module.createHandler ? module.createHandler : void 0;
|
|
51
51
|
const api = predicates.isHttpApi(module.api) ? module.api : void 0;
|
|
52
52
|
const hasRuntimeLayer = void 0 !== module.layer;
|
|
53
53
|
return {
|
|
54
54
|
module,
|
|
55
|
-
|
|
55
|
+
unsupportedShape,
|
|
56
56
|
createHandler,
|
|
57
57
|
createHandlerValidatorAware: void 0 !== createHandler && predicates.isValidatorAwareHandlerFactory(createHandler),
|
|
58
58
|
api,
|
|
@@ -21,30 +21,10 @@ function defineEffectBff(definition) {
|
|
|
21
21
|
validateRequest: options?.validateRequest
|
|
22
22
|
});
|
|
23
23
|
});
|
|
24
|
-
const client = createLoaderMaterializedClientPlaceholder();
|
|
25
24
|
return {
|
|
26
25
|
...definition,
|
|
27
|
-
createHandler
|
|
28
|
-
client
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
const LOADER_CLIENT_IGNORED_KEYS = new Set([
|
|
32
|
-
'then',
|
|
33
|
-
'catch',
|
|
34
|
-
'finally',
|
|
35
|
-
'toJSON',
|
|
36
|
-
'$$typeof'
|
|
37
|
-
]);
|
|
38
|
-
function createLoaderMaterializedClientPlaceholder() {
|
|
39
|
-
const explain = (property)=>{
|
|
40
|
-
throw new Error(`[BFF][Effect] client.${String(property)} is not available here: the typed client only exists when the API entry is imported through the "@api/index" transformed path (the BFF loader replaces it with generated client code). On the server, use HttpApiClient or call the Effect layer directly.`);
|
|
26
|
+
createHandler
|
|
41
27
|
};
|
|
42
|
-
return new Proxy(Object.create(null), {
|
|
43
|
-
get (_target, property) {
|
|
44
|
-
if ('symbol' == typeof property || LOADER_CLIENT_IGNORED_KEYS.has(property)) return;
|
|
45
|
-
return explain(property);
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
28
|
}
|
|
49
29
|
function defineEffectRpcBff(definition) {
|
|
50
30
|
const createHandler = (options)=>createRpcApiHandler({
|
|
@@ -12,7 +12,7 @@ function shareDispose(dispose) {
|
|
|
12
12
|
return disposePromise;
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
-
function
|
|
15
|
+
function rejectUnsupportedEffectModuleShape(options, shape) {
|
|
16
16
|
options.onWarning?.(`${strictEffectApproachMessage} Rejected ${shape}.`);
|
|
17
17
|
return true;
|
|
18
18
|
}
|
|
@@ -40,8 +40,8 @@ function classifyEffectBffRuntimeEntryShape(module) {
|
|
|
40
40
|
}
|
|
41
41
|
function resolveClassifiedEffectBffModuleHandler(facts, options = {}) {
|
|
42
42
|
if (!facts) return null;
|
|
43
|
-
if (facts.
|
|
44
|
-
if (
|
|
43
|
+
if (facts.unsupportedShape) {
|
|
44
|
+
if (rejectUnsupportedEffectModuleShape(options, facts.unsupportedShape)) return null;
|
|
45
45
|
}
|
|
46
46
|
if ('function' == typeof facts.createHandler) {
|
|
47
47
|
if (facts.createHandlerValidatorAware) {
|
|
@@ -54,7 +54,7 @@ function resolveClassifiedEffectBffModuleHandler(facts, options = {}) {
|
|
|
54
54
|
return createLoadedHandler(webHandler);
|
|
55
55
|
}
|
|
56
56
|
if (void 0 === facts.api || !facts.hasRuntimeLayer) {
|
|
57
|
-
|
|
57
|
+
rejectUnsupportedEffectModuleShape(options, 'unbranded `createHandler` export');
|
|
58
58
|
return null;
|
|
59
59
|
}
|
|
60
60
|
options.onWarning?.(`${strictEffectApproachMessage} Ignored unbranded \`createHandler\` export and rebuilt the handler from its { api, layer } exports.`);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { BFF_ENVELOPE_HEADER, BFF_OPERATION_CONTEXT_DETAIL_HEADER, BFF_OPERATION_CONTEXT_HEADER, resolveCrossProjectRequestObservation } from "@modern-js/server-runtime-extensions/bff-policy";
|
|
3
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
4
|
+
import { HttpApi } from "effect/unstable/httpapi";
|
|
5
|
+
import * as __rspack_external_effect_Effect_41060786 from "effect/Effect";
|
|
6
|
+
import * as __rspack_external_effect_Schema_6065f72d from "effect/Schema";
|
|
7
|
+
const encodeJson = __rspack_external_effect_Schema_6065f72d.encodeSync(__rspack_external_effect_Schema_6065f72d.fromJsonString(__rspack_external_effect_Schema_6065f72d.Unknown));
|
|
8
|
+
function withCrossProjectPolicy(api, options, client) {
|
|
9
|
+
const contracts = {};
|
|
10
|
+
const prefix = options.prefix.replace(/\/$/, '');
|
|
11
|
+
HttpApi.reflect(api, {
|
|
12
|
+
onGroup: ()=>{},
|
|
13
|
+
onEndpoint: ({ endpoint })=>{
|
|
14
|
+
const routePath = `${prefix}${'/' === endpoint.path ? '' : endpoint.path}` || '/';
|
|
15
|
+
const name = endpoint.identifier;
|
|
16
|
+
contracts[`${endpoint.method}:${routePath}`] = {
|
|
17
|
+
operationId: `${options.requestId}:${name}`,
|
|
18
|
+
name,
|
|
19
|
+
method: endpoint.method,
|
|
20
|
+
routePath
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return client.pipe(HttpClient.mapRequestEffect((request)=>{
|
|
25
|
+
const observed = resolveCrossProjectRequestObservation({
|
|
26
|
+
method: request.method,
|
|
27
|
+
pathname: new URL(request.url, 'http://effect.invalid').pathname
|
|
28
|
+
}, {
|
|
29
|
+
expectedOperationContracts: contracts
|
|
30
|
+
});
|
|
31
|
+
const contract = observed && contracts[`${observed.method}:${observed.routePath}`];
|
|
32
|
+
if (void 0 === contract) return __rspack_external_effect_Effect_41060786.die(new Error('[BFF][Effect] Request has no matching shared operation contract.'));
|
|
33
|
+
return __rspack_external_effect_Effect_41060786.gen(function*() {
|
|
34
|
+
const input = encodeJson({
|
|
35
|
+
httpMethod: contract.method,
|
|
36
|
+
name: contract.name,
|
|
37
|
+
requestId: options.requestId,
|
|
38
|
+
routePath: contract.routePath
|
|
39
|
+
});
|
|
40
|
+
const digest = yield* __rspack_external_effect_Effect_41060786.promise(()=>globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(input)));
|
|
41
|
+
const schemaHash = Array.from(new Uint8Array(digest), (value)=>value.toString(16).padStart(2, '0')).join('');
|
|
42
|
+
return request.pipe(HttpClientRequest.setHeader(BFF_ENVELOPE_HEADER, encodeJson({
|
|
43
|
+
requestId: options.requestId
|
|
44
|
+
})), HttpClientRequest.setHeader(BFF_OPERATION_CONTEXT_HEADER, contract.operationId), HttpClientRequest.setHeader(BFF_OPERATION_CONTEXT_DETAIL_HEADER, encodeJson({
|
|
45
|
+
requestId: options.requestId,
|
|
46
|
+
operationId: contract.operationId,
|
|
47
|
+
method: contract.method,
|
|
48
|
+
routePath: contract.routePath,
|
|
49
|
+
schemaHash,
|
|
50
|
+
operationVersion: options.operationVersion
|
|
51
|
+
})));
|
|
52
|
+
});
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
export { withCrossProjectPolicy };
|
|
@@ -6,6 +6,7 @@ import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/
|
|
|
6
6
|
import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "effect/unstable/httpapi";
|
|
7
7
|
import { Rpc, RpcClient, RpcGroup, RpcSchema, RpcSerialization } from "effect/unstable/rpc";
|
|
8
8
|
import { getRpcSerializationLayer } from "../effect/rpcSerialization.mjs";
|
|
9
|
+
import { withCrossProjectPolicy } from "./cross-project.mjs";
|
|
9
10
|
import * as __rspack_external_effect_Schema_6065f72d from "effect/Schema";
|
|
10
11
|
import * as __rspack_external_effect_unstable_http_HttpClientError_4b9eaee6 from "effect/unstable/http/HttpClientError";
|
|
11
12
|
import * as __rspack_external_effect_Data_2b3f4efb from "effect/Data";
|
|
@@ -47,7 +48,8 @@ function makeEffectHttpApiClient(api, options) {
|
|
|
47
48
|
for (const [header, value] of Object.entries(requestContextHeaders))if (void 0 === nextRequest.headers[header.toLowerCase()]) nextRequest = HttpClientRequest.setHeader(nextRequest, header, value);
|
|
48
49
|
return nextRequest;
|
|
49
50
|
}));
|
|
50
|
-
|
|
51
|
+
const policyClient = options?.crossProject !== void 0 ? withCrossProjectPolicy(api, options.crossProject, contextClient) : contextClient;
|
|
52
|
+
return 'function' == typeof options?.transformClient ? options.transformClient(policyClient) : policyClient;
|
|
51
53
|
};
|
|
52
54
|
return HttpApiClient.make(api, {
|
|
53
55
|
baseUrl: options?.baseUrl,
|
|
@@ -12,7 +12,7 @@ export { Cookies, Etag, FetchHttpClient, FindMyWay, Headers, HttpBody, HttpClien
|
|
|
12
12
|
export { HttpApi, HttpApiBuilder, HttpApiClient, HttpApiEndpoint, HttpApiError, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, HttpApiSecurity, HttpApiTest, OpenApi, } from 'effect/unstable/httpapi';
|
|
13
13
|
export { Rpc, RpcClient, RpcClientError, RpcGroup, RpcMessage, RpcMiddleware, RpcSchema, RpcSerialization, RpcServer, RpcTest, RpcWorker, Utils, } from 'effect/unstable/rpc';
|
|
14
14
|
export { runWithEffectContext, useEffectContext, useOperationContext, } from './edge-context.js';
|
|
15
|
-
export type { EffectApiClientFromApi,
|
|
15
|
+
export type { EffectApiClientFromApi, EffectBffDefinition, EffectBffHandlerFactory, EffectBffOpenApiConfig, EffectBffRuntime, EffectDataPlatformBatchOptions, EffectDataPlatformSelectionValidationOptions, EffectDataPlatformValidationOptions, EffectRequestValidator, EffectRpcBffDefinition, EffectRpcBffHandlerFactory, EffectRpcBffHandlerOptions, EffectRpcRuntimeLayer, EffectRpcSerialization, EffectRuntimeLayer, EffectRuntimeRequirements, } from './handler.js';
|
|
16
16
|
export { createHttpApiHandler, defineEffectBff, defineEffectRpcBff, } from './handler.js';
|
|
17
17
|
export { type CreateEffectOperationContextOptions, createEffectOperationContext, type EffectContext, } from './operation-context.js';
|
|
18
18
|
export type EffectBffEdgeDispatchOptions = Omit<DispatchEffectBffRequestOptions, 'runWithEffectContext'>;
|
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared Effect HttpApi endpoint reflection used by BOTH sides of the
|
|
3
|
-
* cross-project contract:
|
|
4
|
-
*
|
|
5
|
-
* - the `@modern-js/plugin-bff-extensions/client-generator` package stamps
|
|
6
|
-
* each generated operation with a per-endpoint contract hash;
|
|
7
|
-
* - the `@modern-js/plugin-bff-extensions/effect-adapter` package derives the
|
|
8
|
-
* expected operation-contract map for the cross-project policy from the
|
|
9
|
-
* same endpoints.
|
|
10
|
-
*
|
|
11
|
-
* Keeping route-path normalization and the endpoint -> contract mapping in
|
|
12
|
-
* one module is what guarantees the two hashes agree.
|
|
13
|
-
*/
|
|
1
|
+
/** Shared endpoint normalization and identity hashing for server policy metadata. */
|
|
14
2
|
import { type OperationContractSource } from '@modern-js/server-runtime-extensions/bff-policy/node';
|
|
15
3
|
export type EffectEndpointMeta = {
|
|
16
4
|
apiId: string;
|
|
@@ -26,13 +14,7 @@ type HttpApiGroupLike = {
|
|
|
26
14
|
identifier?: unknown;
|
|
27
15
|
};
|
|
28
16
|
type HttpApiEndpointLike = {
|
|
29
|
-
/**
|
|
30
|
-
* effect 4.0.0-beta.98 renamed the endpoint `name` property to `identifier`,
|
|
31
|
-
* matching `HttpApiGroup`. `name` is kept as a fallback so reflection over an
|
|
32
|
-
* older Effect build does not silently degrade to the class name.
|
|
33
|
-
*/
|
|
34
17
|
identifier?: unknown;
|
|
35
|
-
name?: unknown;
|
|
36
18
|
method?: unknown;
|
|
37
19
|
path?: unknown;
|
|
38
20
|
};
|