@bleedingdev/modern-js-bff-core 3.9.0-ultramodern.4 → 3.9.0-ultramodern.6

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.
Files changed (49) hide show
  1. package/dist/cjs/client/generateClient.js +67 -138
  2. package/dist/cjs/index.js +25 -101
  3. package/dist/esm/client/generateClient.mjs +65 -139
  4. package/dist/esm/index.mjs +0 -4
  5. package/dist/esm-node/client/generateClient.mjs +65 -139
  6. package/dist/esm-node/index.mjs +0 -4
  7. package/dist/types/client/generateClient.d.ts +24 -1
  8. package/dist/types/index.d.ts +0 -4
  9. package/package.json +5 -12
  10. package/dist/cjs/adapter-kit/index.js +0 -144
  11. package/dist/cjs/adapter-kit/parity-scenarios/cross-project-denial.js +0 -55
  12. package/dist/cjs/adapter-kit/parity-scenarios/envelope.js +0 -128
  13. package/dist/cjs/adapter-kit/parity-scenarios/operation-context.js +0 -152
  14. package/dist/cjs/adapter-kit/parity-scenarios/schema.js +0 -109
  15. package/dist/cjs/adapter-kit/parity-scenarios/shared.js +0 -71
  16. package/dist/cjs/adapter-kit/parity.js +0 -204
  17. package/dist/cjs/security/crossProjectPolicy.js +0 -309
  18. package/dist/cjs/security/operationContracts.js +0 -183
  19. package/dist/cjs/security/resolveCrossProjectPolicy.js +0 -65
  20. package/dist/esm/adapter-kit/index.mjs +0 -79
  21. package/dist/esm/adapter-kit/parity-scenarios/cross-project-denial.mjs +0 -17
  22. package/dist/esm/adapter-kit/parity-scenarios/envelope.mjs +0 -90
  23. package/dist/esm/adapter-kit/parity-scenarios/operation-context.mjs +0 -111
  24. package/dist/esm/adapter-kit/parity-scenarios/schema.mjs +0 -71
  25. package/dist/esm/adapter-kit/parity-scenarios/shared.mjs +0 -21
  26. package/dist/esm/adapter-kit/parity.mjs +0 -148
  27. package/dist/esm/security/crossProjectPolicy.mjs +0 -259
  28. package/dist/esm/security/operationContracts.mjs +0 -127
  29. package/dist/esm/security/resolveCrossProjectPolicy.mjs +0 -27
  30. package/dist/esm-node/adapter-kit/index.mjs +0 -80
  31. package/dist/esm-node/adapter-kit/parity-scenarios/cross-project-denial.mjs +0 -18
  32. package/dist/esm-node/adapter-kit/parity-scenarios/envelope.mjs +0 -91
  33. package/dist/esm-node/adapter-kit/parity-scenarios/operation-context.mjs +0 -112
  34. package/dist/esm-node/adapter-kit/parity-scenarios/schema.mjs +0 -72
  35. package/dist/esm-node/adapter-kit/parity-scenarios/shared.mjs +0 -22
  36. package/dist/esm-node/adapter-kit/parity.mjs +0 -149
  37. package/dist/esm-node/security/crossProjectPolicy.mjs +0 -260
  38. package/dist/esm-node/security/operationContracts.mjs +0 -130
  39. package/dist/esm-node/security/resolveCrossProjectPolicy.mjs +0 -28
  40. package/dist/types/adapter-kit/index.d.ts +0 -90
  41. package/dist/types/adapter-kit/parity-scenarios/cross-project-denial.d.ts +0 -2
  42. package/dist/types/adapter-kit/parity-scenarios/envelope.d.ts +0 -2
  43. package/dist/types/adapter-kit/parity-scenarios/operation-context.d.ts +0 -3
  44. package/dist/types/adapter-kit/parity-scenarios/schema.d.ts +0 -2
  45. package/dist/types/adapter-kit/parity-scenarios/shared.d.ts +0 -32
  46. package/dist/types/adapter-kit/parity.d.ts +0 -43
  47. package/dist/types/security/crossProjectPolicy.d.ts +0 -110
  48. package/dist/types/security/operationContracts.d.ts +0 -84
  49. package/dist/types/security/resolveCrossProjectPolicy.d.ts +0 -39
