@bleedingdev/modern-js-bff-core 3.9.0-ultramodern.2 → 3.9.0-ultramodern.20
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/client/generateClient.js +67 -138
- package/dist/cjs/index.js +24 -114
- package/dist/cjs/router/index.js +7 -5
- package/dist/cjs/utils/index.js +11 -21
- package/dist/esm/client/generateClient.mjs +65 -139
- package/dist/esm/index.mjs +1 -5
- package/dist/esm/router/index.mjs +7 -5
- package/dist/esm/utils/index.mjs +0 -1
- package/dist/esm-node/client/generateClient.mjs +65 -139
- package/dist/esm-node/index.mjs +1 -5
- package/dist/esm-node/router/index.mjs +7 -5
- package/dist/esm-node/utils/index.mjs +0 -1
- package/dist/types/client/generateClient.d.ts +24 -1
- package/dist/types/index.d.ts +1 -5
- package/dist/types/utils/index.d.ts +0 -1
- package/package.json +10 -19
- package/dist/cjs/adapter-kit/index.js +0 -144
- package/dist/cjs/adapter-kit/parity-scenarios/cross-project-denial.js +0 -55
- package/dist/cjs/adapter-kit/parity-scenarios/envelope.js +0 -128
- package/dist/cjs/adapter-kit/parity-scenarios/operation-context.js +0 -152
- package/dist/cjs/adapter-kit/parity-scenarios/schema.js +0 -109
- package/dist/cjs/adapter-kit/parity-scenarios/shared.js +0 -71
- package/dist/cjs/adapter-kit/parity.js +0 -204
- package/dist/cjs/security/crossProjectPolicy.js +0 -309
- package/dist/cjs/security/operationContracts.js +0 -183
- package/dist/cjs/security/resolveCrossProjectPolicy.js +0 -65
- package/dist/cjs/utils/alias.js +0 -112
- package/dist/esm/adapter-kit/index.mjs +0 -79
- package/dist/esm/adapter-kit/parity-scenarios/cross-project-denial.mjs +0 -17
- package/dist/esm/adapter-kit/parity-scenarios/envelope.mjs +0 -90
- package/dist/esm/adapter-kit/parity-scenarios/operation-context.mjs +0 -111
- package/dist/esm/adapter-kit/parity-scenarios/schema.mjs +0 -71
- package/dist/esm/adapter-kit/parity-scenarios/shared.mjs +0 -21
- package/dist/esm/adapter-kit/parity.mjs +0 -148
- package/dist/esm/security/crossProjectPolicy.mjs +0 -259
- package/dist/esm/security/operationContracts.mjs +0 -127
- package/dist/esm/security/resolveCrossProjectPolicy.mjs +0 -27
- package/dist/esm/utils/alias.mjs +0 -57
- package/dist/esm-node/adapter-kit/index.mjs +0 -80
- package/dist/esm-node/adapter-kit/parity-scenarios/cross-project-denial.mjs +0 -18
- package/dist/esm-node/adapter-kit/parity-scenarios/envelope.mjs +0 -91
- package/dist/esm-node/adapter-kit/parity-scenarios/operation-context.mjs +0 -112
- package/dist/esm-node/adapter-kit/parity-scenarios/schema.mjs +0 -72
- package/dist/esm-node/adapter-kit/parity-scenarios/shared.mjs +0 -22
- package/dist/esm-node/adapter-kit/parity.mjs +0 -149
- package/dist/esm-node/security/crossProjectPolicy.mjs +0 -260
- package/dist/esm-node/security/operationContracts.mjs +0 -130
- package/dist/esm-node/security/resolveCrossProjectPolicy.mjs +0 -28
- package/dist/esm-node/utils/alias.mjs +0 -58
- package/dist/types/adapter-kit/index.d.ts +0 -90
- package/dist/types/adapter-kit/parity-scenarios/cross-project-denial.d.ts +0 -2
- package/dist/types/adapter-kit/parity-scenarios/envelope.d.ts +0 -2
- package/dist/types/adapter-kit/parity-scenarios/operation-context.d.ts +0 -3
- package/dist/types/adapter-kit/parity-scenarios/schema.d.ts +0 -2
- package/dist/types/adapter-kit/parity-scenarios/shared.d.ts +0 -32
- package/dist/types/adapter-kit/parity.d.ts +0 -43
- package/dist/types/security/crossProjectPolicy.d.ts +0 -110
- package/dist/types/security/operationContracts.d.ts +0 -84
- package/dist/types/security/resolveCrossProjectPolicy.d.ts +0 -39
- package/dist/types/utils/alias.d.ts +0 -7
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
import "node:module";
|
|
2
|
-
const BFF_ENVELOPE_HEADER = 'x-modernjs-bff-envelope';
|
|
3
|
-
const BFF_OPERATION_CONTEXT_HEADER = 'x-operation-id';
|
|
4
|
-
const BFF_OPERATION_CONTEXT_DETAIL_HEADER = 'x-modernjs-bff-operation-context';
|
|
5
|
-
const DEFAULT_DENY_STATUS = 403;
|
|
6
|
-
const NAMESPACE_ALLOWLIST_REQUIRES_VERIFIER_MESSAGE = 'cross-project namespace allowlist requires verifyProducerIdentity';
|
|
7
|
-
let hasWarnedAdvisoryNamespaceAllowlist = false;
|
|
8
|
-
const splitRoutePath = (routePath)=>routePath.split('/').filter(Boolean);
|
|
9
|
-
const isDynamicRouteSegment = (segment)=>segment.startsWith(':') || '*' === segment || segment.startsWith('*') || segment.startsWith('{*');
|
|
10
|
-
const hasTrailingSlash = (routePath)=>routePath.length > 1 && routePath.endsWith('/');
|
|
11
|
-
const hasUnsafePathEncoding = (requestPath)=>{
|
|
12
|
-
let decoded = requestPath;
|
|
13
|
-
for(let depth = 0; depth < 8; depth += 1){
|
|
14
|
-
if (/%(?:2f|5c)/i.test(decoded)) return true;
|
|
15
|
-
try {
|
|
16
|
-
const next = decodeURIComponent(decoded);
|
|
17
|
-
if (next === decoded) return false;
|
|
18
|
-
decoded = next;
|
|
19
|
-
} catch {
|
|
20
|
-
return true;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return decoded.includes('%');
|
|
24
|
-
};
|
|
25
|
-
const routeSegmentsMatch = (templateSegments, requestSegments)=>{
|
|
26
|
-
let templateIndex = 0;
|
|
27
|
-
let requestIndex = 0;
|
|
28
|
-
while(templateIndex < templateSegments.length){
|
|
29
|
-
const segment = templateSegments[templateIndex];
|
|
30
|
-
const wildcard = '*' === segment || segment.startsWith('*') || segment.startsWith('{*');
|
|
31
|
-
const optional = segment.startsWith(':') && segment.endsWith('?');
|
|
32
|
-
if (templateIndex < templateSegments.length - 1 && (wildcard || optional)) return false;
|
|
33
|
-
if (wildcard) return true;
|
|
34
|
-
const requestSegment = requestSegments[requestIndex];
|
|
35
|
-
if (void 0 === requestSegment) return optional && templateIndex === templateSegments.length - 1;
|
|
36
|
-
if (!segment.startsWith(':') && segment !== requestSegment) return false;
|
|
37
|
-
templateIndex += 1;
|
|
38
|
-
requestIndex += 1;
|
|
39
|
-
}
|
|
40
|
-
return requestIndex === requestSegments.length;
|
|
41
|
-
};
|
|
42
|
-
const routeTemplateMatchScore = (routePath, requestPath)=>{
|
|
43
|
-
if (hasTrailingSlash(routePath) !== hasTrailingSlash(requestPath)) return;
|
|
44
|
-
const templateSegments = splitRoutePath(routePath);
|
|
45
|
-
const requestSegments = splitRoutePath(requestPath);
|
|
46
|
-
for(let prefixLength = 0; prefixLength <= templateSegments.length; prefixLength += 1){
|
|
47
|
-
const removedPrefix = templateSegments.slice(0, prefixLength);
|
|
48
|
-
if (removedPrefix.some(isDynamicRouteSegment)) break;
|
|
49
|
-
const mountedTemplate = templateSegments.slice(prefixLength);
|
|
50
|
-
if (!routeSegmentsMatch(mountedTemplate, requestSegments)) continue;
|
|
51
|
-
const literalSegments = mountedTemplate.filter((segment)=>!isDynamicRouteSegment(segment)).length;
|
|
52
|
-
const dynamicSegments = mountedTemplate.length - literalSegments;
|
|
53
|
-
return (0 === prefixLength ? 1000000 : 0) + 10000 * literalSegments - 100 * dynamicSegments - prefixLength;
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
const resolveCrossProjectRequestObservation = (request, policy)=>{
|
|
57
|
-
const requestMethod = String(request.method || '').trim().toUpperCase();
|
|
58
|
-
const routePaths = [
|
|
59
|
-
...new Set(Object.keys(policy.expectedOperationContracts ?? {}).flatMap((key)=>{
|
|
60
|
-
if (key.startsWith('operation:')) return [];
|
|
61
|
-
const separator = key.indexOf(':');
|
|
62
|
-
if (-1 === separator || key.slice(0, separator).trim().toUpperCase() !== requestMethod) return [];
|
|
63
|
-
return [
|
|
64
|
-
key.slice(separator + 1)
|
|
65
|
-
];
|
|
66
|
-
}))
|
|
67
|
-
];
|
|
68
|
-
if (0 === routePaths.length) return;
|
|
69
|
-
if (hasUnsafePathEncoding(request.pathname)) return {
|
|
70
|
-
method: request.method,
|
|
71
|
-
routePath: request.pathname
|
|
72
|
-
};
|
|
73
|
-
const matches = routePaths.flatMap((routePath)=>{
|
|
74
|
-
const score = routeTemplateMatchScore(routePath, request.pathname);
|
|
75
|
-
return void 0 === score ? [] : [
|
|
76
|
-
{
|
|
77
|
-
routePath,
|
|
78
|
-
score
|
|
79
|
-
}
|
|
80
|
-
];
|
|
81
|
-
});
|
|
82
|
-
if (0 === matches.length) return {
|
|
83
|
-
method: request.method,
|
|
84
|
-
routePath: request.pathname
|
|
85
|
-
};
|
|
86
|
-
matches.sort((left, right)=>right.score - left.score);
|
|
87
|
-
return {
|
|
88
|
-
method: request.method,
|
|
89
|
-
routePath: matches[1]?.score === matches[0]?.score ? request.pathname : matches[0].routePath
|
|
90
|
-
};
|
|
91
|
-
};
|
|
92
|
-
const normalizeHeaderName = (headerName, fallback)=>(headerName || fallback).trim().toLowerCase();
|
|
93
|
-
const normalizeStatusCode = (statusCode)=>{
|
|
94
|
-
if ('number' == typeof statusCode && Number.isFinite(statusCode) && statusCode >= 400 && statusCode <= 599) return Math.floor(statusCode);
|
|
95
|
-
return DEFAULT_DENY_STATUS;
|
|
96
|
-
};
|
|
97
|
-
const readHeader = (headers, headerName)=>{
|
|
98
|
-
const lowerHeaderName = headerName.toLowerCase();
|
|
99
|
-
for (const [name, value] of Object.entries(headers || {}))if (name.toLowerCase() === lowerHeaderName) {
|
|
100
|
-
if (Array.isArray(value)) return value.length > 0 ? String(value[0]) : void 0;
|
|
101
|
-
if (null == value) return;
|
|
102
|
-
return String(value);
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
const extractNamespace = (requestId)=>requestId.split(/[/:.]/)[0]?.trim().toLowerCase();
|
|
106
|
-
const createViolation = (reason, message, status)=>({
|
|
107
|
-
code: 'BFF_CROSS_PROJECT_POLICY_DENIED',
|
|
108
|
-
reason,
|
|
109
|
-
message,
|
|
110
|
-
status
|
|
111
|
-
});
|
|
112
|
-
const warnAdvisoryNamespaceAllowlist = ()=>{
|
|
113
|
-
if (hasWarnedAdvisoryNamespaceAllowlist) return;
|
|
114
|
-
hasWarnedAdvisoryNamespaceAllowlist = true;
|
|
115
|
-
console.warn(`[Modern.js BFF] ${NAMESPACE_ALLOWLIST_REQUIRES_VERIFIER_MESSAGE}. allowClientAssertedNamespace=true will continue to evaluate allowlist against client-asserted requestId namespaces only.`);
|
|
116
|
-
};
|
|
117
|
-
const createCrossProjectPolicyEvaluationState = (headers, policy, observedRequest)=>({
|
|
118
|
-
headers,
|
|
119
|
-
policy,
|
|
120
|
-
status: normalizeStatusCode(policy.denyStatus),
|
|
121
|
-
requireEnvelope: policy.requireEnvelope ?? true,
|
|
122
|
-
requireOperationContext: policy.requireOperationContext ?? true,
|
|
123
|
-
requireOperationContextDetails: policy.requireOperationContextDetails ?? true,
|
|
124
|
-
requireOperationSchemaHash: policy.requireOperationSchemaHash ?? true,
|
|
125
|
-
requireOperationVersion: policy.requireOperationVersion ?? true,
|
|
126
|
-
allowUnknownOperations: policy.allowUnknownOperations ?? false,
|
|
127
|
-
allowClientAssertedNamespace: policy.allowClientAssertedNamespace ?? false,
|
|
128
|
-
verifyProducerIdentity: 'function' == typeof policy.verifyProducerIdentity ? policy.verifyProducerIdentity : void 0,
|
|
129
|
-
allowedNamespaces: (policy.allowedNamespaces || []).map((item)=>item.trim().toLowerCase()).filter(Boolean),
|
|
130
|
-
envelopeHeader: normalizeHeaderName(policy.envelopeHeader, BFF_ENVELOPE_HEADER),
|
|
131
|
-
operationContextHeader: normalizeHeaderName(policy.operationContextHeader, BFF_OPERATION_CONTEXT_HEADER),
|
|
132
|
-
operationContextDetailHeader: normalizeHeaderName(policy.operationContextDetailHeader, BFF_OPERATION_CONTEXT_DETAIL_HEADER),
|
|
133
|
-
requestId: '',
|
|
134
|
-
detailSchemaHash: '',
|
|
135
|
-
detailOperationVersion: void 0,
|
|
136
|
-
observedRequest: observedRequest ? {
|
|
137
|
-
method: String(observedRequest.method || '').trim().toUpperCase(),
|
|
138
|
-
routePath: String(observedRequest.routePath || '').trim()
|
|
139
|
-
} : void 0,
|
|
140
|
-
complete: false
|
|
141
|
-
});
|
|
142
|
-
const checkNamespaceAllowlistVerifier = (state)=>{
|
|
143
|
-
if (state.allowedNamespaces.length > 0 && void 0 === state.verifyProducerIdentity) {
|
|
144
|
-
if (!state.allowClientAssertedNamespace) return createViolation('producer_identity_mismatch', NAMESPACE_ALLOWLIST_REQUIRES_VERIFIER_MESSAGE, state.status);
|
|
145
|
-
warnAdvisoryNamespaceAllowlist();
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
const checkEnvelopeHeader = (state)=>{
|
|
149
|
-
const envelopeRaw = readHeader(state.headers, state.envelopeHeader);
|
|
150
|
-
if (!envelopeRaw) {
|
|
151
|
-
if (!state.requireEnvelope) {
|
|
152
|
-
state.complete = true;
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
return createViolation('missing_envelope', `Missing cross-project envelope header "${state.envelopeHeader}"`, state.status);
|
|
156
|
-
}
|
|
157
|
-
let envelope;
|
|
158
|
-
try {
|
|
159
|
-
const parsed = JSON.parse(envelopeRaw);
|
|
160
|
-
if (!parsed || 'object' != typeof parsed) throw new Error('invalid envelope object');
|
|
161
|
-
envelope = parsed;
|
|
162
|
-
} catch (_error) {
|
|
163
|
-
return createViolation('invalid_envelope', `Invalid cross-project envelope header "${state.envelopeHeader}"`, state.status);
|
|
164
|
-
}
|
|
165
|
-
const requestId = String(envelope.requestId || '').trim();
|
|
166
|
-
if (!requestId) return createViolation('missing_request_id', 'Cross-project envelope does not include a valid requestId', state.status);
|
|
167
|
-
state.requestId = requestId;
|
|
168
|
-
state.claimedNamespace = extractNamespace(requestId);
|
|
169
|
-
state.effectiveNamespace = state.claimedNamespace;
|
|
170
|
-
};
|
|
171
|
-
const checkProducerIdentity = (state)=>{
|
|
172
|
-
if (!state.verifyProducerIdentity) return;
|
|
173
|
-
const verifiedNamespaceRaw = state.verifyProducerIdentity(state.headers);
|
|
174
|
-
const verifiedNamespace = 'string' == typeof verifiedNamespaceRaw ? verifiedNamespaceRaw.trim().toLowerCase() : void 0;
|
|
175
|
-
if (!verifiedNamespace) return createViolation('producer_identity_mismatch', 'Producer identity could not be verified for request', state.status);
|
|
176
|
-
if (verifiedNamespace !== state.claimedNamespace) return createViolation('producer_identity_mismatch', `Envelope namespace "${state.claimedNamespace || 'unknown'}" does not match verified producer identity "${verifiedNamespace}"`, state.status);
|
|
177
|
-
state.effectiveNamespace = verifiedNamespace;
|
|
178
|
-
};
|
|
179
|
-
const checkNamespaceAllowlist = (state)=>{
|
|
180
|
-
if (state.allowedNamespaces.length > 0) {
|
|
181
|
-
if (!state.effectiveNamespace || !state.allowedNamespaces.includes(state.effectiveNamespace)) return createViolation('namespace_not_allowed', `Producer namespace "${state.effectiveNamespace || 'unknown'}" not allowed`, state.status);
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
const checkOperationContext = (state)=>{
|
|
185
|
-
if (!state.requireOperationContext) {
|
|
186
|
-
state.complete = true;
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
const operationContext = readHeader(state.headers, state.operationContextHeader);
|
|
190
|
-
if (!operationContext) return createViolation('missing_operation_context', `Missing operation context header "${state.operationContextHeader}"`, state.status);
|
|
191
|
-
if (!operationContext.startsWith(`${state.requestId}:`)) return createViolation('operation_context_mismatch', `Operation context header "${state.operationContextHeader}" does not match requestId "${state.requestId}"`, state.status);
|
|
192
|
-
state.operationContext = operationContext;
|
|
193
|
-
};
|
|
194
|
-
const checkOperationContextDetails = (state)=>{
|
|
195
|
-
const operationContextDetailsRaw = readHeader(state.headers, state.operationContextDetailHeader);
|
|
196
|
-
if (!operationContextDetailsRaw) {
|
|
197
|
-
if (state.requireOperationContextDetails) return createViolation('missing_operation_context_details', `Missing operation context details header "${state.operationContextDetailHeader}"`, state.status);
|
|
198
|
-
state.complete = true;
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
let operationContextDetails;
|
|
202
|
-
try {
|
|
203
|
-
const parsed = JSON.parse(operationContextDetailsRaw);
|
|
204
|
-
if (!parsed || 'object' != typeof parsed || Array.isArray(parsed)) throw new Error('invalid operation context details object');
|
|
205
|
-
operationContextDetails = parsed;
|
|
206
|
-
} catch (_error) {
|
|
207
|
-
return createViolation('invalid_operation_context_details', `Invalid operation context details header "${state.operationContextDetailHeader}"`, state.status);
|
|
208
|
-
}
|
|
209
|
-
const detailRequestId = String(operationContextDetails.requestId || '').trim();
|
|
210
|
-
if (detailRequestId && detailRequestId !== state.requestId) return createViolation('operation_context_details_request_id_mismatch', `Operation context details requestId "${detailRequestId}" does not match envelope requestId "${state.requestId}"`, state.status);
|
|
211
|
-
const detailOperationId = String(operationContextDetails.operationId || '').trim();
|
|
212
|
-
if (state.operationContext && detailOperationId && detailOperationId !== state.operationContext) return createViolation('operation_context_mismatch', `Operation context details operationId "${detailOperationId}" does not match operation context "${state.operationContext}"`, state.status);
|
|
213
|
-
state.operationContextDetails = operationContextDetails;
|
|
214
|
-
state.detailSchemaHash = String(operationContextDetails.schemaHash || '').trim();
|
|
215
|
-
if (state.requireOperationSchemaHash && !state.detailSchemaHash) return createViolation('missing_operation_schema_hash', `Operation context details header "${state.operationContextDetailHeader}" must include schemaHash`, state.status);
|
|
216
|
-
state.detailOperationVersion = operationContextDetails.operationVersion;
|
|
217
|
-
if (state.requireOperationVersion && 'number' != typeof state.detailOperationVersion) return createViolation('missing_operation_version', `Operation context details header "${state.operationContextDetailHeader}" must include operationVersion`, state.status);
|
|
218
|
-
};
|
|
219
|
-
const checkOperationContract = (state)=>{
|
|
220
|
-
if (!state.operationContextDetails) return;
|
|
221
|
-
const expectedContracts = state.policy.expectedOperationContracts;
|
|
222
|
-
if (expectedContracts && 'object' == typeof expectedContracts) {
|
|
223
|
-
const method = String(state.operationContextDetails.method || '').toUpperCase();
|
|
224
|
-
const routePath = String(state.operationContextDetails.routePath || '').trim();
|
|
225
|
-
const operationId = String(state.operationContextDetails.operationId || '').trim();
|
|
226
|
-
const observedMethod = state.observedRequest?.method;
|
|
227
|
-
const observedRoutePath = state.observedRequest?.routePath;
|
|
228
|
-
if (observedMethod && observedRoutePath && (method !== observedMethod || routePath !== observedRoutePath)) return createViolation('operation_context_mismatch', `Client operation "${operationId || `${method}:${routePath}`}" does not match observed request "${observedMethod}:${observedRoutePath}"`, state.status);
|
|
229
|
-
const contractMethod = observedMethod || method;
|
|
230
|
-
const contractRoutePath = observedRoutePath || routePath;
|
|
231
|
-
const expectedContract = expectedContracts[`${contractMethod}:${contractRoutePath}`] || (state.observedRequest ? void 0 : expectedContracts[`operation:${operationId}`]);
|
|
232
|
-
if (expectedContract) {
|
|
233
|
-
const assertedOperationId = operationId || state.operationContext || '';
|
|
234
|
-
const canonicalRouteOperationId = `${state.requestId}:${contractMethod}:${contractRoutePath}`;
|
|
235
|
-
if (state.observedRequest && assertedOperationId !== expectedContract.operationId && assertedOperationId !== canonicalRouteOperationId) return createViolation('operation_context_mismatch', `Client operation "${assertedOperationId}" does not identify observed contract "${expectedContract.operationId || canonicalRouteOperationId}"`, state.status);
|
|
236
|
-
if (expectedContract.schemaHash && state.detailSchemaHash && expectedContract.schemaHash !== state.detailSchemaHash) return createViolation('operation_schema_hash_mismatch', `Operation schema hash mismatch for "${operationId || `${method}:${routePath}`}"`, state.status);
|
|
237
|
-
if ('number' == typeof expectedContract.operationVersion && 'number' == typeof state.detailOperationVersion && expectedContract.operationVersion !== state.detailOperationVersion) return createViolation('operation_version_mismatch', `Operation version mismatch for "${operationId || `${method}:${routePath}`}"`, state.status);
|
|
238
|
-
} else if (!state.allowUnknownOperations) return createViolation('unknown_operation_contract', `No expected operation contract found for operation "${operationId || `${method}:${routePath}`}"`, state.status);
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
const CROSS_PROJECT_POLICY_CHECKS = [
|
|
242
|
-
checkNamespaceAllowlistVerifier,
|
|
243
|
-
checkEnvelopeHeader,
|
|
244
|
-
checkProducerIdentity,
|
|
245
|
-
checkNamespaceAllowlist,
|
|
246
|
-
checkOperationContext,
|
|
247
|
-
checkOperationContextDetails,
|
|
248
|
-
checkOperationContract
|
|
249
|
-
];
|
|
250
|
-
const evaluateCrossProjectPolicy = (headers, policy, observedRequest)=>{
|
|
251
|
-
if (!policy?.enabled) return null;
|
|
252
|
-
const state = createCrossProjectPolicyEvaluationState(headers, policy, observedRequest);
|
|
253
|
-
for (const check of CROSS_PROJECT_POLICY_CHECKS){
|
|
254
|
-
const violation = check(state);
|
|
255
|
-
if (violation) return violation;
|
|
256
|
-
if (state.complete) break;
|
|
257
|
-
}
|
|
258
|
-
return null;
|
|
259
|
-
};
|
|
260
|
-
export { BFF_ENVELOPE_HEADER, BFF_OPERATION_CONTEXT_DETAIL_HEADER, BFF_OPERATION_CONTEXT_HEADER, evaluateCrossProjectPolicy, resolveCrossProjectRequestObservation };
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import "node:module";
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
import { createHash } from "crypto";
|
|
4
|
-
import "reflect-metadata";
|
|
5
|
-
import { HttpMetadata } from "../types.mjs";
|
|
6
|
-
import { fileURLToPath as __rspack_fileURLToPath } from "node:url";
|
|
7
|
-
var __rspack_import_meta_filename__ = __rspack_fileURLToPath(import.meta.url);
|
|
8
|
-
const DEFAULT_OPERATION_VERSION = 1;
|
|
9
|
-
const deriveOperationVersion = (packageVersion)=>{
|
|
10
|
-
if ('string' != typeof packageVersion) return DEFAULT_OPERATION_VERSION;
|
|
11
|
-
const match = packageVersion.trim().match(/^v?(\d+)\./);
|
|
12
|
-
if (!match) return DEFAULT_OPERATION_VERSION;
|
|
13
|
-
const major = Number.parseInt(match[1], 10);
|
|
14
|
-
return Number.isInteger(major) && major >= 0 ? major : DEFAULT_OPERATION_VERSION;
|
|
15
|
-
};
|
|
16
|
-
const stableStringify = (value)=>{
|
|
17
|
-
if (Array.isArray(value)) return `[${value.map((item)=>stableStringify(item)).join(',')}]`;
|
|
18
|
-
if (value && 'object' == typeof value) {
|
|
19
|
-
const entries = Object.entries(value).filter(([, entryValue])=>void 0 !== entryValue).sort(([a], [b])=>a.localeCompare(b)).map(([key, entryValue])=>`${JSON.stringify(key)}:${stableStringify(entryValue)}`);
|
|
20
|
-
return `{${entries.join(',')}}`;
|
|
21
|
-
}
|
|
22
|
-
return JSON.stringify(value) ?? 'null';
|
|
23
|
-
};
|
|
24
|
-
const sha256 = (text)=>createHash('sha256').update(text).digest('hex');
|
|
25
|
-
let cachedZodToJSONSchema;
|
|
26
|
-
const ZOD_SPECIFIER = "zod";
|
|
27
|
-
const resolveZodToJSONSchema = ()=>{
|
|
28
|
-
if (void 0 !== cachedZodToJSONSchema) return cachedZodToJSONSchema;
|
|
29
|
-
try {
|
|
30
|
-
const load = createRequire(__rspack_import_meta_filename__);
|
|
31
|
-
const zod = load(ZOD_SPECIFIER);
|
|
32
|
-
const candidate = zod?.toJSONSchema ?? zod?.z?.toJSONSchema;
|
|
33
|
-
cachedZodToJSONSchema = 'function' == typeof candidate ? candidate : null;
|
|
34
|
-
} catch {
|
|
35
|
-
cachedZodToJSONSchema = null;
|
|
36
|
-
}
|
|
37
|
-
return cachedZodToJSONSchema;
|
|
38
|
-
};
|
|
39
|
-
const INPUT_SCHEMA_METADATA_KEYS = [
|
|
40
|
-
HttpMetadata.Data,
|
|
41
|
-
HttpMetadata.Query,
|
|
42
|
-
HttpMetadata.Params,
|
|
43
|
-
HttpMetadata.Headers,
|
|
44
|
-
HttpMetadata.Files
|
|
45
|
-
];
|
|
46
|
-
const serializeSchema = (schema)=>{
|
|
47
|
-
const toJSONSchema = resolveZodToJSONSchema();
|
|
48
|
-
if (toJSONSchema) try {
|
|
49
|
-
return toJSONSchema(schema, {
|
|
50
|
-
io: 'input',
|
|
51
|
-
unrepresentable: 'any'
|
|
52
|
-
});
|
|
53
|
-
} catch {}
|
|
54
|
-
return {
|
|
55
|
-
__unserializableSchema: true
|
|
56
|
-
};
|
|
57
|
-
};
|
|
58
|
-
const serializeOperationSchemas = (handler)=>{
|
|
59
|
-
if ('function' != typeof handler) return;
|
|
60
|
-
const serialized = {};
|
|
61
|
-
for (const metadataKey of INPUT_SCHEMA_METADATA_KEYS){
|
|
62
|
-
let schema;
|
|
63
|
-
try {
|
|
64
|
-
schema = Reflect.getMetadata(metadataKey, handler);
|
|
65
|
-
} catch {
|
|
66
|
-
schema = void 0;
|
|
67
|
-
}
|
|
68
|
-
if (null != schema) serialized[metadataKey] = serializeSchema(schema);
|
|
69
|
-
}
|
|
70
|
-
return Object.keys(serialized).length > 0 ? serialized : void 0;
|
|
71
|
-
};
|
|
72
|
-
const createOperationContractHash = (operation, requestId)=>sha256(stableStringify({
|
|
73
|
-
httpMethod: String(operation.httpMethod || '').toUpperCase(),
|
|
74
|
-
name: operation.name,
|
|
75
|
-
requestId,
|
|
76
|
-
routePath: operation.routePath,
|
|
77
|
-
...operation.schemas ? {
|
|
78
|
-
schemas: operation.schemas
|
|
79
|
-
} : {}
|
|
80
|
-
}));
|
|
81
|
-
const createOperationEntries = (handlers)=>handlers.map((item)=>({
|
|
82
|
-
name: item.name,
|
|
83
|
-
httpMethod: String(item.httpMethod || '').toUpperCase(),
|
|
84
|
-
routePath: item.routePath
|
|
85
|
-
})).sort((a, b)=>{
|
|
86
|
-
const keyA = `${a.routePath}:${a.httpMethod}:${a.name}`;
|
|
87
|
-
const keyB = `${b.routePath}:${b.httpMethod}:${b.name}`;
|
|
88
|
-
return keyA.localeCompare(keyB);
|
|
89
|
-
});
|
|
90
|
-
const createOperationSchemaHash = (operationEntries, requestId)=>sha256(stableStringify({
|
|
91
|
-
operations: [
|
|
92
|
-
...operationEntries
|
|
93
|
-
].map((item)=>({
|
|
94
|
-
hash: item.schemaHash ?? createOperationContractHash({
|
|
95
|
-
name: item.name,
|
|
96
|
-
httpMethod: item.httpMethod,
|
|
97
|
-
routePath: item.routePath
|
|
98
|
-
}, requestId)
|
|
99
|
-
})).sort((a, b)=>a.hash.localeCompare(b.hash)),
|
|
100
|
-
requestId
|
|
101
|
-
}));
|
|
102
|
-
const buildOperationContractMap = ({ handlers, requestId, operationVersion })=>{
|
|
103
|
-
const normalizedRequestId = 'string' == typeof requestId && requestId.trim().length > 0 ? requestId.trim() : 'default';
|
|
104
|
-
const normalizedOperationVersion = 'number' == typeof operationVersion && Number.isInteger(operationVersion) ? operationVersion : DEFAULT_OPERATION_VERSION;
|
|
105
|
-
const contracts = {};
|
|
106
|
-
handlers.forEach((item)=>{
|
|
107
|
-
const httpMethod = String(item.httpMethod || '').toUpperCase();
|
|
108
|
-
const schemaHash = createOperationContractHash({
|
|
109
|
-
name: item.name,
|
|
110
|
-
httpMethod,
|
|
111
|
-
routePath: item.routePath,
|
|
112
|
-
schemas: serializeOperationSchemas(item.handler)
|
|
113
|
-
}, normalizedRequestId);
|
|
114
|
-
const operationId = `${normalizedRequestId}:${item.name}`;
|
|
115
|
-
const contract = {
|
|
116
|
-
requestId: normalizedRequestId,
|
|
117
|
-
operationVersion: normalizedOperationVersion,
|
|
118
|
-
schemaHash,
|
|
119
|
-
method: httpMethod,
|
|
120
|
-
routePath: item.routePath,
|
|
121
|
-
operationId,
|
|
122
|
-
handlerName: item.name,
|
|
123
|
-
filename: item.filename
|
|
124
|
-
};
|
|
125
|
-
contracts[`${httpMethod}:${item.routePath}`] = contract;
|
|
126
|
-
contracts[`operation:${operationId}`] = contract;
|
|
127
|
-
});
|
|
128
|
-
return contracts;
|
|
129
|
-
};
|
|
130
|
-
export { DEFAULT_OPERATION_VERSION, buildOperationContractMap, createOperationContractHash, createOperationEntries, createOperationSchemaHash, deriveOperationVersion, serializeOperationSchemas };
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import "node:module";
|
|
2
|
-
import { buildOperationContractMap } from "./operationContracts.mjs";
|
|
3
|
-
const resolveCrossProjectPolicy = (input)=>{
|
|
4
|
-
const { crossProjectPolicy, handlers, requestId, isCrossProjectServer, operationVersion } = input;
|
|
5
|
-
if (!crossProjectPolicy && !isCrossProjectServer) return;
|
|
6
|
-
const policy = crossProjectPolicy ?? {};
|
|
7
|
-
const effectiveRequestId = 'string' == typeof requestId && requestId.trim().length > 0 ? requestId : 'default';
|
|
8
|
-
const generatedContracts = buildOperationContractMap({
|
|
9
|
-
handlers,
|
|
10
|
-
requestId: effectiveRequestId,
|
|
11
|
-
operationVersion
|
|
12
|
-
});
|
|
13
|
-
return {
|
|
14
|
-
...policy,
|
|
15
|
-
enabled: policy.enabled ?? Boolean(isCrossProjectServer),
|
|
16
|
-
requireEnvelope: policy.requireEnvelope ?? true,
|
|
17
|
-
requireOperationContext: policy.requireOperationContext ?? true,
|
|
18
|
-
requireOperationContextDetails: policy.requireOperationContextDetails ?? true,
|
|
19
|
-
requireOperationSchemaHash: policy.requireOperationSchemaHash ?? true,
|
|
20
|
-
requireOperationVersion: policy.requireOperationVersion ?? true,
|
|
21
|
-
allowUnknownOperations: policy.allowUnknownOperations ?? false,
|
|
22
|
-
expectedOperationContracts: {
|
|
23
|
-
...policy.expectedOperationContracts ?? {},
|
|
24
|
-
...generatedContracts
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
export { resolveCrossProjectPolicy };
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import "node:module";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import module_0 from "module";
|
|
4
|
-
import * as __rspack_external_os from "os";
|
|
5
|
-
import * as __rspack_external_path from "path";
|
|
6
|
-
const getRelativeRuntimePath = (appDirectory, serverRuntimePath)=>{
|
|
7
|
-
let relativeRuntimePath = '';
|
|
8
|
-
relativeRuntimePath = 'win32' === __rspack_external_os.platform() ? `../${__rspack_external_path.relative(appDirectory, serverRuntimePath)}` : __rspack_external_path.join('../', __rspack_external_path.relative(appDirectory, serverRuntimePath));
|
|
9
|
-
if ('development' === process.env.NODE_ENV || 'test' === process.env.NODE_ENV) relativeRuntimePath = `./${__rspack_external_path.relative(appDirectory, serverRuntimePath)}`;
|
|
10
|
-
return relativeRuntimePath;
|
|
11
|
-
};
|
|
12
|
-
const sortByLongestPrefix = (arr)=>arr.concat().sort((a, b)=>b.length - a.length);
|
|
13
|
-
const createMatchPath = (paths)=>{
|
|
14
|
-
const sortedKeys = sortByLongestPrefix(Object.keys(paths));
|
|
15
|
-
const sortedPaths = {};
|
|
16
|
-
sortedKeys.forEach((key)=>{
|
|
17
|
-
sortedPaths[key] = paths[key];
|
|
18
|
-
});
|
|
19
|
-
return (request)=>{
|
|
20
|
-
const found = Object.keys(sortedPaths).find((key)=>request.startsWith(key));
|
|
21
|
-
if (found) {
|
|
22
|
-
let foundPaths = sortedPaths[found];
|
|
23
|
-
if (!Array.isArray(foundPaths)) foundPaths = [
|
|
24
|
-
foundPaths
|
|
25
|
-
];
|
|
26
|
-
foundPaths = foundPaths.filter((foundPath)=>__rspack_external_path.isAbsolute(foundPath));
|
|
27
|
-
for (const p of foundPaths){
|
|
28
|
-
const foundPath = request.replace(found, p);
|
|
29
|
-
if (fs.existsSync(foundPath)) return foundPath;
|
|
30
|
-
}
|
|
31
|
-
return request.replace(found, foundPaths[0]);
|
|
32
|
-
}
|
|
33
|
-
return null;
|
|
34
|
-
};
|
|
35
|
-
};
|
|
36
|
-
const registerPaths = (paths)=>{
|
|
37
|
-
const originalResolveFilename = module_0._resolveFilename;
|
|
38
|
-
const { builtinModules } = module_0;
|
|
39
|
-
const matchPath = createMatchPath(paths);
|
|
40
|
-
module_0._resolveFilename = function(request, _parent) {
|
|
41
|
-
const isCoreModule = builtinModules.includes(request);
|
|
42
|
-
if (!isCoreModule) {
|
|
43
|
-
const matched = matchPath(request);
|
|
44
|
-
if (matched) {
|
|
45
|
-
const modifiedArguments = [
|
|
46
|
-
matched,
|
|
47
|
-
...[].slice.call(arguments, 1)
|
|
48
|
-
];
|
|
49
|
-
return originalResolveFilename.apply(this, modifiedArguments);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return originalResolveFilename.apply(this, arguments);
|
|
53
|
-
};
|
|
54
|
-
return ()=>{
|
|
55
|
-
module_0._resolveFilename = originalResolveFilename;
|
|
56
|
-
};
|
|
57
|
-
};
|
|
58
|
-
export { createMatchPath, getRelativeRuntimePath, registerPaths };
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import 'reflect-metadata';
|
|
2
|
-
import type { ResponseMeta } from '../operators/http.js';
|
|
3
|
-
import type { APIHandlerInfo, ApiHandler } from '../router/index.js';
|
|
4
|
-
import { type CrossProjectPolicyConfig, type CrossProjectPolicyViolation } from '../security/crossProjectPolicy.js';
|
|
5
|
-
import { HttpMethod } from '../types.js';
|
|
6
|
-
/** Lowercase route-registration method shared by supported adapter routers. */
|
|
7
|
-
export type ApiRouteMethod = Lowercase<`${HttpMethod}`>;
|
|
8
|
-
/**
|
|
9
|
-
* Maps an `APIHandlerInfo.httpMethod` onto the lowercase router method.
|
|
10
|
-
* Unknown methods fail fast with a descriptive error instead of the
|
|
11
|
-
* `app[method] is not a function` TypeError the adapters used to throw.
|
|
12
|
-
*/
|
|
13
|
-
export declare const toApiRouteMethod: (httpMethod: APIHandlerInfo['httpMethod']) => ApiRouteMethod;
|
|
14
|
-
/** Route middlewares attached by BFF operators via reflect metadata. */
|
|
15
|
-
export declare const getRouteMiddlewares: <Middleware = unknown>(handler: ApiHandler) => Middleware[];
|
|
16
|
-
export type ApiRoutePlanEntry<Middleware = unknown> = {
|
|
17
|
-
method: ApiRouteMethod;
|
|
18
|
-
routePath: string;
|
|
19
|
-
handler: ApiHandler;
|
|
20
|
-
middlewares: Middleware[];
|
|
21
|
-
};
|
|
22
|
-
/**
|
|
23
|
-
* Computes the framework-agnostic registration plan for a set of API
|
|
24
|
-
* handlers: registration order, lowercase route method and operator
|
|
25
|
-
* middlewares. Adapters only translate each entry into framework calls.
|
|
26
|
-
*/
|
|
27
|
-
export declare const planApiRoutes: <Middleware = unknown>(handlerInfos: APIHandlerInfo[]) => ApiRoutePlanEntry<Middleware>[];
|
|
28
|
-
/**
|
|
29
|
-
* Marker used by `@modern-js/bff-runtime` schema handlers. Re-declared here
|
|
30
|
-
* (value-compatible) so adapters do not need a runtime dependency on
|
|
31
|
-
* `@modern-js/bff-runtime` just to detect the handler mode.
|
|
32
|
-
*/
|
|
33
|
-
export declare const HANDLER_WITH_SCHEMA = "HANDLER_WITH_SCHEMA";
|
|
34
|
-
export declare const isSchemaApiHandler: (handler: unknown) => boolean;
|
|
35
|
-
export type ApiHandlerMode = 'meta' | 'schema' | 'inputParamsDecider' | 'plain';
|
|
36
|
-
/**
|
|
37
|
-
* Detects how an API handler expects to be invoked. The probe order (meta →
|
|
38
|
-
* schema → input-params-decider → plain) is shared by every adapter.
|
|
39
|
-
*
|
|
40
|
-
* Adapters call this once at route-registration time, not per request:
|
|
41
|
-
* meta/schema markers are attached by decorators at module load, so a handler
|
|
42
|
-
* marked after registration would not be picked up.
|
|
43
|
-
*/
|
|
44
|
-
export declare const getApiHandlerMode: (handler: ApiHandler) => ApiHandlerMode;
|
|
45
|
-
/** Result envelope produced by `@modern-js/bff-runtime` schema handlers. */
|
|
46
|
-
export type SchemaHandlerResult = {
|
|
47
|
-
type: 'HandleSuccess';
|
|
48
|
-
value: unknown;
|
|
49
|
-
} | {
|
|
50
|
-
type: 'InputValidationError' | 'OutputValidationError';
|
|
51
|
-
message: unknown;
|
|
52
|
-
};
|
|
53
|
-
export type SchemaHandlerHttpOutcome = {
|
|
54
|
-
success: boolean;
|
|
55
|
-
status: number;
|
|
56
|
-
body: unknown;
|
|
57
|
-
};
|
|
58
|
-
/**
|
|
59
|
-
* Maps a schema-handler result onto the HTTP outcome both adapters must
|
|
60
|
-
* produce: 200/value on success, 400/message on input validation errors and
|
|
61
|
-
* 500/message for any other failure.
|
|
62
|
-
*/
|
|
63
|
-
export declare const mapSchemaHandlerResult: (result: SchemaHandlerResult) => SchemaHandlerHttpOutcome;
|
|
64
|
-
/**
|
|
65
|
-
* Reads the response metadata (headers/redirect/status) attached to a meta
|
|
66
|
-
* handler. Returns an empty list when no metadata is present so adapters can
|
|
67
|
-
* iterate unconditionally.
|
|
68
|
-
*/
|
|
69
|
-
export declare const getResponseMetaList: (handler: ApiHandler) => ResponseMeta[];
|
|
70
|
-
export type ApiHandlerInput = {
|
|
71
|
-
params: Record<string, unknown>;
|
|
72
|
-
} & Record<string, unknown>;
|
|
73
|
-
/**
|
|
74
|
-
* Positional invocation convention for plain function handlers: route params
|
|
75
|
-
* in declaration order followed by the full input object.
|
|
76
|
-
*/
|
|
77
|
-
export declare const buildPositionalHandlerArgs: (input: ApiHandlerInput, routePath?: string) => unknown[];
|
|
78
|
-
export type CrossProjectPolicyDenial = {
|
|
79
|
-
status: number;
|
|
80
|
-
body: {
|
|
81
|
-
code: CrossProjectPolicyViolation['code'];
|
|
82
|
-
reason: CrossProjectPolicyViolation['reason'];
|
|
83
|
-
message: string;
|
|
84
|
-
};
|
|
85
|
-
};
|
|
86
|
-
/**
|
|
87
|
-
* Evaluates the cross-project policy for a request and, on violation,
|
|
88
|
-
* returns the exact HTTP status and JSON body every adapter must send.
|
|
89
|
-
*/
|
|
90
|
-
export declare const checkCrossProjectPolicy: (headers: Record<string, unknown>, policy: CrossProjectPolicyConfig | undefined) => CrossProjectPolicyDenial | null;
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import type { AdapterParityScenario, AdapterParityScenarioContext } from './shared.js';
|
|
2
|
-
export declare const createOperationContextSuccessScenarios: ({ helloContract, validEnvelope, validOperationId, }: AdapterParityScenarioContext) => AdapterParityScenario[];
|
|
3
|
-
export declare const createOperationContextDenialScenarios: ({ helloContract, validEnvelope, validOperationId, }: AdapterParityScenarioContext) => AdapterParityScenario[];
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { CrossProjectOperationContract, CrossProjectPolicyViolationReason } from '../../security/crossProjectPolicy.js';
|
|
2
|
-
export declare const PARITY_REQUEST_ID = "crm";
|
|
3
|
-
export declare const PARITY_PRODUCER_REQUEST_ID = "crm.producer-a";
|
|
4
|
-
export type ParityExpectation = {
|
|
5
|
-
kind: 'payload';
|
|
6
|
-
status: number;
|
|
7
|
-
payload: unknown;
|
|
8
|
-
} | {
|
|
9
|
-
kind: 'denied';
|
|
10
|
-
status: number;
|
|
11
|
-
reason: CrossProjectPolicyViolationReason;
|
|
12
|
-
};
|
|
13
|
-
export type AdapterParityScenario = {
|
|
14
|
-
name: string;
|
|
15
|
-
/** Run against policy-enabled server instead of the open one. */
|
|
16
|
-
policy: boolean;
|
|
17
|
-
request: {
|
|
18
|
-
method: 'get' | 'post' | 'patch';
|
|
19
|
-
path: string;
|
|
20
|
-
headers?: Record<string, string>;
|
|
21
|
-
body?: unknown;
|
|
22
|
-
};
|
|
23
|
-
expected: ParityExpectation;
|
|
24
|
-
};
|
|
25
|
-
export type AdapterParityScenarioContext = {
|
|
26
|
-
helloContract: CrossProjectOperationContract;
|
|
27
|
-
validEnvelope: string;
|
|
28
|
-
validOperationId: string;
|
|
29
|
-
};
|
|
30
|
-
export declare const envelopeHeader: (requestId: unknown) => string;
|
|
31
|
-
export declare const detailHeader: (details: Record<string, unknown>) => string;
|
|
32
|
-
export declare const deniedScenario: (name: string, reason: CrossProjectPolicyViolationReason, headers: Record<string, string>) => AdapterParityScenario;
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { APIHandlerInfo } from '../router/index.js';
|
|
2
|
-
import type { AdapterParityScenario } from './parity-scenarios/shared.js';
|
|
3
|
-
export type { AdapterParityScenario, ParityExpectation, } from './parity-scenarios/shared.js';
|
|
4
|
-
export { PARITY_PRODUCER_REQUEST_ID, PARITY_REQUEST_ID, } from './parity-scenarios/shared.js';
|
|
5
|
-
/**
|
|
6
|
-
* Handler fixtures registered in both adapters before running the table.
|
|
7
|
-
*/
|
|
8
|
-
export declare const createParityApiHandlerInfos: () => APIHandlerInfo[];
|
|
9
|
-
/**
|
|
10
|
-
* `bff` config slice for policy-enabled parity server. All `require*`
|
|
11
|
-
* switches stay at strict defaults.
|
|
12
|
-
*/
|
|
13
|
-
export declare const createParityBffConfig: () => {
|
|
14
|
-
requestId: string;
|
|
15
|
-
crossProjectPolicy: {
|
|
16
|
-
enabled: boolean;
|
|
17
|
-
allowedNamespaces: string[];
|
|
18
|
-
allowClientAssertedNamespace: boolean;
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
export declare const createAdapterParityScenarios: () => AdapterParityScenario[];
|
|
22
|
-
/** Structural slice of supertest response used for normalization. */
|
|
23
|
-
export type ParityHttpResponse = {
|
|
24
|
-
status: number;
|
|
25
|
-
/** Content-type mime, e.g. `application/json`. */
|
|
26
|
-
type: string;
|
|
27
|
-
body: unknown;
|
|
28
|
-
text: string;
|
|
29
|
-
};
|
|
30
|
-
type AdapterParityResult = {
|
|
31
|
-
status: number;
|
|
32
|
-
payload: unknown;
|
|
33
|
-
};
|
|
34
|
-
/**
|
|
35
|
-
* Normalizes raw HTTP response into an observable payload value so JSON and
|
|
36
|
-
* text encodings of the same scalar compare equal.
|
|
37
|
-
*/
|
|
38
|
-
export declare const toParityResult: (res: ParityHttpResponse) => AdapterParityResult;
|
|
39
|
-
/**
|
|
40
|
-
* Framework-agnostic assertion: throws a descriptive error when adapter
|
|
41
|
-
* response deviates from the scenario expectation.
|
|
42
|
-
*/
|
|
43
|
-
export declare const assertParityResult: (scenario: AdapterParityScenario, res: ParityHttpResponse) => void;
|