@bleedingdev/modern-js-plugin-bff 3.2.0-ultramodern.9 → 3.2.0-ultramodern.91
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/loader.js +23 -0
- package/dist/cjs/runtime/data-platform/index.js +39 -8
- package/dist/cjs/runtime/effect/adapter.js +15 -82
- package/dist/cjs/runtime/effect/context.js +10 -2
- package/dist/cjs/runtime/effect/edge.js +169 -0
- package/dist/cjs/runtime/effect/handler.js +598 -0
- package/dist/cjs/runtime/effect/index.js +16 -545
- package/dist/cjs/runtime/effect/module.js +115 -0
- package/dist/cjs/runtime/effect/operation-context.js +111 -0
- package/dist/cjs/runtime/effect-client/index.js +13 -1
- package/dist/cjs/utils/effectClientGenerator.js +17 -0
- package/dist/esm/loader.mjs +23 -0
- package/dist/esm/runtime/data-platform/index.mjs +31 -9
- package/dist/esm/runtime/effect/adapter.mjs +16 -83
- package/dist/esm/runtime/effect/context.mjs +3 -1
- package/dist/esm/runtime/effect/edge.mjs +90 -0
- package/dist/esm/runtime/effect/handler.mjs +437 -0
- package/dist/esm/runtime/effect/index.mjs +2 -438
- package/dist/esm/runtime/effect/module.mjs +81 -0
- package/dist/esm/runtime/effect/operation-context.mjs +77 -0
- package/dist/esm/runtime/effect-client/index.mjs +14 -2
- package/dist/esm/utils/effectClientGenerator.mjs +17 -0
- package/dist/esm-node/loader.mjs +23 -0
- package/dist/esm-node/runtime/data-platform/index.mjs +31 -9
- package/dist/esm-node/runtime/effect/adapter.mjs +16 -83
- package/dist/esm-node/runtime/effect/context.mjs +3 -1
- package/dist/esm-node/runtime/effect/edge.mjs +91 -0
- package/dist/esm-node/runtime/effect/handler.mjs +438 -0
- package/dist/esm-node/runtime/effect/index.mjs +2 -438
- package/dist/esm-node/runtime/effect/module.mjs +82 -0
- package/dist/esm-node/runtime/effect/operation-context.mjs +78 -0
- package/dist/esm-node/runtime/effect-client/index.mjs +14 -2
- package/dist/esm-node/utils/effectClientGenerator.mjs +17 -0
- package/dist/types/runtime/create-request/index.d.ts +1 -0
- package/dist/types/runtime/data-platform/index.d.ts +4 -0
- package/dist/types/runtime/effect/context.d.ts +3 -6
- package/dist/types/runtime/effect/edge.d.ts +25 -0
- package/dist/types/runtime/effect/handler.d.ts +170 -0
- package/dist/types/runtime/effect/index.d.ts +2 -171
- package/dist/types/runtime/effect/module.d.ts +28 -0
- package/dist/types/runtime/effect/operation-context.d.ts +10 -0
- package/dist/types/runtime/effect-client/index.d.ts +6 -1
- package/package.json +27 -18
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.d = (exports1, definition)=>{
|
|
5
|
+
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: definition[key]
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
})();
|
|
11
|
+
(()=>{
|
|
12
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
13
|
+
})();
|
|
14
|
+
(()=>{
|
|
15
|
+
__webpack_require__.r = (exports1)=>{
|
|
16
|
+
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
17
|
+
value: 'Module'
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
20
|
+
value: true
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
})();
|
|
24
|
+
var __webpack_exports__ = {};
|
|
25
|
+
__webpack_require__.r(__webpack_exports__);
|
|
26
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
+
resolveEffectBffModuleHandler: ()=>resolveEffectBffModuleHandler
|
|
28
|
+
});
|
|
29
|
+
const httpapi_namespaceObject = require("effect/unstable/httpapi");
|
|
30
|
+
const external_handler_js_namespaceObject = require("./handler.js");
|
|
31
|
+
function isRecord(value) {
|
|
32
|
+
return 'object' == typeof value && null !== value;
|
|
33
|
+
}
|
|
34
|
+
function includesRuntimeExports(value) {
|
|
35
|
+
return 'api' in value || 'layer' in value || 'createHandler' in value || 'handler' in value;
|
|
36
|
+
}
|
|
37
|
+
function isRequestHandler(value) {
|
|
38
|
+
return 'function' == typeof value;
|
|
39
|
+
}
|
|
40
|
+
function isEffectApiDefinition(module) {
|
|
41
|
+
return httpapi_namespaceObject.HttpApi.isHttpApi(module.api) && void 0 !== module.layer;
|
|
42
|
+
}
|
|
43
|
+
async function resolveEffectBffModuleHandler(mod, options = {}) {
|
|
44
|
+
let normalizedModule = mod;
|
|
45
|
+
const mergeRuntimeExports = (value)=>{
|
|
46
|
+
if (!isRecord(value) || !includesRuntimeExports(value)) return;
|
|
47
|
+
normalizedModule = {
|
|
48
|
+
...normalizedModule,
|
|
49
|
+
...value
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
if (isRequestHandler(normalizedModule.handler)) return {
|
|
53
|
+
handler: normalizedModule.handler
|
|
54
|
+
};
|
|
55
|
+
const entry = normalizedModule.default;
|
|
56
|
+
if (isRequestHandler(entry)) return {
|
|
57
|
+
handler: entry
|
|
58
|
+
};
|
|
59
|
+
if ('function' == typeof entry && 0 === entry.length) {
|
|
60
|
+
const out = await entry();
|
|
61
|
+
if (isRequestHandler(out)) return {
|
|
62
|
+
handler: out
|
|
63
|
+
};
|
|
64
|
+
mergeRuntimeExports(out);
|
|
65
|
+
}
|
|
66
|
+
if (isRecord(entry)) normalizedModule = {
|
|
67
|
+
...normalizedModule,
|
|
68
|
+
...entry
|
|
69
|
+
};
|
|
70
|
+
if (isRecord(entry) && 'handler' in entry) {
|
|
71
|
+
const maybeHandler = entry.handler;
|
|
72
|
+
if (isRequestHandler(maybeHandler)) normalizedModule = {
|
|
73
|
+
...normalizedModule,
|
|
74
|
+
handler: maybeHandler
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (isRequestHandler(normalizedModule.handler)) return {
|
|
78
|
+
handler: normalizedModule.handler
|
|
79
|
+
};
|
|
80
|
+
if ('function' == typeof normalizedModule.createHandler) {
|
|
81
|
+
const webHandler = normalizedModule.createHandler({
|
|
82
|
+
openapi: options.openapi,
|
|
83
|
+
dataPlatform: options.dataPlatform
|
|
84
|
+
});
|
|
85
|
+
return {
|
|
86
|
+
handler: async (request, context)=>webHandler.handler(request, context),
|
|
87
|
+
dispose: async ()=>{
|
|
88
|
+
await webHandler.dispose();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (isEffectApiDefinition(normalizedModule)) {
|
|
93
|
+
options.onWarning?.('[BFF][Effect] Detected { api, layer } export without createHandler. Prefer `defineEffectBff(...)` from @modern-js/plugin-bff/server to avoid module instance mismatch.');
|
|
94
|
+
const webHandler = (0, external_handler_js_namespaceObject.createHttpApiHandler)({
|
|
95
|
+
api: normalizedModule.api,
|
|
96
|
+
layer: normalizedModule.layer,
|
|
97
|
+
openapi: options.openapi,
|
|
98
|
+
dataPlatform: options.dataPlatform
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
handler: async (request, context)=>webHandler.handler(request, context),
|
|
102
|
+
dispose: async ()=>{
|
|
103
|
+
await webHandler.dispose();
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
exports.resolveEffectBffModuleHandler = __webpack_exports__.resolveEffectBffModuleHandler;
|
|
110
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
111
|
+
"resolveEffectBffModuleHandler"
|
|
112
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
113
|
+
Object.defineProperty(exports, '__esModule', {
|
|
114
|
+
value: true
|
|
115
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.d = (exports1, definition)=>{
|
|
5
|
+
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: definition[key]
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
})();
|
|
11
|
+
(()=>{
|
|
12
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
13
|
+
})();
|
|
14
|
+
(()=>{
|
|
15
|
+
__webpack_require__.r = (exports1)=>{
|
|
16
|
+
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
17
|
+
value: 'Module'
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
20
|
+
value: true
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
})();
|
|
24
|
+
var __webpack_exports__ = {};
|
|
25
|
+
__webpack_require__.r(__webpack_exports__);
|
|
26
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
+
createEffectOperationContext: ()=>createEffectOperationContext
|
|
28
|
+
});
|
|
29
|
+
const create_request_namespaceObject = require("@modern-js/create-request");
|
|
30
|
+
const TRACEPARENT_REGEX = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/i;
|
|
31
|
+
const readHeader = (headers, header)=>{
|
|
32
|
+
const value = headers.get(header);
|
|
33
|
+
return value && value.length > 0 ? value : void 0;
|
|
34
|
+
};
|
|
35
|
+
const copyStringField = (target, details, key)=>{
|
|
36
|
+
const value = details[key];
|
|
37
|
+
if ('string' == typeof value && value.length > 0) target[key] = value;
|
|
38
|
+
};
|
|
39
|
+
const parseTraceparent = (traceparent)=>{
|
|
40
|
+
if (!traceparent) return;
|
|
41
|
+
const match = traceparent.trim().match(TRACEPARENT_REGEX);
|
|
42
|
+
if (!match) return;
|
|
43
|
+
const [, traceId, spanId] = match;
|
|
44
|
+
if (!traceId || !spanId) return;
|
|
45
|
+
return {
|
|
46
|
+
traceId: traceId.toLowerCase(),
|
|
47
|
+
spanId: spanId.toLowerCase()
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
const readOperationContextDetails = (request)=>{
|
|
51
|
+
const rawDetails = readHeader(request.headers, create_request_namespaceObject.BFF_OPERATION_CONTEXT_DETAIL_HEADER);
|
|
52
|
+
if (!rawDetails) return {};
|
|
53
|
+
try {
|
|
54
|
+
const parsed = JSON.parse(rawDetails);
|
|
55
|
+
if (!parsed || 'object' != typeof parsed || Array.isArray(parsed)) return {};
|
|
56
|
+
const details = parsed;
|
|
57
|
+
const safeDetails = {};
|
|
58
|
+
copyStringField(safeDetails, details, 'requestId');
|
|
59
|
+
copyStringField(safeDetails, details, 'operationId');
|
|
60
|
+
copyStringField(safeDetails, details, 'schemaHash');
|
|
61
|
+
copyStringField(safeDetails, details, 'traceparent');
|
|
62
|
+
copyStringField(safeDetails, details, 'traceId');
|
|
63
|
+
copyStringField(safeDetails, details, 'spanId');
|
|
64
|
+
if ('number' == typeof details.operationVersion) safeDetails.operationVersion = details.operationVersion;
|
|
65
|
+
return safeDetails;
|
|
66
|
+
} catch {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const createEffectOperationContext = ({ request, path, method })=>{
|
|
71
|
+
const details = readOperationContextDetails(request);
|
|
72
|
+
const servicePath = new URL(request.url).pathname;
|
|
73
|
+
const traceparent = readHeader(request.headers, create_request_namespaceObject.BFF_TRACEPARENT_HEADER) || details.traceparent;
|
|
74
|
+
const parsedTraceparent = details.traceId && details.spanId ? {
|
|
75
|
+
traceId: details.traceId,
|
|
76
|
+
spanId: details.spanId
|
|
77
|
+
} : parseTraceparent(traceparent);
|
|
78
|
+
const locale = readHeader(request.headers, create_request_namespaceObject.BFF_LOCALE_HEADER);
|
|
79
|
+
const headerOperationId = readHeader(request.headers, create_request_namespaceObject.BFF_OPERATION_CONTEXT_HEADER);
|
|
80
|
+
return {
|
|
81
|
+
...details,
|
|
82
|
+
...headerOperationId || details.operationId ? {
|
|
83
|
+
operationId: headerOperationId || details.operationId
|
|
84
|
+
} : {},
|
|
85
|
+
routePath: servicePath,
|
|
86
|
+
method: (method || request.method || 'GET').toUpperCase(),
|
|
87
|
+
source: 'effect-adapter',
|
|
88
|
+
...path && path !== servicePath ? {
|
|
89
|
+
attributes: {
|
|
90
|
+
mountedPath: path
|
|
91
|
+
}
|
|
92
|
+
} : {},
|
|
93
|
+
...locale ? {
|
|
94
|
+
locale
|
|
95
|
+
} : {},
|
|
96
|
+
...traceparent ? {
|
|
97
|
+
traceparent
|
|
98
|
+
} : {},
|
|
99
|
+
...parsedTraceparent ? {
|
|
100
|
+
traceId: parsedTraceparent.traceId,
|
|
101
|
+
spanId: parsedTraceparent.spanId
|
|
102
|
+
} : {}
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
exports.createEffectOperationContext = __webpack_exports__.createEffectOperationContext;
|
|
106
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
107
|
+
"createEffectOperationContext"
|
|
108
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
109
|
+
Object.defineProperty(exports, '__esModule', {
|
|
110
|
+
value: true
|
|
111
|
+
});
|
|
@@ -45,6 +45,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
45
45
|
runEffectView: ()=>runEffectView,
|
|
46
46
|
view: ()=>view
|
|
47
47
|
});
|
|
48
|
+
const create_request_namespaceObject = require("@modern-js/create-request");
|
|
48
49
|
const Data_namespaceObject = require("effect/Data");
|
|
49
50
|
const Effect_namespaceObject = require("effect/Effect");
|
|
50
51
|
const Exit_namespaceObject = require("effect/Exit");
|
|
@@ -94,8 +95,19 @@ function getRpcSerializationLayer(serialization) {
|
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
97
|
function makeEffectHttpApiClient(api, options) {
|
|
98
|
+
const requestContextHeaders = (0, create_request_namespaceObject.createRequestContextHeaders)(options?.requestContext);
|
|
99
|
+
const transformClient = (client)=>{
|
|
100
|
+
const contextClient = 0 === Object.keys(requestContextHeaders).length ? client : client.pipe(http_namespaceObject.HttpClient.mapRequest((request)=>{
|
|
101
|
+
let nextRequest = request;
|
|
102
|
+
for (const [header, value] of Object.entries(requestContextHeaders))if (void 0 === nextRequest.headers[header.toLowerCase()]) nextRequest = http_namespaceObject.HttpClientRequest.setHeader(nextRequest, header, value);
|
|
103
|
+
return nextRequest;
|
|
104
|
+
}));
|
|
105
|
+
return options?.transformClient ? options.transformClient(contextClient) : contextClient;
|
|
106
|
+
};
|
|
97
107
|
return httpapi_namespaceObject.HttpApiClient.make(api, {
|
|
98
|
-
baseUrl: options?.baseUrl
|
|
108
|
+
baseUrl: options?.baseUrl,
|
|
109
|
+
transformClient,
|
|
110
|
+
transformResponse: options?.transformResponse
|
|
99
111
|
}).pipe(Effect_namespaceObject.provide(http_namespaceObject.FetchHttpClient.layer));
|
|
100
112
|
}
|
|
101
113
|
function makeEffectRpcClient(group, options) {
|
|
@@ -621,9 +621,26 @@ export type EffectOperationManifest = Record<
|
|
|
621
621
|
string,
|
|
622
622
|
Record<string, EffectOperationDescriptor>
|
|
623
623
|
>;
|
|
624
|
+
export type EffectOperationContext = {
|
|
625
|
+
requestId?: string;
|
|
626
|
+
operationId?: string;
|
|
627
|
+
routePath?: string;
|
|
628
|
+
method?: string;
|
|
629
|
+
schemaHash?: string;
|
|
630
|
+
operationVersion?: number;
|
|
631
|
+
locale?: string;
|
|
632
|
+
traceparent?: string;
|
|
633
|
+
traceId?: string;
|
|
634
|
+
spanId?: string;
|
|
635
|
+
source?: string;
|
|
636
|
+
scope?: Record<string, unknown>;
|
|
637
|
+
sessionClaims?: Record<string, unknown>;
|
|
638
|
+
attributes?: Record<string, unknown>;
|
|
639
|
+
};
|
|
624
640
|
export type EffectRequestContext = {
|
|
625
641
|
headers?: Record<string, string>;
|
|
626
642
|
locale?: string;
|
|
643
|
+
operationContext?: EffectOperationContext;
|
|
627
644
|
traceparent?: string;
|
|
628
645
|
traceId?: string;
|
|
629
646
|
spanId?: string;
|
package/dist/esm/loader.mjs
CHANGED
|
@@ -2,6 +2,24 @@ import { generateClient } from "@modern-js/bff-core";
|
|
|
2
2
|
import { logger } from "@modern-js/utils";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { generateEffectClientCode, resolveEffectEntryFile } from "./utils/effectClientGenerator.mjs";
|
|
5
|
+
async function transformEffectRuntimeSource(source, filename) {
|
|
6
|
+
const swc = await import("@swc/core");
|
|
7
|
+
const result = await swc.transform(source, {
|
|
8
|
+
filename,
|
|
9
|
+
sourceMaps: false,
|
|
10
|
+
jsc: {
|
|
11
|
+
parser: {
|
|
12
|
+
syntax: "typescript",
|
|
13
|
+
tsx: filename.endsWith('.tsx') || filename.endsWith('.jsx')
|
|
14
|
+
},
|
|
15
|
+
target: 'es2022'
|
|
16
|
+
},
|
|
17
|
+
module: {
|
|
18
|
+
type: 'es6'
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
return result.code;
|
|
22
|
+
}
|
|
5
23
|
async function loader(source) {
|
|
6
24
|
this.cacheable();
|
|
7
25
|
const { resourcePath } = this;
|
|
@@ -13,6 +31,11 @@ async function loader(source) {
|
|
|
13
31
|
apiDir: draftOptions.apiDir,
|
|
14
32
|
effectEntry: draftOptions.effectEntry
|
|
15
33
|
});
|
|
34
|
+
if ('effect' === draftOptions.bffRuntimeFramework && effectEntryFile && path.resolve(effectEntryFile) === path.resolve(resourcePath) && this.resourceQuery.includes('modern-bff-runtime')) {
|
|
35
|
+
const code = await transformEffectRuntimeSource(source, resourcePath);
|
|
36
|
+
callback(void 0, code);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
16
39
|
if ('effect' === draftOptions.bffRuntimeFramework && effectEntryFile && path.resolve(effectEntryFile) === path.resolve(resourcePath)) {
|
|
17
40
|
const code = await generateEffectClientCode({
|
|
18
41
|
appDir: draftOptions.appDir,
|
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
import { trace as api_trace } from "@opentelemetry/api";
|
|
2
|
+
const DATA_BATCH_TRANSPORT_OTEL_EVENT = 'modernjs.data.batch';
|
|
3
|
+
function createDataBatchTransportTelemetryAttributes(event) {
|
|
4
|
+
return {
|
|
5
|
+
'modernjs.data.batch.type': event.type,
|
|
6
|
+
'modernjs.data.batch.endpoint': event.endpoint,
|
|
7
|
+
'modernjs.data.batch.degraded': 'fallback' === event.type || 'disable' === event.type,
|
|
8
|
+
...event.batchId ? {
|
|
9
|
+
'modernjs.data.batch.id': event.batchId
|
|
10
|
+
} : {},
|
|
11
|
+
...'number' == typeof event.size ? {
|
|
12
|
+
'modernjs.data.batch.size': event.size
|
|
13
|
+
} : {},
|
|
14
|
+
...event.reason ? {
|
|
15
|
+
'modernjs.data.batch.reason': event.reason
|
|
16
|
+
} : {}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function emitDataBatchTransportEvent(onEvent, event) {
|
|
20
|
+
onEvent?.(event);
|
|
21
|
+
api_trace.getActiveSpan()?.addEvent(DATA_BATCH_TRANSPORT_OTEL_EVENT, createDataBatchTransportTelemetryAttributes(event));
|
|
22
|
+
}
|
|
1
23
|
const DEFAULT_DATA_ENVELOPE_HEADER = 'x-modernjs-data-envelope';
|
|
2
24
|
const DEFAULT_DATA_BATCH_ENDPOINT = '/_data/batch';
|
|
3
25
|
const DEFAULT_DATA_BATCH_HEADER = 'x-modernjs-data-batch';
|
|
@@ -415,7 +437,7 @@ function createDataBatchTransport(options = {}) {
|
|
|
415
437
|
bucket.items = [];
|
|
416
438
|
bucket.bytes = 0;
|
|
417
439
|
if (1 === items.length || disabledEndpoints.has(endpoint)) {
|
|
418
|
-
onEvent
|
|
440
|
+
emitDataBatchTransportEvent(onEvent, {
|
|
419
441
|
type: disabledEndpoints.has(endpoint) ? 'fallback' : 'flush',
|
|
420
442
|
endpoint,
|
|
421
443
|
size: items.length,
|
|
@@ -432,7 +454,7 @@ function createDataBatchTransport(options = {}) {
|
|
|
432
454
|
sentAt: Date.now(),
|
|
433
455
|
items: items.map((item)=>item.item)
|
|
434
456
|
};
|
|
435
|
-
onEvent
|
|
457
|
+
emitDataBatchTransportEvent(onEvent, {
|
|
436
458
|
type: 'flush',
|
|
437
459
|
endpoint,
|
|
438
460
|
batchId,
|
|
@@ -459,7 +481,7 @@ function createDataBatchTransport(options = {}) {
|
|
|
459
481
|
requestInit.signal = controller.signal;
|
|
460
482
|
timeoutHandle = setTimeout(()=>{
|
|
461
483
|
controller.abort();
|
|
462
|
-
onEvent
|
|
484
|
+
emitDataBatchTransportEvent(onEvent, {
|
|
463
485
|
type: 'fallback',
|
|
464
486
|
endpoint,
|
|
465
487
|
batchId,
|
|
@@ -472,13 +494,13 @@ function createDataBatchTransport(options = {}) {
|
|
|
472
494
|
if (!response.ok) {
|
|
473
495
|
if (404 === response.status || 405 === response.status) {
|
|
474
496
|
disabledEndpoints.add(endpoint);
|
|
475
|
-
onEvent
|
|
497
|
+
emitDataBatchTransportEvent(onEvent, {
|
|
476
498
|
type: 'disable',
|
|
477
499
|
endpoint,
|
|
478
500
|
batchId,
|
|
479
501
|
reason: `batch-endpoint-unavailable-${String(response.status)}`
|
|
480
502
|
});
|
|
481
|
-
} else onEvent
|
|
503
|
+
} else emitDataBatchTransportEvent(onEvent, {
|
|
482
504
|
type: 'fallback',
|
|
483
505
|
endpoint,
|
|
484
506
|
batchId,
|
|
@@ -491,7 +513,7 @@ function createDataBatchTransport(options = {}) {
|
|
|
491
513
|
}
|
|
492
514
|
const result = await response.json();
|
|
493
515
|
if (!isBatchResponsePayload(result)) {
|
|
494
|
-
onEvent
|
|
516
|
+
emitDataBatchTransportEvent(onEvent, {
|
|
495
517
|
type: 'fallback',
|
|
496
518
|
endpoint,
|
|
497
519
|
batchId,
|
|
@@ -514,7 +536,7 @@ function createDataBatchTransport(options = {}) {
|
|
|
514
536
|
return parseResponseLikeCreateRequest(reconstructedResponse);
|
|
515
537
|
});
|
|
516
538
|
} catch (error) {
|
|
517
|
-
onEvent
|
|
539
|
+
emitDataBatchTransportEvent(onEvent, {
|
|
518
540
|
type: 'fallback',
|
|
519
541
|
endpoint,
|
|
520
542
|
batchId,
|
|
@@ -581,7 +603,7 @@ function createDataBatchTransport(options = {}) {
|
|
|
581
603
|
};
|
|
582
604
|
bucket.items.push(queued);
|
|
583
605
|
bucket.bytes += size;
|
|
584
|
-
onEvent
|
|
606
|
+
emitDataBatchTransportEvent(onEvent, {
|
|
585
607
|
type: 'enqueue',
|
|
586
608
|
endpoint,
|
|
587
609
|
size: bucket.items.length
|
|
@@ -596,4 +618,4 @@ function createDataBatchTransport(options = {}) {
|
|
|
596
618
|
return promise;
|
|
597
619
|
};
|
|
598
620
|
}
|
|
599
|
-
export { DEFAULT_DATA_BATCH_ENDPOINT, DEFAULT_DATA_BATCH_HEADER, DEFAULT_DATA_ENVELOPE_HEADER, buildQueryKey, buildScopeKey, createDataBatchTransport, createHydrationEnvelope, createInvalidationEvent, createOperationId, createRequestEnvelope, decodeRequestEnvelopeHeader, deriveChildTraceContext, encodeRequestEnvelopeHeader, formatTraceparentHeader, normalizeOrigin, parseTraceparentHeader, shouldApplyInvalidation, stableStringify, validateHydrationEnvelope, validateRequestEnvelope, validateSelectionPlan };
|
|
621
|
+
export { DATA_BATCH_TRANSPORT_OTEL_EVENT, DEFAULT_DATA_BATCH_ENDPOINT, DEFAULT_DATA_BATCH_HEADER, DEFAULT_DATA_ENVELOPE_HEADER, buildQueryKey, buildScopeKey, createDataBatchTransport, createDataBatchTransportTelemetryAttributes, createHydrationEnvelope, createInvalidationEvent, createOperationId, createRequestEnvelope, decodeRequestEnvelopeHeader, deriveChildTraceContext, emitDataBatchTransportEvent, encodeRequestEnvelopeHeader, formatTraceparentHeader, normalizeOrigin, parseTraceparentHeader, shouldApplyInvalidation, stableStringify, validateHydrationEnvelope, validateRequestEnvelope, validateSelectionPlan };
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { API_DIR, compatibleRequire, findExists, fs, isProd, logger } from "@modern-js/utils";
|
|
2
|
-
import { HttpApi } from "effect/unstable/httpapi";
|
|
3
2
|
import path from "path";
|
|
4
|
-
import { runWithEffectContext } from "./context.mjs";
|
|
5
|
-
import {
|
|
3
|
+
import { createEffectOperationContext, runWithEffectContext } from "./context.mjs";
|
|
4
|
+
import { resolveEffectBffModuleHandler } from "./module.mjs";
|
|
6
5
|
const before = [
|
|
7
6
|
'custom-server-hook',
|
|
8
7
|
'custom-server-middleware',
|
|
@@ -35,24 +34,9 @@ function createRequestForMountedPrefix(req, prefix) {
|
|
|
35
34
|
url.pathname = nextPath;
|
|
36
35
|
return new Request(url, req);
|
|
37
36
|
}
|
|
38
|
-
function isRequestHandler(value) {
|
|
39
|
-
return 'function' == typeof value;
|
|
40
|
-
}
|
|
41
37
|
function maybeResponse(value) {
|
|
42
38
|
return value instanceof Response;
|
|
43
39
|
}
|
|
44
|
-
function isRecord(value) {
|
|
45
|
-
return 'object' == typeof value && null !== value;
|
|
46
|
-
}
|
|
47
|
-
function includesRuntimeExports(value) {
|
|
48
|
-
return 'api' in value || 'layer' in value || 'createHandler' in value || 'handler' in value;
|
|
49
|
-
}
|
|
50
|
-
function isHttpApiWithProps(value) {
|
|
51
|
-
return HttpApi.isHttpApi(value) && isRecord(value) && 'string' == typeof value.identifier && isRecord(value.groups);
|
|
52
|
-
}
|
|
53
|
-
function isEffectApiDefinition(module) {
|
|
54
|
-
return isHttpApiWithProps(module.api) && void 0 !== module.layer;
|
|
55
|
-
}
|
|
56
40
|
class EffectAdapter {
|
|
57
41
|
resolveEntryFile() {
|
|
58
42
|
const { appDirectory, apiDirectory } = this.api.getServerContext();
|
|
@@ -63,70 +47,13 @@ class EffectAdapter {
|
|
|
63
47
|
return findExists(JS_OR_TS_EXTS.map((ext)=>`${entryWithoutExt}${ext}`));
|
|
64
48
|
}
|
|
65
49
|
async loadEffectHandlerFromModule(mod) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
};
|
|
74
|
-
if (isRequestHandler(normalizedModule.handler)) return {
|
|
75
|
-
handler: normalizedModule.handler
|
|
76
|
-
};
|
|
77
|
-
const entry = normalizedModule.default;
|
|
78
|
-
if (isRequestHandler(entry)) return {
|
|
79
|
-
handler: entry
|
|
80
|
-
};
|
|
81
|
-
if ('function' == typeof entry && 0 === entry.length) {
|
|
82
|
-
const out = await entry();
|
|
83
|
-
if (isRequestHandler(out)) return {
|
|
84
|
-
handler: out
|
|
85
|
-
};
|
|
86
|
-
mergeRuntimeExports(out);
|
|
87
|
-
}
|
|
88
|
-
if (isRecord(entry)) normalizedModule = {
|
|
89
|
-
...normalizedModule,
|
|
90
|
-
...entry
|
|
91
|
-
};
|
|
92
|
-
if (isRecord(entry) && 'handler' in entry) {
|
|
93
|
-
const maybeHandler = entry.handler;
|
|
94
|
-
if (isRequestHandler(maybeHandler)) normalizedModule = {
|
|
95
|
-
...normalizedModule,
|
|
96
|
-
handler: maybeHandler
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
if (isRequestHandler(normalizedModule.handler)) return {
|
|
100
|
-
handler: normalizedModule.handler
|
|
101
|
-
};
|
|
102
|
-
if ('function' == typeof normalizedModule.createHandler) {
|
|
103
|
-
const webHandler = normalizedModule.createHandler({
|
|
104
|
-
openapi: this.api.getServerConfig()?.bff?.effect?.openapi,
|
|
105
|
-
dataPlatform: this.api.getServerConfig()?.bff?.effect?.dataPlatform
|
|
106
|
-
});
|
|
107
|
-
return {
|
|
108
|
-
handler: async (request)=>webHandler.handler(request),
|
|
109
|
-
dispose: async ()=>{
|
|
110
|
-
await webHandler.dispose();
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
if (isEffectApiDefinition(normalizedModule)) {
|
|
115
|
-
logger.warn('[BFF][Effect] Detected { api, layer } export without createHandler. Prefer `defineEffectBff(...)` from @modern-js/plugin-bff/server to avoid module instance mismatch.');
|
|
116
|
-
const webHandler = createHttpApiHandler({
|
|
117
|
-
api: normalizedModule.api,
|
|
118
|
-
layer: normalizedModule.layer,
|
|
119
|
-
openapi: this.api.getServerConfig()?.bff?.effect?.openapi,
|
|
120
|
-
dataPlatform: this.api.getServerConfig()?.bff?.effect?.dataPlatform
|
|
121
|
-
});
|
|
122
|
-
return {
|
|
123
|
-
handler: async (request)=>webHandler.handler(request),
|
|
124
|
-
dispose: async ()=>{
|
|
125
|
-
await webHandler.dispose();
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
return null;
|
|
50
|
+
return resolveEffectBffModuleHandler(mod, {
|
|
51
|
+
openapi: this.api.getServerConfig()?.bff?.effect?.openapi,
|
|
52
|
+
dataPlatform: this.api.getServerConfig()?.bff?.effect?.dataPlatform,
|
|
53
|
+
onWarning: (message)=>{
|
|
54
|
+
logger.warn(message);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
130
57
|
}
|
|
131
58
|
async reloadHandler() {
|
|
132
59
|
if (!this.isEffect) return;
|
|
@@ -244,7 +171,13 @@ class EffectAdapter {
|
|
|
244
171
|
request: effectRequest,
|
|
245
172
|
env: c.env,
|
|
246
173
|
path: c.req.path,
|
|
247
|
-
method: c.req.method
|
|
174
|
+
method: c.req.method,
|
|
175
|
+
operationContext: createEffectOperationContext({
|
|
176
|
+
request: effectRequest,
|
|
177
|
+
env: c.env,
|
|
178
|
+
path: c.req.path,
|
|
179
|
+
method: c.req.method
|
|
180
|
+
})
|
|
248
181
|
};
|
|
249
182
|
response = await runWithEffectContext(effectContext, ()=>this.handler.length > 1 ? this.handler(effectRequest, effectContext) : this.handler(effectRequest));
|
|
250
183
|
} catch (error) {
|
|
@@ -8,4 +8,6 @@ const useEffectContext = ()=>{
|
|
|
8
8
|
if (!context) throw new Error("Can't call useEffectContext out of Effect runtime scope");
|
|
9
9
|
return context;
|
|
10
10
|
};
|
|
11
|
-
|
|
11
|
+
const useOperationContext = ()=>useEffectContext().operationContext;
|
|
12
|
+
export { createEffectOperationContext } from "./operation-context.mjs";
|
|
13
|
+
export { runWithEffectContext, useEffectContext, useOperationContext };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { resolveEffectBffModuleHandler } from "./module.mjs";
|
|
2
|
+
import { createEffectOperationContext } from "./operation-context.mjs";
|
|
3
|
+
export * from "./handler.mjs";
|
|
4
|
+
function normalizePrefix(prefix) {
|
|
5
|
+
if (!prefix || '/' === prefix) return '';
|
|
6
|
+
return prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
|
|
7
|
+
}
|
|
8
|
+
function removePrefixFromPath(pathname, prefix) {
|
|
9
|
+
const normalized = normalizePrefix(prefix);
|
|
10
|
+
if (!normalized || pathname !== normalized && !pathname.startsWith(`${normalized}/`)) return pathname;
|
|
11
|
+
const sliced = pathname.slice(normalized.length);
|
|
12
|
+
return sliced.startsWith('/') ? sliced : `/${sliced}`;
|
|
13
|
+
}
|
|
14
|
+
function matchesPrefix(pathname, prefix) {
|
|
15
|
+
const normalized = normalizePrefix(prefix);
|
|
16
|
+
return !normalized || pathname === normalized || pathname.startsWith(`${normalized}/`);
|
|
17
|
+
}
|
|
18
|
+
function createRequestForMountedPrefix(req, prefix) {
|
|
19
|
+
const url = new URL(req.url);
|
|
20
|
+
const nextPath = removePrefixFromPath(url.pathname, prefix);
|
|
21
|
+
if (nextPath === url.pathname) return req;
|
|
22
|
+
url.pathname = nextPath;
|
|
23
|
+
return new Request(url, req);
|
|
24
|
+
}
|
|
25
|
+
function createEdgeEffectContext(originalRequest, effectRequest, options) {
|
|
26
|
+
const originalPath = options.path || new URL(originalRequest.url).pathname;
|
|
27
|
+
const method = options.method || originalRequest.method;
|
|
28
|
+
return {
|
|
29
|
+
request: effectRequest,
|
|
30
|
+
env: options.env || {},
|
|
31
|
+
path: originalPath,
|
|
32
|
+
method,
|
|
33
|
+
operationContext: createEffectOperationContext({
|
|
34
|
+
request: effectRequest,
|
|
35
|
+
env: options.env || {},
|
|
36
|
+
path: originalPath,
|
|
37
|
+
method
|
|
38
|
+
})
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function createRuntimeErrorResponse(error) {
|
|
42
|
+
const status = 'object' == typeof error && null !== error && 'status' in error && 'number' == typeof error.status ? error.status : 500;
|
|
43
|
+
return new Response(JSON.stringify({
|
|
44
|
+
message: error instanceof Error ? error.message : '[BFF] Internal Server Error'
|
|
45
|
+
}), {
|
|
46
|
+
status,
|
|
47
|
+
headers: {
|
|
48
|
+
'content-type': 'application/json; charset=utf-8'
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function dispatchEffectBffRequest(handler, request, options = {}) {
|
|
53
|
+
const requestPathname = new URL(request.url).pathname;
|
|
54
|
+
if (!matchesPrefix(requestPathname, options.prefix)) return new Response(null, {
|
|
55
|
+
status: 404
|
|
56
|
+
});
|
|
57
|
+
const effectRequest = createRequestForMountedPrefix(request, options.prefix);
|
|
58
|
+
const effectContext = createEdgeEffectContext(request, effectRequest, options);
|
|
59
|
+
try {
|
|
60
|
+
const response = handler.length > 1 ? await handler(effectRequest, effectContext) : await handler(effectRequest);
|
|
61
|
+
if (!(response instanceof Response)) throw new Error('[BFF][Effect] Effect handler must return a Response instance.');
|
|
62
|
+
return new Response(response.body, response);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (error instanceof Response) return new Response(error.body, error);
|
|
65
|
+
if (options.onError) {
|
|
66
|
+
const errorResponse = await options.onError(error, effectContext);
|
|
67
|
+
if (errorResponse instanceof Response) return errorResponse;
|
|
68
|
+
}
|
|
69
|
+
return createRuntimeErrorResponse(error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function createEffectBffEdgeHandler(options) {
|
|
73
|
+
const loaded = await resolveEffectBffModuleHandler(options.module, {
|
|
74
|
+
openapi: options.openapi,
|
|
75
|
+
dataPlatform: options.dataPlatform,
|
|
76
|
+
onWarning: options.onWarning
|
|
77
|
+
});
|
|
78
|
+
if (!loaded) throw new Error('[BFF][Effect] Invalid Effect edge module. Export { api, layer }, createHandler, or handler.');
|
|
79
|
+
return {
|
|
80
|
+
handler: (request, dispatchOptions = {})=>dispatchEffectBffRequest(loaded.handler, request, {
|
|
81
|
+
...dispatchOptions,
|
|
82
|
+
prefix: options.prefix,
|
|
83
|
+
onError: options.onError
|
|
84
|
+
}),
|
|
85
|
+
dispose: async ()=>{
|
|
86
|
+
await loaded.dispose?.();
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export { createEffectBffEdgeHandler, createEffectOperationContext, dispatchEffectBffRequest };
|