@@ -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,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,2 +0,0 @@
1
- import type { AdapterParityScenario } from './shared.js';
2
- export declare const createCrossProjectDenialScenarios: () => AdapterParityScenario[];
@@ -1,2 +0,0 @@
1
- import type { AdapterParityScenario } from './shared.js';
2
- export declare const createEnvelopeParityScenarios: () => AdapterParityScenario[];
@@ -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,2 +0,0 @@
1
- import type { AdapterParityScenario } from './shared.js';
2
- export declare const createSchemaParityScenarios: () => 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;
@@ -1,110 +0,0 @@
1
- /**
2
- * Cross-project BFF policy evaluator.
3
- *
4
- * THREAT MODEL — read before relying on this module:
5
- *
6
- * Every header this evaluator inspects (envelope, operation id, operation
7
- * context details) is constructed by the CLIENT from public, open-source
8
- * formats. Without an out-of-band identity binding the checks below are a
9
- * version-skew and misconfiguration gate, not an authentication or
10
- * authorization boundary: any caller can echo an allowed `requestId` and a
11
- * matching operation context.
12
- *
13
- * To use `allowedNamespaces`, supply
14
- * {@link CrossProjectPolicyConfig.verifyProducerIdentity}: a server-side
15
- * hook that derives the producer namespace from a VERIFIED channel (mTLS
16
- * peer identity, gateway-authenticated JWT claims, service-mesh headers
17
- * stripped at the edge, ...). Requests fail closed when a namespace allowlist
18
- * is configured without this hook unless `allowClientAssertedNamespace` is
19
- * explicitly enabled. That escape hatch keeps legacy local/demo ergonomics:
20
- * the client-asserted namespace must match the allowlist. When the hook is
21
- * present, the client-asserted namespace must match the verified namespace
22
- * and the allowlist is checked against the verified value.
23
- *
24
- * Client-side counterparts in `@modern-js/create-request` (identity binding,
25
- * operation contract validation) are developer-experience aids that fail
26
- * fast in well-behaved clients; they protect nothing against a malicious
27
- * caller.
28
- */
29
- export declare const BFF_ENVELOPE_HEADER = "x-modernjs-bff-envelope";
30
- export declare const BFF_OPERATION_CONTEXT_HEADER = "x-operation-id";
31
- export declare const BFF_OPERATION_CONTEXT_DETAIL_HEADER = "x-modernjs-bff-operation-context";
32
- export type CrossProjectOperationContract = {
33
- schemaHash?: string;
34
- operationVersion?: number;
35
- operationId?: string;
36
- };
37
- /**
38
- * Request facts observed by the server adapter rather than asserted by the
39
- * client. `routePath` is the matched route template when the router exposes
40
- * one (for example `/api/customers/:id`), not the client-provided detail.
41
- */
42
- export type CrossProjectRequestObservation = {
43
- method: string;
44
- routePath: string;
45
- };
46
- export type CrossProjectRequestTarget = {
47
- method: string;
48
- pathname: string;
49
- };
50
- export type CrossProjectPolicyViolationReason = 'missing_envelope' | 'invalid_envelope' | 'missing_request_id' | 'namespace_not_allowed' | 'missing_operation_context' | 'operation_context_mismatch' | 'missing_operation_context_details' | 'invalid_operation_context_details' | 'operation_context_details_request_id_mismatch' | 'missing_operation_schema_hash' | 'missing_operation_version' | 'unknown_operation_contract' | 'operation_schema_hash_mismatch' | 'operation_version_mismatch' | 'producer_identity_mismatch';
51
- export type CrossProjectPolicyViolation = {
52
- code: 'BFF_CROSS_PROJECT_POLICY_DENIED';
53
- reason: CrossProjectPolicyViolationReason;
54
- message: string;
55
- status: number;
56
- };
57
- export interface CrossProjectPolicyConfig {
58
- enabled?: boolean;
59
- requireEnvelope?: boolean;
60
- requireOperationContext?: boolean;
61
- requireOperationContextDetails?: boolean;
62
- requireOperationSchemaHash?: boolean;
63
- requireOperationVersion?: boolean;
64
- allowedNamespaces?: string[];
65
- /**
66
- * Explicit local/demo escape hatch for legacy clients that only provide
67
- * client-built requestId namespaces. Defaults to false. Enabling this keeps
68
- * `allowedNamespaces` advisory and must not be used as authorization.
69
- */
70
- allowClientAssertedNamespace?: boolean;
71
- envelopeHeader?: string;
72
- operationContextHeader?: string;
73
- operationContextDetailHeader?: string;
74
- expectedOperationContracts?: Record<string, CrossProjectOperationContract>;
75
- allowUnknownOperations?: boolean;
76
- denyStatus?: number;
77
- /**
78
- * Server-side hook binding producer namespace to VERIFIED identity
79
- * channel (mTLS peer, gateway-authenticated JWT, mesh identity headers).
80
- *
81
- * When provided, the namespace asserted by the client envelope must match
82
- * the namespace returned by this hook, and `allowedNamespaces` is checked
83
- * against the verified value instead of the client-asserted one. Returning
84
- * `undefined` (identity could not be verified) denies the request.
85
- *
86
- * By default, `allowedNamespaces` requires this hook and fails closed
87
- * without it. Set `allowClientAssertedNamespace` only for local/demo
88
- * ergonomics where advisory client-asserted namespace checks are acceptable.
89
- */
90
- verifyProducerIdentity?: (headers: Record<string, unknown>) => string | undefined;
91
- }
92
- /** Serializable, fully-defaulted policy accepted by edge dispatchers. */
93
- export type NormalizedCrossProjectPolicy = CrossProjectPolicyConfig & {
94
- enabled: boolean;
95
- requireEnvelope: boolean;
96
- requireOperationContext: boolean;
97
- requireOperationContextDetails: boolean;
98
- requireOperationSchemaHash: boolean;
99
- requireOperationVersion: boolean;
100
- allowUnknownOperations: boolean;
101
- expectedOperationContracts: Record<string, CrossProjectOperationContract>;
102
- };
103
- /**
104
- * Resolves a concrete adapter request to the server-owned route template in
105
- * the operation contract map. Effect receives requests after its BFF mount
106
- * prefix has been stripped, so leading literal route segments may be mount
107
- * prefixes. Ambiguous matches fail closed by retaining the concrete path.
108
- */
109
- export declare const resolveCrossProjectRequestObservation: (request: CrossProjectRequestTarget, policy: Pick<CrossProjectPolicyConfig, 'expectedOperationContracts'>) => CrossProjectRequestObservation | undefined;
110
- export declare const evaluateCrossProjectPolicy: (headers: Record<string, unknown>, policy?: CrossProjectPolicyConfig, observedRequest?: CrossProjectRequestObservation) => CrossProjectPolicyViolation | null;
@@ -1,84 +0,0 @@
1
- import 'reflect-metadata';
2
- import type { ApiHandler } from '../router/types.js';
3
- export type OperationContractEntry = {
4
- name: string;
5
- httpMethod: string;
6
- routePath: string;
7
- };
8
- export type OperationContractDefinition = {
9
- requestId: string;
10
- operationVersion: number;
11
- schemaHash: string;
12
- method: string;
13
- routePath: string;
14
- operationId: string;
15
- handlerName: string;
16
- filename?: string;
17
- };
18
- export type OperationContractMap = Record<string, OperationContractDefinition>;
19
- export declare const DEFAULT_OPERATION_VERSION = 1;
20
- /**
21
- * Derives the operation version from a producer package version: the semver
22
- * major is the contract version, so consumers regenerated against an older
23
- * producer major fail the `operation_version_mismatch` gate instead of
24
- * silently calling an incompatible API.
25
- *
26
- * Falls back to {@link DEFAULT_OPERATION_VERSION} when no parseable version
27
- * is available.
28
- */
29
- export declare const deriveOperationVersion: (packageVersion?: unknown) => number;
30
- /**
31
- * Serializes the zod input schemas attached to an operator-decorated handler
32
- * (`Data`/`Query`/`Params`/`Headers`/`Upload` metadata) into JSON-schema
33
- * documents. Returns `undefined` for handlers without schema metadata
34
- * (plain handlers, farrow schema-mode handlers).
35
- */
36
- export declare const serializeOperationSchemas: (handler: ApiHandler | undefined) => Record<string, unknown> | undefined;
37
- export type OperationContractHashInput = OperationContractEntry & {
38
- /** Serialized schema documents; omit for schema-less operations. */
39
- schemas?: Record<string, unknown> | undefined;
40
- };
41
- /**
42
- * Per-operation contract hash. The hash covers the operation identity
43
- * (name, method, route, producer requestId) plus the serialized input
44
- * schemas, so:
45
- *
46
- * - changing an input schema changes the hash of exactly that operation;
47
- * - reordering routes or adding unrelated operations never rotates the hash
48
- * of other operations (each operation is hashed independently).
49
- */
50
- export declare const createOperationContractHash: (operation: OperationContractHashInput, requestId: string) => string;
51
- export declare const createOperationEntries: (handlers: Array<{
52
- name: string;
53
- httpMethod: string;
54
- routePath: string;
55
- }>) => OperationContractEntry[];
56
- /**
57
- * Aggregate hash over a set of per-operation contract hashes. Used for the
58
- * module-level `operationSchemaHash` manifest export; stable across route
59
- * reordering because the per-operation hashes are sorted before hashing.
60
- */
61
- export declare const createOperationSchemaHash: (operationEntries: Array<OperationContractEntry & {
62
- schemaHash?: string;
63
- }>, requestId: string) => string;
64
- export type OperationContractSource = {
65
- name: string;
66
- httpMethod: string;
67
- routePath: string;
68
- filename?: string;
69
- /**
70
- * Optional handler function used to serialize schema metadata. Sources
71
- * without a handler (e.g. reflected Effect HttpApi endpoints) hash the
72
- * route identity only.
73
- */
74
- handler?: ApiHandler;
75
- };
76
- export declare const buildOperationContractMap: ({ handlers, requestId, operationVersion, }: {
77
- handlers: OperationContractSource[];
78
- requestId?: string;
79
- /**
80
- * Producer contract version, usually `deriveOperationVersion(pkg.version)`.
81
- * Defaults to {@link DEFAULT_OPERATION_VERSION}.
82
- */
83
- operationVersion?: number;
84
- }) => OperationContractMap;
@@ -1,39 +0,0 @@
1
- import type { CrossProjectPolicyConfig, NormalizedCrossProjectPolicy } from './crossProjectPolicy.js';
2
- import { type OperationContractSource } from './operationContracts.js';
3
- /**
4
- * Runtime input collected by BFF server adapters (hono/effect/...) before
5
- * the cross-project policy can be enforced.
6
- *
7
- * `crossProjectPolicy` accepts the raw `bff.crossProjectPolicy` user config —
8
- * `BffCrossProjectPolicyUserConfig` is a structural subset of
9
- * `CrossProjectPolicyConfig`, so no casts are required at the call site.
10
- */
11
- export interface ResolveCrossProjectPolicyInput {
12
- crossProjectPolicy?: CrossProjectPolicyConfig;
13
- /** Registered API handlers used to derive the operation-contract map. */
14
- handlers: OperationContractSource[];
15
- /** Logical producer ID (`bff.requestId`). */
16
- requestId?: string;
17
- /** Marker injected by generated cross-project SDK plugins. */
18
- isCrossProjectServer?: boolean;
19
- /**
20
- * Producer contract version (usually derived from the producer
21
- * package.json major via `deriveOperationVersion`).
22
- */
23
- operationVersion?: number;
24
- }
25
- /**
26
- * Fully-defaulted policy returned by {@link resolveCrossProjectPolicy}.
27
- */
28
- export type ResolvedCrossProjectPolicy = NormalizedCrossProjectPolicy;
29
- /**
30
- * Normalizes the user-facing cross-project policy config into the evaluator
31
- * input shared by every BFF server adapter:
32
- *
33
- * - returns `undefined` when neither a policy nor the cross-project server
34
- * marker is present (policy middleware must not be installed);
35
- * - applies the documented defaults for all `require*` switches;
36
- * - merges generated operation contracts (derived from the registered
37
- * handlers) over any user-provided `expectedOperationContracts`.
38
- */
39
- export declare const resolveCrossProjectPolicy: (input: ResolveCrossProjectPolicyInput) => ResolvedCrossProjectPolicy | undefined;