@bleedingdev/modern-js-bff-core 3.2.0-ultramodern.12 → 3.2.0-ultramodern.121
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/adapter-kit/index.js +140 -0
- package/dist/cjs/adapter-kit/parity.js +546 -0
- package/dist/cjs/api.js +9 -5
- package/dist/cjs/client/generateClient.js +74 -17
- package/dist/cjs/client/index.js +9 -5
- package/dist/cjs/client/result.js +13 -9
- package/dist/cjs/contracts/eventContracts.js +14 -10
- package/dist/cjs/errors/http.js +13 -9
- package/dist/cjs/index.js +83 -41
- package/dist/cjs/operators/http.js +9 -5
- package/dist/cjs/router/constants.js +9 -5
- package/dist/cjs/router/index.js +12 -8
- package/dist/cjs/router/utils.js +9 -5
- package/dist/cjs/security/crossProjectPolicy.js +25 -13
- package/dist/cjs/security/operationContracts.js +155 -59
- package/dist/cjs/security/resolveCrossProjectPolicy.js +65 -0
- package/dist/cjs/types.js +18 -13
- package/dist/cjs/utils/alias.js +9 -5
- package/dist/cjs/utils/debug.js +9 -5
- package/dist/cjs/utils/index.js +12 -8
- package/dist/cjs/utils/meta.js +15 -11
- package/dist/cjs/utils/storage.js +9 -5
- package/dist/cjs/utils/validate.js +9 -5
- package/dist/esm/adapter-kit/index.mjs +75 -0
- package/dist/esm/adapter-kit/parity.mjs +490 -0
- package/dist/esm/client/generateClient.mjs +66 -13
- package/dist/esm/index.mjs +2 -0
- package/dist/esm/rslib-runtime.mjs +18 -0
- package/dist/esm/security/crossProjectPolicy.mjs +10 -2
- package/dist/esm/security/operationContracts.mjs +111 -37
- package/dist/esm/security/resolveCrossProjectPolicy.mjs +27 -0
- package/dist/esm-node/adapter-kit/index.mjs +76 -0
- package/dist/esm-node/adapter-kit/parity.mjs +491 -0
- package/dist/esm-node/client/generateClient.mjs +66 -13
- package/dist/esm-node/index.mjs +2 -0
- package/dist/esm-node/rslib-runtime.mjs +19 -0
- package/dist/esm-node/security/crossProjectPolicy.mjs +10 -2
- package/dist/esm-node/security/operationContracts.mjs +111 -37
- package/dist/esm-node/security/resolveCrossProjectPolicy.mjs +28 -0
- package/dist/types/adapter-kit/index.d.ts +90 -0
- package/dist/types/adapter-kit/parity.d.ts +102 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/security/crossProjectPolicy.d.ts +40 -1
- package/dist/types/security/operationContracts.d.ts +60 -4
- package/dist/types/security/resolveCrossProjectPolicy.d.ts +48 -0
- package/package.json +12 -10
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
var __webpack_modules__ = {};
|
|
2
|
+
var __webpack_module_cache__ = {};
|
|
3
|
+
function __webpack_require__(moduleId) {
|
|
4
|
+
var cachedModule = __webpack_module_cache__[moduleId];
|
|
5
|
+
if (void 0 !== cachedModule) return cachedModule.exports;
|
|
6
|
+
var module = __webpack_module_cache__[moduleId] = {
|
|
7
|
+
exports: {}
|
|
8
|
+
};
|
|
9
|
+
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
|
|
10
|
+
return module.exports;
|
|
11
|
+
}
|
|
12
|
+
__webpack_require__.m = __webpack_modules__;
|
|
13
|
+
(()=>{
|
|
14
|
+
__webpack_require__.add = function(modules) {
|
|
15
|
+
Object.assign(__webpack_require__.m, modules);
|
|
16
|
+
};
|
|
17
|
+
})();
|
|
18
|
+
export { __webpack_require__ };
|
|
@@ -49,10 +49,18 @@ const evaluateCrossProjectPolicy = (headers, policy)=>{
|
|
|
49
49
|
}
|
|
50
50
|
const requestId = String(envelope.requestId || '').trim();
|
|
51
51
|
if (!requestId) return createViolation('missing_request_id', 'Cross-project envelope does not include a valid requestId', status);
|
|
52
|
+
const claimedNamespace = extractNamespace(requestId);
|
|
53
|
+
let effectiveNamespace = claimedNamespace;
|
|
54
|
+
if ('function' == typeof policy.verifyProducerIdentity) {
|
|
55
|
+
const verifiedNamespaceRaw = policy.verifyProducerIdentity(headers);
|
|
56
|
+
const verifiedNamespace = 'string' == typeof verifiedNamespaceRaw ? verifiedNamespaceRaw.trim().toLowerCase() : void 0;
|
|
57
|
+
if (!verifiedNamespace) return createViolation('producer_identity_mismatch', 'Producer identity could not be verified for this request', status);
|
|
58
|
+
if (verifiedNamespace !== claimedNamespace) return createViolation('producer_identity_mismatch', `Envelope namespace "${claimedNamespace || 'unknown'}" does not match verified producer identity "${verifiedNamespace}"`, status);
|
|
59
|
+
effectiveNamespace = verifiedNamespace;
|
|
60
|
+
}
|
|
52
61
|
const namespaces = (policy.allowedNamespaces || []).map((item)=>item.trim().toLowerCase()).filter(Boolean);
|
|
53
62
|
if (namespaces.length > 0) {
|
|
54
|
-
|
|
55
|
-
if (!namespace || !namespaces.includes(namespace)) return createViolation('namespace_not_allowed', `Producer namespace "${namespace || 'unknown'}" is not allowed`, status);
|
|
63
|
+
if (!effectiveNamespace || !namespaces.includes(effectiveNamespace)) return createViolation('namespace_not_allowed', `Producer namespace "${effectiveNamespace || 'unknown'}" is not allowed`, status);
|
|
56
64
|
}
|
|
57
65
|
if (requireOperationContext) {
|
|
58
66
|
const operationContext = readHeader(headers, operationContextHeader);
|
|
@@ -1,5 +1,84 @@
|
|
|
1
1
|
import { createHash } from "crypto";
|
|
2
|
+
import "reflect-metadata";
|
|
3
|
+
import { HttpMetadata } from "../types.mjs";
|
|
4
|
+
import { __webpack_require__ } from "../rslib-runtime.mjs";
|
|
5
|
+
import * as __rspack_external_zod from "zod";
|
|
6
|
+
__webpack_require__.add({
|
|
7
|
+
zod (module) {
|
|
8
|
+
module.exports = __rspack_external_zod;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
2
11
|
const DEFAULT_OPERATION_VERSION = 1;
|
|
12
|
+
const deriveOperationVersion = (packageVersion)=>{
|
|
13
|
+
if ('string' != typeof packageVersion) return DEFAULT_OPERATION_VERSION;
|
|
14
|
+
const match = packageVersion.trim().match(/^v?(\d+)\./);
|
|
15
|
+
if (!match) return DEFAULT_OPERATION_VERSION;
|
|
16
|
+
const major = Number.parseInt(match[1], 10);
|
|
17
|
+
return Number.isInteger(major) && major >= 0 ? major : DEFAULT_OPERATION_VERSION;
|
|
18
|
+
};
|
|
19
|
+
const stableStringify = (value)=>{
|
|
20
|
+
if (Array.isArray(value)) return `[${value.map((item)=>stableStringify(item)).join(',')}]`;
|
|
21
|
+
if (value && 'object' == typeof value) {
|
|
22
|
+
const entries = Object.entries(value).filter(([, entryValue])=>void 0 !== entryValue).sort(([a], [b])=>a.localeCompare(b)).map(([key, entryValue])=>`${JSON.stringify(key)}:${stableStringify(entryValue)}`);
|
|
23
|
+
return `{${entries.join(',')}}`;
|
|
24
|
+
}
|
|
25
|
+
return JSON.stringify(value) ?? 'null';
|
|
26
|
+
};
|
|
27
|
+
const sha256 = (text)=>createHash('sha256').update(text).digest('hex');
|
|
28
|
+
let cachedZodToJSONSchema;
|
|
29
|
+
const resolveZodToJSONSchema = ()=>{
|
|
30
|
+
if (void 0 !== cachedZodToJSONSchema) return cachedZodToJSONSchema;
|
|
31
|
+
try {
|
|
32
|
+
const zod = __webpack_require__("zod");
|
|
33
|
+
const candidate = zod?.toJSONSchema ?? zod?.z?.toJSONSchema;
|
|
34
|
+
cachedZodToJSONSchema = 'function' == typeof candidate ? candidate : null;
|
|
35
|
+
} catch {
|
|
36
|
+
cachedZodToJSONSchema = null;
|
|
37
|
+
}
|
|
38
|
+
return cachedZodToJSONSchema;
|
|
39
|
+
};
|
|
40
|
+
const INPUT_SCHEMA_METADATA_KEYS = [
|
|
41
|
+
HttpMetadata.Data,
|
|
42
|
+
HttpMetadata.Query,
|
|
43
|
+
HttpMetadata.Params,
|
|
44
|
+
HttpMetadata.Headers,
|
|
45
|
+
HttpMetadata.Files
|
|
46
|
+
];
|
|
47
|
+
const serializeSchema = (schema)=>{
|
|
48
|
+
const toJSONSchema = resolveZodToJSONSchema();
|
|
49
|
+
if (toJSONSchema) try {
|
|
50
|
+
return toJSONSchema(schema, {
|
|
51
|
+
io: 'input',
|
|
52
|
+
unrepresentable: 'any'
|
|
53
|
+
});
|
|
54
|
+
} catch {}
|
|
55
|
+
return {
|
|
56
|
+
__unserializableSchema: true
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
const serializeOperationSchemas = (handler)=>{
|
|
60
|
+
if ('function' != typeof handler) return;
|
|
61
|
+
const serialized = {};
|
|
62
|
+
for (const metadataKey of INPUT_SCHEMA_METADATA_KEYS){
|
|
63
|
+
let schema;
|
|
64
|
+
try {
|
|
65
|
+
schema = Reflect.getMetadata(metadataKey, handler);
|
|
66
|
+
} catch {
|
|
67
|
+
schema = void 0;
|
|
68
|
+
}
|
|
69
|
+
if (null != schema) serialized[metadataKey] = serializeSchema(schema);
|
|
70
|
+
}
|
|
71
|
+
return Object.keys(serialized).length > 0 ? serialized : void 0;
|
|
72
|
+
};
|
|
73
|
+
const createOperationContractHash = (operation, requestId)=>sha256(stableStringify({
|
|
74
|
+
httpMethod: String(operation.httpMethod || '').toUpperCase(),
|
|
75
|
+
name: operation.name,
|
|
76
|
+
requestId,
|
|
77
|
+
routePath: operation.routePath,
|
|
78
|
+
...operation.schemas ? {
|
|
79
|
+
schemas: operation.schemas
|
|
80
|
+
} : {}
|
|
81
|
+
}));
|
|
3
82
|
const createOperationEntries = (handlers)=>handlers.map((item)=>({
|
|
4
83
|
name: item.name,
|
|
5
84
|
httpMethod: String(item.httpMethod || '').toUpperCase(),
|
|
@@ -9,49 +88,44 @@ const createOperationEntries = (handlers)=>handlers.map((item)=>({
|
|
|
9
88
|
const keyB = `${b.routePath}:${b.httpMethod}:${b.name}`;
|
|
10
89
|
return keyA.localeCompare(keyB);
|
|
11
90
|
});
|
|
12
|
-
const createOperationSchemaHash = (operationEntries, requestId)=>
|
|
13
|
-
operations:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
91
|
+
const createOperationSchemaHash = (operationEntries, requestId)=>sha256(stableStringify({
|
|
92
|
+
operations: [
|
|
93
|
+
...operationEntries
|
|
94
|
+
].map((item)=>({
|
|
95
|
+
hash: item.schemaHash ?? createOperationContractHash({
|
|
96
|
+
name: item.name,
|
|
97
|
+
httpMethod: item.httpMethod,
|
|
98
|
+
routePath: item.routePath
|
|
99
|
+
}, requestId)
|
|
100
|
+
})).sort((a, b)=>a.hash.localeCompare(b.hash)),
|
|
18
101
|
requestId
|
|
19
|
-
}))
|
|
20
|
-
const buildOperationContractMap = ({ handlers, requestId })=>{
|
|
102
|
+
}));
|
|
103
|
+
const buildOperationContractMap = ({ handlers, requestId, operationVersion })=>{
|
|
21
104
|
const normalizedRequestId = 'string' == typeof requestId && requestId.trim().length > 0 ? requestId.trim() : 'default';
|
|
22
|
-
const
|
|
105
|
+
const normalizedOperationVersion = 'number' == typeof operationVersion && Number.isInteger(operationVersion) ? operationVersion : DEFAULT_OPERATION_VERSION;
|
|
106
|
+
const contracts = {};
|
|
23
107
|
handlers.forEach((item)=>{
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
group.push({
|
|
108
|
+
const httpMethod = String(item.httpMethod || '').toUpperCase();
|
|
109
|
+
const schemaHash = createOperationContractHash({
|
|
27
110
|
name: item.name,
|
|
28
|
-
httpMethod
|
|
111
|
+
httpMethod,
|
|
112
|
+
routePath: item.routePath,
|
|
113
|
+
schemas: serializeOperationSchemas(item.handler)
|
|
114
|
+
}, normalizedRequestId);
|
|
115
|
+
const operationId = `${normalizedRequestId}:${item.name}`;
|
|
116
|
+
const contract = {
|
|
117
|
+
requestId: normalizedRequestId,
|
|
118
|
+
operationVersion: normalizedOperationVersion,
|
|
119
|
+
schemaHash,
|
|
120
|
+
method: httpMethod,
|
|
29
121
|
routePath: item.routePath,
|
|
122
|
+
operationId,
|
|
123
|
+
handlerName: item.name,
|
|
30
124
|
filename: item.filename
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const contracts = {};
|
|
35
|
-
byModule.forEach((moduleEntries)=>{
|
|
36
|
-
const entries = createOperationEntries(moduleEntries);
|
|
37
|
-
const schemaHash = createOperationSchemaHash(entries, normalizedRequestId);
|
|
38
|
-
const filename = moduleEntries[0]?.filename;
|
|
39
|
-
entries.forEach((entry)=>{
|
|
40
|
-
const operationId = `${normalizedRequestId}:${entry.name}`;
|
|
41
|
-
const contract = {
|
|
42
|
-
requestId: normalizedRequestId,
|
|
43
|
-
operationVersion: DEFAULT_OPERATION_VERSION,
|
|
44
|
-
schemaHash,
|
|
45
|
-
method: entry.httpMethod,
|
|
46
|
-
routePath: entry.routePath,
|
|
47
|
-
operationId,
|
|
48
|
-
handlerName: entry.name,
|
|
49
|
-
filename
|
|
50
|
-
};
|
|
51
|
-
contracts[`${entry.httpMethod}:${entry.routePath}`] = contract;
|
|
52
|
-
contracts[`operation:${operationId}`] = contract;
|
|
53
|
-
});
|
|
125
|
+
};
|
|
126
|
+
contracts[`${httpMethod}:${item.routePath}`] = contract;
|
|
127
|
+
contracts[`operation:${operationId}`] = contract;
|
|
54
128
|
});
|
|
55
129
|
return contracts;
|
|
56
130
|
};
|
|
57
|
-
export { DEFAULT_OPERATION_VERSION, buildOperationContractMap, createOperationEntries, createOperationSchemaHash };
|
|
131
|
+
export { DEFAULT_OPERATION_VERSION, buildOperationContractMap, createOperationContractHash, createOperationEntries, createOperationSchemaHash, deriveOperationVersion, serializeOperationSchemas };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { buildOperationContractMap } from "./operationContracts.mjs";
|
|
2
|
+
const resolveCrossProjectPolicy = (input)=>{
|
|
3
|
+
const { crossProjectPolicy, handlers, requestId, isCrossProjectServer, operationVersion } = input;
|
|
4
|
+
if (!crossProjectPolicy && !isCrossProjectServer) return;
|
|
5
|
+
const policy = crossProjectPolicy ?? {};
|
|
6
|
+
const effectiveRequestId = 'string' == typeof requestId && requestId.trim().length > 0 ? requestId : 'default';
|
|
7
|
+
const generatedContracts = buildOperationContractMap({
|
|
8
|
+
handlers,
|
|
9
|
+
requestId: effectiveRequestId,
|
|
10
|
+
operationVersion
|
|
11
|
+
});
|
|
12
|
+
return {
|
|
13
|
+
...policy,
|
|
14
|
+
enabled: policy.enabled ?? Boolean(isCrossProjectServer),
|
|
15
|
+
requireEnvelope: policy.requireEnvelope ?? true,
|
|
16
|
+
requireOperationContext: policy.requireOperationContext ?? true,
|
|
17
|
+
requireOperationContextDetails: policy.requireOperationContextDetails ?? true,
|
|
18
|
+
requireOperationSchemaHash: policy.requireOperationSchemaHash ?? true,
|
|
19
|
+
requireOperationVersion: policy.requireOperationVersion ?? true,
|
|
20
|
+
allowUnknownOperations: policy.allowUnknownOperations ?? false,
|
|
21
|
+
expectedOperationContracts: {
|
|
22
|
+
...policy.expectedOperationContracts ?? {},
|
|
23
|
+
...generatedContracts
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
export { resolveCrossProjectPolicy };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import "reflect-metadata";
|
|
3
|
+
import { evaluateCrossProjectPolicy } from "../security/crossProjectPolicy.mjs";
|
|
4
|
+
import { HttpMetadata, HttpMethod } from "../types.mjs";
|
|
5
|
+
import { isInputParamsDeciderHandler, isWithMetaHandler } from "../utils/index.mjs";
|
|
6
|
+
const API_ROUTE_METHODS = {
|
|
7
|
+
[HttpMethod.Get]: 'get',
|
|
8
|
+
[HttpMethod.Post]: 'post',
|
|
9
|
+
[HttpMethod.Put]: 'put',
|
|
10
|
+
[HttpMethod.Delete]: 'delete',
|
|
11
|
+
[HttpMethod.Connect]: 'connect',
|
|
12
|
+
[HttpMethod.Trace]: 'trace',
|
|
13
|
+
[HttpMethod.Patch]: 'patch',
|
|
14
|
+
[HttpMethod.Options]: 'options',
|
|
15
|
+
[HttpMethod.Head]: 'head'
|
|
16
|
+
};
|
|
17
|
+
const toApiRouteMethod = (httpMethod)=>{
|
|
18
|
+
const method = API_ROUTE_METHODS[httpMethod];
|
|
19
|
+
if (!method) throw new Error(`[bff-core] Unsupported HTTP method "${String(httpMethod)}" in API handler info`);
|
|
20
|
+
return method;
|
|
21
|
+
};
|
|
22
|
+
const getRouteMiddlewares = (handler)=>{
|
|
23
|
+
const middlewares = Reflect.getMetadata('middleware', handler);
|
|
24
|
+
return Array.isArray(middlewares) ? middlewares : [];
|
|
25
|
+
};
|
|
26
|
+
const planApiRoutes = (handlerInfos)=>handlerInfos.map(({ routePath, handler, httpMethod })=>({
|
|
27
|
+
method: toApiRouteMethod(httpMethod),
|
|
28
|
+
routePath,
|
|
29
|
+
handler,
|
|
30
|
+
middlewares: getRouteMiddlewares(handler)
|
|
31
|
+
}));
|
|
32
|
+
const HANDLER_WITH_SCHEMA = 'HANDLER_WITH_SCHEMA';
|
|
33
|
+
const isSchemaApiHandler = (handler)=>{
|
|
34
|
+
if ('function' != typeof handler) return false;
|
|
35
|
+
const marked = handler;
|
|
36
|
+
return true === marked[HANDLER_WITH_SCHEMA];
|
|
37
|
+
};
|
|
38
|
+
const getApiHandlerMode = (handler)=>{
|
|
39
|
+
if (isWithMetaHandler(handler)) return 'meta';
|
|
40
|
+
if (isSchemaApiHandler(handler)) return 'schema';
|
|
41
|
+
if (isInputParamsDeciderHandler(handler)) return 'inputParamsDecider';
|
|
42
|
+
return 'plain';
|
|
43
|
+
};
|
|
44
|
+
const mapSchemaHandlerResult = (result)=>{
|
|
45
|
+
if ('HandleSuccess' === result.type) return {
|
|
46
|
+
success: true,
|
|
47
|
+
status: 200,
|
|
48
|
+
body: result.value
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
status: 'InputValidationError' === result.type ? 400 : 500,
|
|
53
|
+
body: result.message
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
const getResponseMetaList = (handler)=>{
|
|
57
|
+
const responseMeta = Reflect.getMetadata(HttpMetadata.Response, handler);
|
|
58
|
+
return Array.isArray(responseMeta) ? responseMeta : [];
|
|
59
|
+
};
|
|
60
|
+
const buildPositionalHandlerArgs = (input)=>[
|
|
61
|
+
...Object.values(input.params),
|
|
62
|
+
input
|
|
63
|
+
];
|
|
64
|
+
const checkCrossProjectPolicy = (headers, policy)=>{
|
|
65
|
+
const violation = evaluateCrossProjectPolicy(headers, policy);
|
|
66
|
+
if (!violation) return null;
|
|
67
|
+
return {
|
|
68
|
+
status: violation.status,
|
|
69
|
+
body: {
|
|
70
|
+
code: violation.code,
|
|
71
|
+
reason: violation.reason,
|
|
72
|
+
message: violation.message
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
export { HANDLER_WITH_SCHEMA, buildPositionalHandlerArgs, checkCrossProjectPolicy, getApiHandlerMode, getResponseMetaList, getRouteMiddlewares, isSchemaApiHandler, mapSchemaHandlerResult, planApiRoutes, toApiRouteMethod };
|