@webpieces/core-util 0.4.838 → 0.4.840
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -20
- package/package.json +1 -1
- package/src/http/RouteMetadataFactory.js +20 -5
- package/src/http/RouteMetadataFactory.js.map +1 -1
- package/src/http/StreamErrorControl.d.ts +11 -0
- package/src/http/StreamErrorControl.js +78 -0
- package/src/http/StreamErrorControl.js.map +1 -0
- package/src/http/StreamingCompileAssertions.d.ts +1 -0
- package/src/http/StreamingCompileAssertions.js +22 -0
- package/src/http/StreamingCompileAssertions.js.map +1 -0
- package/src/http/StreamingContract.d.ts +38 -62
- package/src/http/StreamingContract.js +73 -42
- package/src/http/StreamingContract.js.map +1 -1
- package/src/http/StreamingSchemaCatalog.d.ts +9 -0
- package/src/http/StreamingSchemaCatalog.js +39 -0
- package/src/http/StreamingSchemaCatalog.js.map +1 -0
- package/src/http/WebpiecesCoreHeaders.d.ts +2 -0
- package/src/http/WebpiecesCoreHeaders.js +3 -0
- package/src/http/WebpiecesCoreHeaders.js.map +1 -1
- package/src/index.d.ts +6 -4
- package/src/index.js +13 -4
- package/src/index.js.map +1 -1
package/README.md
CHANGED
|
@@ -105,26 +105,44 @@ This package has zero dependencies and works in all modern browsers and Node.js
|
|
|
105
105
|
|
|
106
106
|
## Typed streaming contracts
|
|
107
107
|
|
|
108
|
-
`@WpStream(
|
|
109
|
-
contract. The
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
108
|
+
`@WpStream(StreamDirection.RESPONSE | REQUEST | FULL)` makes the direction part of the shared API
|
|
109
|
+
contract. The method signature is the only declaration of the initial request/response and later
|
|
110
|
+
event types; generated schema wiring registers the four derived schemas separately from the
|
|
111
|
+
decorator.
|
|
112
|
+
|
|
113
|
+
`RESPONSE` is the browser-safe shape:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
@WpStream(StreamDirection.RESPONSE)
|
|
117
|
+
abstract watch(
|
|
118
|
+
request: WatchRequest,
|
|
119
|
+
responses: ResponseStream<WatchEvent>,
|
|
120
|
+
): Promise<WatchAccepted>;
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The finite request body contains the initial request and closes. The promise resolves after the
|
|
124
|
+
initial response has arrived and validated; later response records are delivered incrementally.
|
|
125
|
+
Browser clients reject `REQUEST` and `FULL` at bind time because Fetch cannot consume a response
|
|
126
|
+
while keeping a streaming upload open.
|
|
127
|
+
|
|
128
|
+
Every `event` and `close` call awaits its next transport/consumer boundary. Events are ordered and
|
|
129
|
+
backpressured. `close()` ends only that direction; `cancel(error?)` terminates the whole exchange
|
|
130
|
+
and delivers cancellation to the peer once. There are no non-terminal failures, `fail`, `complete`,
|
|
131
|
+
or `onCancel` methods. Business correlation belongs in the application's own DTO.
|
|
132
|
+
|
|
133
|
+
`FULL` returns `Promise<RequestStream<InitialResponse, RequestEvent>>` and accepts a
|
|
134
|
+
`ResponseStream<ResponseEvent>` second parameter. `REQUEST` returns the same request-stream shape
|
|
135
|
+
but has no response-stream parameter. Install the build-generated `stream-Contract-schemas.json`
|
|
136
|
+
using `registerStreamingCatalog(Contract, catalog)` before binding a contract; `wp-openapi` derives
|
|
137
|
+
all required schema slots from these signatures and diagnoses mismatched forms.
|
|
138
|
+
|
|
139
|
+
The HTTP media type is `application/x-webpieces-jsonl`. Initial values and ordinary events are raw
|
|
140
|
+
application-owned JSON records followed by `\n`; Webpieces never wraps them in a `kind` or `payload`
|
|
141
|
+
object. An abnormal post-open cancellation is the reserved sideband record
|
|
142
|
+
`0x1E + {"control":"error","response":<HttpResponseDto>} + \n`. This reuses the configured
|
|
143
|
+
`ErrorTranslator` in both directions without making an error look like an application event.
|
|
144
|
+
Graceful `close()` remains ordinary directional EOF. Transport loss where no control record can cross
|
|
145
|
+
is reported locally as `StreamTransportError`.
|
|
128
146
|
|
|
129
147
|
## Related Packages
|
|
130
148
|
|
package/package.json
CHANGED
|
@@ -6,6 +6,7 @@ const http_parameter_decorators_1 = require("./http-parameter-decorators");
|
|
|
6
6
|
const HttpContract_1 = require("./HttpContract");
|
|
7
7
|
const RouteMetadata_1 = require("./RouteMetadata");
|
|
8
8
|
const StreamingContract_1 = require("./StreamingContract");
|
|
9
|
+
const StreamingContract_2 = require("./StreamingContract");
|
|
9
10
|
/**
|
|
10
11
|
* Build the one runtime route model consumed by incoming routing, in-process clients, and both
|
|
11
12
|
* generated HTTP clients. Keeping this join/validation here prevents four subtly different
|
|
@@ -24,7 +25,7 @@ class RouteMetadataFactory {
|
|
|
24
25
|
const parameterTypes = this.parameterTypes(apiClass, methodName);
|
|
25
26
|
const declarations = (0, http_parameter_decorators_1.getHttpParameterDeclarations)(apiClass, methodName);
|
|
26
27
|
const bodyParameterIndex = streaming
|
|
27
|
-
? this.validateStreamingParameters(apiClass, methodName, httpMethod, parameterTypes, declarations)
|
|
28
|
+
? this.validateStreamingParameters(apiClass, methodName, httpMethod, parameterTypes, declarations, streaming)
|
|
28
29
|
: this.validateParameters(apiClass, methodName, fullPath, httpMethod, parameterTypes, declarations);
|
|
29
30
|
const bindings = declarations
|
|
30
31
|
.map((declaration) => new HttpContract_1.HttpParameterBinding(declaration.index, declaration.source, declaration.wireName, this.valueType(parameterTypes[declaration.index])))
|
|
@@ -48,15 +49,29 @@ class RouteMetadataFactory {
|
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
// webpieces-disable no-function-outside-class -- private pure validator alongside existing contract validators
|
|
51
|
-
static validateStreamingParameters(apiClass, methodName, httpMethod, parameterTypes, declarations) {
|
|
52
|
+
static validateStreamingParameters(apiClass, methodName, httpMethod, parameterTypes, declarations, streaming) {
|
|
52
53
|
const label = `${apiClass.name || 'Unknown'}.${methodName}`;
|
|
54
|
+
if (!streaming.initialRequestSchema ||
|
|
55
|
+
!streaming.initialResponseSchema ||
|
|
56
|
+
(streaming.direction !== StreamingContract_2.StreamDirection.RESPONSE && !streaming.requestSchema) ||
|
|
57
|
+
(streaming.direction !== StreamingContract_2.StreamDirection.REQUEST && !streaming.responseSchema)) {
|
|
58
|
+
throw new Error(`${label} requires generated streaming schemas. Run wp-openapi and registerStreamingCatalog(${apiClass.name}, catalog) before binding.`);
|
|
59
|
+
}
|
|
53
60
|
if (httpMethod !== 'POST')
|
|
54
61
|
throw new Error(`${label} is streaming and must use POST.`);
|
|
55
62
|
if (declarations.length > 0)
|
|
56
63
|
throw new Error(`${label} is streaming and cannot declare path/query parameters.`);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
64
|
+
const expected = streaming.direction === StreamingContract_2.StreamDirection.REQUEST ? 1 : 2;
|
|
65
|
+
if (parameterTypes.length !== expected) {
|
|
66
|
+
const form = streaming.direction === StreamingContract_2.StreamDirection.REQUEST
|
|
67
|
+
? '(InitialRequest) => Promise<RequestStream<InitialResponse, RequestEvent>>'
|
|
68
|
+
: streaming.direction === StreamingContract_2.StreamDirection.RESPONSE
|
|
69
|
+
? '(InitialRequest, ResponseStream<ResponseEvent>) => Promise<InitialResponse>'
|
|
70
|
+
: '(InitialRequest, ResponseStream<ResponseEvent>) => Promise<RequestStream<InitialResponse, RequestEvent>>';
|
|
71
|
+
throw new Error(`Invalid @WpStream(StreamDirection.${streaming.direction.toUpperCase()}) method ${label}: ` +
|
|
72
|
+
`${streaming.direction.toUpperCase()} accepts exactly ${form}.`);
|
|
73
|
+
}
|
|
74
|
+
return 0;
|
|
60
75
|
}
|
|
61
76
|
/**
|
|
62
77
|
* Refuse a contract in which two endpoints resolve to the same HTTP method + path.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RouteMetadataFactory.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/RouteMetadataFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n getApiPath,\n getAuthMeta,\n getEndpointHttpMethod,\n getEndpointOperation,\n getEndpointOptions,\n getEndpoints,\n getMaskSpec,\n} from './decorators';\nimport { getHttpParameterDeclarations } from './http-parameter-decorators';\nimport {\n EndpointResponseType,\n HttpParameterBinding,\n HttpParameterDeclaration,\n HttpParameterValueType,\n HttpMethod,\n} from './HttpContract';\nimport { RouteMetadata } from './RouteMetadata';\nimport { getStreamingEndpoint } from './StreamingContract';\n\n/**\n * Build the one runtime route model consumed by incoming routing, in-process clients, and both\n * generated HTTP clients. Keeping this join/validation here prevents four subtly different\n * interpretations of the same decorators.\n */\nexport class RouteMetadataFactory {\n // webpieces-disable no-function-outside-class -- deterministic decorator metadata factory shared by DI-free browser and server runtimes\n static create(\n apiClass: Function,\n methodName: string,\n controllerClassName?: string,\n ): RouteMetadata {\n const endpointPath = this.endpointPath(apiClass, methodName);\n const fullPath = this.joinPath(getApiPath(apiClass) ?? '', endpointPath);\n const options = getEndpointOptions(apiClass, methodName);\n const streaming = getStreamingEndpoint(apiClass, methodName);\n const httpMethod = getEndpointHttpMethod(apiClass, methodName);\n const operation = getEndpointOperation(apiClass, methodName);\n this.validateEndpointOptions(\n apiClass,\n methodName,\n httpMethod,\n options.formPost,\n options.rawBody,\n );\n const parameterTypes = this.parameterTypes(apiClass, methodName);\n const declarations = getHttpParameterDeclarations(apiClass, methodName);\n const bodyParameterIndex = streaming\n ? this.validateStreamingParameters(\n apiClass,\n methodName,\n httpMethod,\n parameterTypes,\n declarations,\n )\n : this.validateParameters(\n apiClass,\n methodName,\n fullPath,\n httpMethod,\n parameterTypes,\n declarations,\n );\n const bindings = declarations\n .map(\n (declaration: HttpParameterDeclaration) =>\n new HttpParameterBinding(\n declaration.index,\n declaration.source,\n declaration.wireName,\n this.valueType(parameterTypes[declaration.index]),\n ),\n )\n .sort((a: HttpParameterBinding, b: HttpParameterBinding) => a.index - b.index);\n\n return new RouteMetadata(\n httpMethod,\n fullPath,\n methodName,\n operation,\n controllerClassName,\n getAuthMeta(apiClass, methodName),\n apiClass.name || 'UnknownApi',\n options.formPost === true,\n getMaskSpec(apiClass, methodName),\n options.rawBody === true,\n bindings,\n bodyParameterIndex,\n this.responseType(options.responseType, apiClass, methodName),\n streaming,\n options.background === true,\n );\n }\n\n // webpieces-disable no-function-outside-class -- private pure helper for static route metadata construction\n private static endpointPath(apiClass: Function, methodName: string): string {\n const path = (getEndpoints(apiClass) ?? {})[methodName];\n if (path !== undefined) return path;\n throw new Error(`No @Endpoint metadata for ${apiClass.name || 'Unknown'}.${methodName}.`);\n }\n\n // webpieces-disable no-function-outside-class -- private pure helper keeps the existing static metadata factory below the method-size limit\n private static validateEndpointOptions(\n apiClass: Function,\n methodName: string,\n httpMethod: HttpMethod,\n formPost?: boolean,\n rawBody?: boolean,\n ): void {\n if (httpMethod === 'GET' && formPost === true) {\n throw new Error(\n `${apiClass.name}.${methodName} is GET and cannot declare formPost:true because GET has no request body.`,\n );\n }\n if (httpMethod === 'GET' && rawBody === true) {\n throw new Error(\n `${apiClass.name}.${methodName} is GET and cannot declare rawBody:true because GET has no request body.`,\n );\n }\n }\n\n // webpieces-disable no-function-outside-class -- private pure validator alongside existing contract validators\n private static validateStreamingParameters(\n apiClass: Function,\n methodName: string,\n httpMethod: HttpMethod,\n parameterTypes: readonly Function[],\n declarations: readonly HttpParameterDeclaration[],\n ): undefined {\n const label = `${apiClass.name || 'Unknown'}.${methodName}`;\n if (httpMethod !== 'POST') throw new Error(`${label} is streaming and must use POST.`);\n if (declarations.length > 0)\n throw new Error(`${label} is streaming and cannot declare path/query parameters.`);\n if (parameterTypes.length !== 1)\n throw new Error(`${label} must take exactly one ResponseStream parameter.`);\n return undefined;\n }\n\n /**\n * Refuse a contract in which two endpoints resolve to the same HTTP method + path.\n *\n * Empty paths are LEGAL (`@ApiPath('')` + `@Endpoint(POST, '', ...)` is how a contract says \"the whole\n * destination arrives per call\"), so the thing actually worth rejecting is ambiguity: two methods\n * that land on one route. On the server the second registration would silently shadow the first\n * in the route map, and on the client both would dial the same URL — neither is ever what the\n * author meant. Placeholder NAMES are ignored when comparing (`/a/{id}` and `/a/{key}` are the\n * same route to a router), `''` and `'/'` compare equal (the server serves an empty path at the\n * root), and the error names BOTH methods so the fix is obvious.\n *\n * Called by every transport that binds a whole contract (the generated clients and the server's\n * route registration), so a duplicate dies at startup however the contract is consumed.\n *\n * @throws Error naming both colliding methods and the shared route.\n */\n // webpieces-disable no-function-outside-class -- deterministic contract validation shared by DI-free browser and server runtimes\n static assertNoDuplicateRoutes(apiClass: Function): void {\n const label = apiClass.name || 'Unknown';\n const seen = new Map<string, string>();\n for (const methodName of Object.keys(getEndpoints(apiClass) ?? {})) {\n const route = this.create(apiClass, methodName);\n // '' and '/' are one route to a server (every request path starts with '/').\n const shape = (route.path === '' ? '/' : route.path).replace(/\\{[^{}]+\\}/g, '{}');\n const key = `${route.httpMethod} ${shape}`;\n const previous = seen.get(key);\n if (previous !== undefined) {\n throw new Error(\n `${label}.${previous} and ${label}.${methodName} both resolve to ` +\n `${route.httpMethod} '${route.path}'. Two endpoints of one contract cannot share ` +\n `an HTTP method + path; give one of them a distinct @Endpoint path or HTTP method.`,\n );\n }\n seen.set(key, methodName);\n }\n }\n\n /**\n * Join `@ApiPath` and `@Endpoint` into the route path.\n *\n * Both empty joins to `''`, NOT `'/'`. A contract declaring `@ApiPath('')` + `@Endpoint(POST, '', ...)`\n * is saying \"this route adds nothing to the base URL\", and a client whose base URL is a full\n * destination (host + path + query) must send it byte for byte. Before #926 the client joined with\n * plain concatenation (`'' + ''`), and forcing a `/` here broke exactly that (#944).\n */\n // webpieces-disable no-function-outside-class -- private pure helper for the static metadata factory\n private static joinPath(basePath: string, endpointPath: string): string {\n if (basePath === '' && endpointPath === '') return '';\n const joined = `${basePath}/${endpointPath}`.replace(/\\/{2,}/g, '/');\n return joined.startsWith('/') ? joined : `/${joined}`;\n }\n\n // webpieces-disable no-function-outside-class -- private pure helper for the static metadata factory\n private static responseType(\n value: EndpointResponseType | undefined,\n apiClass: Function,\n methodName: string,\n ): EndpointResponseType {\n const responseType = value ?? 'body';\n if (responseType === 'body' || responseType === 'full') return responseType;\n throw new Error(\n `${apiClass.name}.${methodName} declares unsupported responseType '${String(responseType)}'; use body or full.`,\n );\n }\n\n // webpieces-disable no-function-outside-class -- Reflect metadata lookup has no injectable dependency\n private static parameterTypes(apiClass: Function, methodName: string): Function[] {\n return Reflect.getMetadata('design:paramtypes', apiClass.prototype, methodName) ?? [];\n }\n\n // webpieces-disable no-function-outside-class -- private deterministic contract validation helper\n private static validateParameters(\n apiClass: Function,\n methodName: string,\n fullPath: string,\n httpMethod: HttpMethod,\n parameterTypes: readonly Function[],\n declarations: readonly HttpParameterDeclaration[],\n ): number | undefined {\n const label = `${apiClass.name || 'Unknown'}.${methodName}`;\n const placeholders = [...fullPath.matchAll(/\\{([^{}]+)\\}/g)].map(\n (match: RegExpMatchArray) => match[1],\n );\n const pathNames = declarations\n .filter((declaration: HttpParameterDeclaration) => declaration.source === 'path')\n .map((declaration: HttpParameterDeclaration) => declaration.wireName);\n for (const placeholder of new Set(placeholders)) {\n if (!pathNames.includes(placeholder)) {\n throw new Error(\n `${label} path '${fullPath}' contains {${placeholder}} but no @PathParam('${placeholder}') parameter.`,\n );\n }\n }\n for (const pathName of pathNames) {\n if (!placeholders.includes(pathName)) {\n throw new Error(\n `${label} declares @PathParam('${pathName}') but '${fullPath}' has no {${pathName}} placeholder.`,\n );\n }\n }\n\n const seenWire = new Set<string>();\n const mappedIndexes = new Set<number>();\n for (const declaration of declarations) {\n const wireKey = `${declaration.source}:${declaration.wireName}`;\n if (seenWire.has(wireKey)) {\n throw new Error(`${label} maps '${wireKey}' more than once.`);\n }\n seenWire.add(wireKey);\n mappedIndexes.add(declaration.index);\n }\n\n const declaredCount = Math.max(\n parameterTypes.length,\n declarations.reduce(\n (highest: number, declaration: HttpParameterDeclaration) =>\n Math.max(highest, declaration.index + 1),\n 0,\n ),\n );\n const unbound: number[] = [];\n for (let index = 0; index < declaredCount; index++) {\n if (!mappedIndexes.has(index)) unbound.push(index);\n }\n if (httpMethod === 'GET' && unbound.length > 0) {\n throw new Error(\n `${label} is GET, so every parameter must use @PathParam(...) or @QueryParam(...); argument ${unbound[0]} is unmapped.`,\n );\n }\n if (unbound.length > 1) {\n throw new Error(\n `${label} has ${unbound.length} unmapped parameters. POST permits exactly one unannotated request body; map every other parameter explicitly.`,\n );\n }\n return unbound[0];\n }\n\n // webpieces-disable no-function-outside-class -- private conversion of reflected constructors to wire types\n private static valueType(type: Function | undefined): HttpParameterValueType {\n if (type === String) return 'string';\n if (type === Number) return 'number';\n if (type === Boolean) return 'boolean';\n if (type === Array) return 'array';\n return 'unknown';\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"RouteMetadataFactory.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/RouteMetadataFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n getApiPath,\n getAuthMeta,\n getEndpointHttpMethod,\n getEndpointOperation,\n getEndpointOptions,\n getEndpoints,\n getMaskSpec,\n} from './decorators';\nimport { getHttpParameterDeclarations } from './http-parameter-decorators';\nimport {\n EndpointResponseType,\n HttpParameterBinding,\n HttpParameterDeclaration,\n HttpParameterValueType,\n HttpMethod,\n} from './HttpContract';\nimport { RouteMetadata } from './RouteMetadata';\nimport { getStreamingEndpoint } from './StreamingContract';\nimport { StreamDirection, StreamingEndpointMetadata } from './StreamingContract';\n\n/**\n * Build the one runtime route model consumed by incoming routing, in-process clients, and both\n * generated HTTP clients. Keeping this join/validation here prevents four subtly different\n * interpretations of the same decorators.\n */\nexport class RouteMetadataFactory {\n // webpieces-disable no-function-outside-class -- deterministic decorator metadata factory shared by DI-free browser and server runtimes\n static create(\n apiClass: Function,\n methodName: string,\n controllerClassName?: string,\n ): RouteMetadata {\n const endpointPath = this.endpointPath(apiClass, methodName);\n const fullPath = this.joinPath(getApiPath(apiClass) ?? '', endpointPath);\n const options = getEndpointOptions(apiClass, methodName);\n const streaming = getStreamingEndpoint(apiClass, methodName);\n const httpMethod = getEndpointHttpMethod(apiClass, methodName);\n const operation = getEndpointOperation(apiClass, methodName);\n this.validateEndpointOptions(\n apiClass,\n methodName,\n httpMethod,\n options.formPost,\n options.rawBody,\n );\n const parameterTypes = this.parameterTypes(apiClass, methodName);\n const declarations = getHttpParameterDeclarations(apiClass, methodName);\n const bodyParameterIndex = streaming\n ? this.validateStreamingParameters(\n apiClass,\n methodName,\n httpMethod,\n parameterTypes,\n declarations,\n streaming,\n )\n : this.validateParameters(\n apiClass,\n methodName,\n fullPath,\n httpMethod,\n parameterTypes,\n declarations,\n );\n const bindings = declarations\n .map(\n (declaration: HttpParameterDeclaration) =>\n new HttpParameterBinding(\n declaration.index,\n declaration.source,\n declaration.wireName,\n this.valueType(parameterTypes[declaration.index]),\n ),\n )\n .sort((a: HttpParameterBinding, b: HttpParameterBinding) => a.index - b.index);\n\n return new RouteMetadata(\n httpMethod,\n fullPath,\n methodName,\n operation,\n controllerClassName,\n getAuthMeta(apiClass, methodName),\n apiClass.name || 'UnknownApi',\n options.formPost === true,\n getMaskSpec(apiClass, methodName),\n options.rawBody === true,\n bindings,\n bodyParameterIndex,\n this.responseType(options.responseType, apiClass, methodName),\n streaming,\n options.background === true,\n );\n }\n\n // webpieces-disable no-function-outside-class -- private pure helper for static route metadata construction\n private static endpointPath(apiClass: Function, methodName: string): string {\n const path = (getEndpoints(apiClass) ?? {})[methodName];\n if (path !== undefined) return path;\n throw new Error(`No @Endpoint metadata for ${apiClass.name || 'Unknown'}.${methodName}.`);\n }\n\n // webpieces-disable no-function-outside-class -- private pure helper keeps the existing static metadata factory below the method-size limit\n private static validateEndpointOptions(\n apiClass: Function,\n methodName: string,\n httpMethod: HttpMethod,\n formPost?: boolean,\n rawBody?: boolean,\n ): void {\n if (httpMethod === 'GET' && formPost === true) {\n throw new Error(\n `${apiClass.name}.${methodName} is GET and cannot declare formPost:true because GET has no request body.`,\n );\n }\n if (httpMethod === 'GET' && rawBody === true) {\n throw new Error(\n `${apiClass.name}.${methodName} is GET and cannot declare rawBody:true because GET has no request body.`,\n );\n }\n }\n\n // webpieces-disable no-function-outside-class -- private pure validator alongside existing contract validators\n private static validateStreamingParameters(\n apiClass: Function,\n methodName: string,\n httpMethod: HttpMethod,\n parameterTypes: readonly Function[],\n declarations: readonly HttpParameterDeclaration[],\n streaming: StreamingEndpointMetadata,\n ): number | undefined {\n const label = `${apiClass.name || 'Unknown'}.${methodName}`;\n if (\n !streaming.initialRequestSchema ||\n !streaming.initialResponseSchema ||\n (streaming.direction !== StreamDirection.RESPONSE && !streaming.requestSchema) ||\n (streaming.direction !== StreamDirection.REQUEST && !streaming.responseSchema)\n ) {\n throw new Error(\n `${label} requires generated streaming schemas. Run wp-openapi and registerStreamingCatalog(${apiClass.name}, catalog) before binding.`,\n );\n }\n if (httpMethod !== 'POST') throw new Error(`${label} is streaming and must use POST.`);\n if (declarations.length > 0)\n throw new Error(`${label} is streaming and cannot declare path/query parameters.`);\n const expected = streaming.direction === StreamDirection.REQUEST ? 1 : 2;\n if (parameterTypes.length !== expected) {\n const form =\n streaming.direction === StreamDirection.REQUEST\n ? '(InitialRequest) => Promise<RequestStream<InitialResponse, RequestEvent>>'\n : streaming.direction === StreamDirection.RESPONSE\n ? '(InitialRequest, ResponseStream<ResponseEvent>) => Promise<InitialResponse>'\n : '(InitialRequest, ResponseStream<ResponseEvent>) => Promise<RequestStream<InitialResponse, RequestEvent>>';\n throw new Error(\n `Invalid @WpStream(StreamDirection.${streaming.direction.toUpperCase()}) method ${label}: ` +\n `${streaming.direction.toUpperCase()} accepts exactly ${form}.`,\n );\n }\n return 0;\n }\n\n /**\n * Refuse a contract in which two endpoints resolve to the same HTTP method + path.\n *\n * Empty paths are LEGAL (`@ApiPath('')` + `@Endpoint(POST, '', ...)` is how a contract says \"the whole\n * destination arrives per call\"), so the thing actually worth rejecting is ambiguity: two methods\n * that land on one route. On the server the second registration would silently shadow the first\n * in the route map, and on the client both would dial the same URL — neither is ever what the\n * author meant. Placeholder NAMES are ignored when comparing (`/a/{id}` and `/a/{key}` are the\n * same route to a router), `''` and `'/'` compare equal (the server serves an empty path at the\n * root), and the error names BOTH methods so the fix is obvious.\n *\n * Called by every transport that binds a whole contract (the generated clients and the server's\n * route registration), so a duplicate dies at startup however the contract is consumed.\n *\n * @throws Error naming both colliding methods and the shared route.\n */\n // webpieces-disable no-function-outside-class -- deterministic contract validation shared by DI-free browser and server runtimes\n static assertNoDuplicateRoutes(apiClass: Function): void {\n const label = apiClass.name || 'Unknown';\n const seen = new Map<string, string>();\n for (const methodName of Object.keys(getEndpoints(apiClass) ?? {})) {\n const route = this.create(apiClass, methodName);\n // '' and '/' are one route to a server (every request path starts with '/').\n const shape = (route.path === '' ? '/' : route.path).replace(/\\{[^{}]+\\}/g, '{}');\n const key = `${route.httpMethod} ${shape}`;\n const previous = seen.get(key);\n if (previous !== undefined) {\n throw new Error(\n `${label}.${previous} and ${label}.${methodName} both resolve to ` +\n `${route.httpMethod} '${route.path}'. Two endpoints of one contract cannot share ` +\n `an HTTP method + path; give one of them a distinct @Endpoint path or HTTP method.`,\n );\n }\n seen.set(key, methodName);\n }\n }\n\n /**\n * Join `@ApiPath` and `@Endpoint` into the route path.\n *\n * Both empty joins to `''`, NOT `'/'`. A contract declaring `@ApiPath('')` + `@Endpoint(POST, '', ...)`\n * is saying \"this route adds nothing to the base URL\", and a client whose base URL is a full\n * destination (host + path + query) must send it byte for byte. Before #926 the client joined with\n * plain concatenation (`'' + ''`), and forcing a `/` here broke exactly that (#944).\n */\n // webpieces-disable no-function-outside-class -- private pure helper for the static metadata factory\n private static joinPath(basePath: string, endpointPath: string): string {\n if (basePath === '' && endpointPath === '') return '';\n const joined = `${basePath}/${endpointPath}`.replace(/\\/{2,}/g, '/');\n return joined.startsWith('/') ? joined : `/${joined}`;\n }\n\n // webpieces-disable no-function-outside-class -- private pure helper for the static metadata factory\n private static responseType(\n value: EndpointResponseType | undefined,\n apiClass: Function,\n methodName: string,\n ): EndpointResponseType {\n const responseType = value ?? 'body';\n if (responseType === 'body' || responseType === 'full') return responseType;\n throw new Error(\n `${apiClass.name}.${methodName} declares unsupported responseType '${String(responseType)}'; use body or full.`,\n );\n }\n\n // webpieces-disable no-function-outside-class -- Reflect metadata lookup has no injectable dependency\n private static parameterTypes(apiClass: Function, methodName: string): Function[] {\n return Reflect.getMetadata('design:paramtypes', apiClass.prototype, methodName) ?? [];\n }\n\n // webpieces-disable no-function-outside-class -- private deterministic contract validation helper\n private static validateParameters(\n apiClass: Function,\n methodName: string,\n fullPath: string,\n httpMethod: HttpMethod,\n parameterTypes: readonly Function[],\n declarations: readonly HttpParameterDeclaration[],\n ): number | undefined {\n const label = `${apiClass.name || 'Unknown'}.${methodName}`;\n const placeholders = [...fullPath.matchAll(/\\{([^{}]+)\\}/g)].map(\n (match: RegExpMatchArray) => match[1],\n );\n const pathNames = declarations\n .filter((declaration: HttpParameterDeclaration) => declaration.source === 'path')\n .map((declaration: HttpParameterDeclaration) => declaration.wireName);\n for (const placeholder of new Set(placeholders)) {\n if (!pathNames.includes(placeholder)) {\n throw new Error(\n `${label} path '${fullPath}' contains {${placeholder}} but no @PathParam('${placeholder}') parameter.`,\n );\n }\n }\n for (const pathName of pathNames) {\n if (!placeholders.includes(pathName)) {\n throw new Error(\n `${label} declares @PathParam('${pathName}') but '${fullPath}' has no {${pathName}} placeholder.`,\n );\n }\n }\n\n const seenWire = new Set<string>();\n const mappedIndexes = new Set<number>();\n for (const declaration of declarations) {\n const wireKey = `${declaration.source}:${declaration.wireName}`;\n if (seenWire.has(wireKey)) {\n throw new Error(`${label} maps '${wireKey}' more than once.`);\n }\n seenWire.add(wireKey);\n mappedIndexes.add(declaration.index);\n }\n\n const declaredCount = Math.max(\n parameterTypes.length,\n declarations.reduce(\n (highest: number, declaration: HttpParameterDeclaration) =>\n Math.max(highest, declaration.index + 1),\n 0,\n ),\n );\n const unbound: number[] = [];\n for (let index = 0; index < declaredCount; index++) {\n if (!mappedIndexes.has(index)) unbound.push(index);\n }\n if (httpMethod === 'GET' && unbound.length > 0) {\n throw new Error(\n `${label} is GET, so every parameter must use @PathParam(...) or @QueryParam(...); argument ${unbound[0]} is unmapped.`,\n );\n }\n if (unbound.length > 1) {\n throw new Error(\n `${label} has ${unbound.length} unmapped parameters. POST permits exactly one unannotated request body; map every other parameter explicitly.`,\n );\n }\n return unbound[0];\n }\n\n // webpieces-disable no-function-outside-class -- private conversion of reflected constructors to wire types\n private static valueType(type: Function | undefined): HttpParameterValueType {\n if (type === String) return 'string';\n if (type === Number) return 'number';\n if (type === Boolean) return 'boolean';\n if (type === Array) return 'array';\n return 'unknown';\n }\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StreamTransportError } from './StreamingContract';
|
|
2
|
+
export declare class StreamProtocolError extends StreamTransportError {
|
|
3
|
+
}
|
|
4
|
+
export declare class StreamDisconnectedError extends StreamTransportError {
|
|
5
|
+
}
|
|
6
|
+
/** Reserved ASCII-RS controls share exactly the unary ErrorTranslator seam in both directions. */
|
|
7
|
+
export declare class StreamErrorControl {
|
|
8
|
+
encode(error: Error): string;
|
|
9
|
+
decode(line: string): Error;
|
|
10
|
+
private response;
|
|
11
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StreamErrorControl = exports.StreamDisconnectedError = exports.StreamProtocolError = void 0;
|
|
4
|
+
const ClientRegistry_1 = require("./ClientRegistry");
|
|
5
|
+
const HttpResponseDto_1 = require("./HttpResponseDto");
|
|
6
|
+
const WebpiecesDefaultErrorTranslator_1 = require("./WebpiecesDefaultErrorTranslator");
|
|
7
|
+
const errorUtils_1 = require("../lib/errorUtils");
|
|
8
|
+
const StreamingContract_1 = require("./StreamingContract");
|
|
9
|
+
class StreamProtocolError extends StreamingContract_1.StreamTransportError {
|
|
10
|
+
}
|
|
11
|
+
exports.StreamProtocolError = StreamProtocolError;
|
|
12
|
+
class StreamDisconnectedError extends StreamingContract_1.StreamTransportError {
|
|
13
|
+
}
|
|
14
|
+
exports.StreamDisconnectedError = StreamDisconnectedError;
|
|
15
|
+
class ControlStatus {
|
|
16
|
+
code;
|
|
17
|
+
reason;
|
|
18
|
+
}
|
|
19
|
+
class ControlHeader {
|
|
20
|
+
name;
|
|
21
|
+
value;
|
|
22
|
+
}
|
|
23
|
+
class ControlResponse {
|
|
24
|
+
status;
|
|
25
|
+
headers;
|
|
26
|
+
body;
|
|
27
|
+
}
|
|
28
|
+
class ControlRecord {
|
|
29
|
+
control;
|
|
30
|
+
response;
|
|
31
|
+
}
|
|
32
|
+
/** Reserved ASCII-RS controls share exactly the unary ErrorTranslator seam in both directions. */
|
|
33
|
+
class StreamErrorControl {
|
|
34
|
+
encode(error) {
|
|
35
|
+
const response = ClientRegistry_1.ClientRegistry.getErrorTranslator().toWire(error);
|
|
36
|
+
return `${String.fromCharCode(0x1e)}${JSON.stringify({ control: 'error', response })}\n`;
|
|
37
|
+
}
|
|
38
|
+
decode(line) {
|
|
39
|
+
const response = this.response(line);
|
|
40
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- fromWire returns its typed result by throwing
|
|
41
|
+
try {
|
|
42
|
+
ClientRegistry_1.ClientRegistry.getErrorTranslator().fromWire(response);
|
|
43
|
+
WebpiecesDefaultErrorTranslator_1.WEBPIECES_DEFAULT_ERROR_TRANSLATOR.fromWire(response);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
const error = (0, errorUtils_1.toError)(err);
|
|
47
|
+
return error;
|
|
48
|
+
}
|
|
49
|
+
return new StreamProtocolError('Error translator returned for a JSONL error control record.');
|
|
50
|
+
}
|
|
51
|
+
response(line) {
|
|
52
|
+
let control;
|
|
53
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- JSON.parse is the untrusted control boundary
|
|
54
|
+
try {
|
|
55
|
+
control = JSON.parse(line.slice(1));
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
const error = (0, errorUtils_1.toError)(err);
|
|
59
|
+
throw new StreamProtocolError('Malformed JSONL error control record.', undefined, undefined, error);
|
|
60
|
+
}
|
|
61
|
+
const status = control?.response?.status;
|
|
62
|
+
const headers = control?.response?.headers;
|
|
63
|
+
if (control?.control !== 'error' ||
|
|
64
|
+
typeof status?.code !== 'number' ||
|
|
65
|
+
typeof status.reason !== 'string' ||
|
|
66
|
+
!Array.isArray(headers))
|
|
67
|
+
throw new StreamProtocolError('Malformed JSONL error control record.');
|
|
68
|
+
const mapped = headers.map((header) => {
|
|
69
|
+
if (typeof header?.name !== 'string' || typeof header.value !== 'string') {
|
|
70
|
+
throw new StreamProtocolError('Malformed JSONL error control header.');
|
|
71
|
+
}
|
|
72
|
+
return new HttpResponseDto_1.HttpHeader(header.name, header.value);
|
|
73
|
+
});
|
|
74
|
+
return new HttpResponseDto_1.HttpResponseDto(new HttpResponseDto_1.HttpResponseStatus(status.code, status.reason), mapped, control.response?.body);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.StreamErrorControl = StreamErrorControl;
|
|
78
|
+
//# sourceMappingURL=StreamErrorControl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StreamErrorControl.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/StreamErrorControl.ts"],"names":[],"mappings":"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","sourcesContent":["import { ClientRegistry } from './ClientRegistry';\nimport { HttpHeader, HttpResponseDto, HttpResponseStatus } from './HttpResponseDto';\nimport { WEBPIECES_DEFAULT_ERROR_TRANSLATOR } from './WebpiecesDefaultErrorTranslator';\nimport { DtoValue } from '../mcp/DtoSchema';\nimport { toError } from '../lib/errorUtils';\nimport { StreamTransportError } from './StreamingContract';\n\nexport class StreamProtocolError extends StreamTransportError {}\n\nexport class StreamDisconnectedError extends StreamTransportError {}\n\nclass ControlStatus {\n code?: number;\n reason?: string;\n}\n\nclass ControlHeader {\n name?: string;\n value?: string;\n}\n\nclass ControlResponse {\n status?: ControlStatus;\n headers?: ControlHeader[];\n body?: DtoValue;\n}\n\nclass ControlRecord {\n control?: string;\n response?: ControlResponse;\n}\n\n/** Reserved ASCII-RS controls share exactly the unary ErrorTranslator seam in both directions. */\nexport class StreamErrorControl {\n encode(error: Error): string {\n const response = ClientRegistry.getErrorTranslator().toWire(error);\n return `${String.fromCharCode(0x1e)}${JSON.stringify({ control: 'error', response })}\\n`;\n }\n\n decode(line: string): Error {\n const response = this.response(line);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- fromWire returns its typed result by throwing\n try {\n ClientRegistry.getErrorTranslator().fromWire(response);\n WEBPIECES_DEFAULT_ERROR_TRANSLATOR.fromWire(response);\n } catch (err: unknown) {\n const error = toError(err);\n return error;\n }\n return new StreamProtocolError(\n 'Error translator returned for a JSONL error control record.',\n );\n }\n\n private response(line: string): HttpResponseDto {\n let control: ControlRecord;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- JSON.parse is the untrusted control boundary\n try {\n control = JSON.parse(line.slice(1)) as ControlRecord;\n } catch (err: unknown) {\n const error = toError(err);\n throw new StreamProtocolError(\n 'Malformed JSONL error control record.',\n undefined,\n undefined,\n error,\n );\n }\n const status = control?.response?.status;\n const headers = control?.response?.headers;\n if (\n control?.control !== 'error' ||\n typeof status?.code !== 'number' ||\n typeof status.reason !== 'string' ||\n !Array.isArray(headers)\n )\n throw new StreamProtocolError('Malformed JSONL error control record.');\n const mapped = headers.map((header: ControlHeader): HttpHeader => {\n if (typeof header?.name !== 'string' || typeof header.value !== 'string') {\n throw new StreamProtocolError('Malformed JSONL error control header.');\n }\n return new HttpHeader(header.name, header.value);\n });\n return new HttpResponseDto(\n new HttpResponseStatus(status.code, status.reason),\n mapped,\n control.response?.body,\n );\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const DtoSchema_1 = require("../mcp/DtoSchema");
|
|
4
|
+
const StreamingContract_1 = require("./StreamingContract");
|
|
5
|
+
class StreamMigrationAssertions {
|
|
6
|
+
check(response, request) {
|
|
7
|
+
(0, StreamingContract_1.WpStream)(StreamingContract_1.StreamDirection.FULL);
|
|
8
|
+
// @ts-expect-error -- WpStream accepts direction only; schemas come from the build model
|
|
9
|
+
(0, StreamingContract_1.WpStream)(new DtoSchema_1.ApiJsonSchema('object'), new DtoSchema_1.ApiJsonSchema('object'));
|
|
10
|
+
// @ts-expect-error -- graceful EOF is close()
|
|
11
|
+
response.complete();
|
|
12
|
+
// @ts-expect-error -- abnormal termination is cancel(error?)
|
|
13
|
+
response.fail(new Error('cancel'));
|
|
14
|
+
// @ts-expect-error -- implement cancel(error?) directly on the peer stream
|
|
15
|
+
response.onCancel(() => undefined);
|
|
16
|
+
// @ts-expect-error -- initial response and later request events require two generics
|
|
17
|
+
const old = request;
|
|
18
|
+
void old;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
void StreamMigrationAssertions;
|
|
22
|
+
//# sourceMappingURL=StreamingCompileAssertions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StreamingCompileAssertions.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/StreamingCompileAssertions.ts"],"names":[],"mappings":";;AAAA,gDAAiD;AACjD,2DAA+F;AAE/F,MAAM,yBAAyB;IAC3B,KAAK,CAAC,QAAgC,EAAE,OAAsC;QAC1E,IAAA,4BAAQ,EAAC,mCAAe,CAAC,IAAI,CAAC,CAAC;QAC/B,yFAAyF;QACzF,IAAA,4BAAQ,EAAC,IAAI,yBAAa,CAAC,QAAQ,CAAC,EAAE,IAAI,yBAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;QACnE,8CAA8C;QAC9C,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACpB,6DAA6D;QAC7D,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;QACnC,2EAA2E;QAC3E,QAAQ,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACnC,qFAAqF;QACrF,MAAM,GAAG,GAA0B,OAAO,CAAC;QAC3C,KAAK,GAAG,CAAC;IACb,CAAC;CACJ;AACD,KAAK,yBAAyB,CAAC","sourcesContent":["import { ApiJsonSchema } from '../mcp/DtoSchema';\nimport { RequestStream, ResponseStream, StreamDirection, WpStream } from './StreamingContract';\n\nclass StreamMigrationAssertions {\n check(response: ResponseStream<string>, request: RequestStream<string, number>): void {\n WpStream(StreamDirection.FULL);\n // @ts-expect-error -- WpStream accepts direction only; schemas come from the build model\n WpStream(new ApiJsonSchema('object'), new ApiJsonSchema('object'));\n // @ts-expect-error -- graceful EOF is close()\n response.complete();\n // @ts-expect-error -- abnormal termination is cancel(error?)\n response.fail(new Error('cancel'));\n // @ts-expect-error -- implement cancel(error?) directly on the peer stream\n response.onCancel(() => undefined);\n // @ts-expect-error -- initial response and later request events require two generics\n const old: RequestStream<string> = request;\n void old;\n }\n}\nvoid StreamMigrationAssertions;\n"]}
|
|
@@ -8,52 +8,29 @@ export declare class StreamCorrelation {
|
|
|
8
8
|
readonly requestId?: string | undefined;
|
|
9
9
|
constructor(key: string | number, requestId?: string | undefined);
|
|
10
10
|
}
|
|
11
|
-
export interface StreamFailureOptions {
|
|
12
|
-
/** Defaults to true. Wire adapters may promote false to true when continuation is unsafe. */
|
|
13
|
-
terminal?: boolean;
|
|
14
|
-
}
|
|
15
11
|
/** Server-to-client half of a typed stream. Every write is an explicit backpressure boundary. */
|
|
12
|
+
export declare enum StreamDirection {
|
|
13
|
+
RESPONSE = "response",
|
|
14
|
+
REQUEST = "request",
|
|
15
|
+
FULL = "full"
|
|
16
|
+
}
|
|
16
17
|
export interface ResponseStream<T> {
|
|
17
18
|
event(value: T, correlation?: StreamCorrelation): Promise<void>;
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* published as kind `implementation` with generic text, exactly as on every other boundary.
|
|
23
|
-
*
|
|
24
|
-
* This is the FAITHFUL {@link ApiErrorCodec} encode, NOT `ApiErrorBoundary.encode`, and that is
|
|
25
|
-
* deliberate: a stream failure is delivered IN BAND to a peer that is reading the stream, so a
|
|
26
|
-
* {@link StreamTransportError} (an `ApiConnectionError`) must arrive as kind `connection` for
|
|
27
|
-
* the peer to reconstruct it as the transport failure it is. Republishing it as
|
|
28
|
-
* `implementation` — right at an HTTP/MCP edge, where the caller is asking us to do work — would
|
|
29
|
-
* tell the peer the wrong thing about a stream it is holding one end of.
|
|
30
|
-
*/
|
|
31
|
-
fail(error: Error, correlation?: StreamCorrelation, options?: StreamFailureOptions): Promise<void>;
|
|
32
|
-
complete(): Promise<void>;
|
|
33
|
-
onCancel(handler: (reason?: unknown) => void | Promise<void>): void;
|
|
19
|
+
/** Graceful directional EOF after all queued records are acknowledged. */
|
|
20
|
+
close(): Promise<void>;
|
|
21
|
+
/** Abnormal terminal cancellation of the logical exchange. */
|
|
22
|
+
cancel(error?: Error): Promise<void>;
|
|
34
23
|
}
|
|
35
24
|
/** Client-to-server half. Cancellation exists immediately and is independent of writer readiness. */
|
|
36
|
-
export interface RequestStream<T> {
|
|
25
|
+
export interface RequestStream<TInitialResponse, T> {
|
|
26
|
+
/** The typed opening acknowledgement received before later request events are writable. */
|
|
27
|
+
getInitialResponse(): TInitialResponse;
|
|
37
28
|
event(value: T, correlation?: StreamCorrelation): Promise<void>;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
* CONVENIENCE taxonomy webpieces ships so the common cases are easy, never something the
|
|
41
|
-
* framework may demand from an application. An app's own `MyLibError extends Error` is
|
|
42
|
-
* published as kind `implementation` with generic text, exactly as on every other boundary.
|
|
43
|
-
*
|
|
44
|
-
* This is the FAITHFUL {@link ApiErrorCodec} encode, NOT `ApiErrorBoundary.encode`, and that is
|
|
45
|
-
* deliberate: a stream failure is delivered IN BAND to a peer that is reading the stream, so a
|
|
46
|
-
* {@link StreamTransportError} (an `ApiConnectionError`) must arrive as kind `connection` for
|
|
47
|
-
* the peer to reconstruct it as the transport failure it is. Republishing it as
|
|
48
|
-
* `implementation` — right at an HTTP/MCP edge, where the caller is asking us to do work — would
|
|
49
|
-
* tell the peer the wrong thing about a stream it is holding one end of.
|
|
50
|
-
*/
|
|
51
|
-
fail(error: Error, correlation?: StreamCorrelation, options?: StreamFailureOptions): Promise<void>;
|
|
52
|
-
complete(): Promise<void>;
|
|
53
|
-
cancel(reason?: unknown): Promise<void>;
|
|
29
|
+
close(): Promise<void>;
|
|
30
|
+
cancel(error?: Error): Promise<void>;
|
|
54
31
|
}
|
|
55
32
|
export type StreamEnvelopeKind = 'event' | 'failure' | 'complete';
|
|
56
|
-
/**
|
|
33
|
+
/** Internal acknowledgement records; these are never serialized around application JSONL DTOs. */
|
|
57
34
|
export declare class StreamEnvelope<T = DtoValue> {
|
|
58
35
|
readonly kind: StreamEnvelopeKind;
|
|
59
36
|
readonly value?: T | undefined;
|
|
@@ -71,27 +48,26 @@ export declare class StreamTransportError extends ApiConnectionError {
|
|
|
71
48
|
/**
|
|
72
49
|
* Runtime schema and wire-policy metadata for one streaming method.
|
|
73
50
|
*
|
|
74
|
-
*
|
|
75
|
-
* their schemas from `@WpDtoField` at decoration time; that decorator is deleted, because the
|
|
76
|
-
* compiler already knows every fact it restated (#983). A schema is the thing a boundary validates
|
|
77
|
-
* against, so it is the thing the metadata carries.
|
|
51
|
+
* The build-time API model derives initial and event schemas independently from the signature.
|
|
78
52
|
*/
|
|
79
53
|
export declare class StreamingEndpointMetadata {
|
|
80
|
-
readonly
|
|
81
|
-
readonly
|
|
82
|
-
|
|
83
|
-
readonly
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
supportsNonTerminalFailures?: boolean);
|
|
54
|
+
readonly direction: StreamDirection;
|
|
55
|
+
readonly initialRequestSchema?: ApiJsonSchema | undefined;
|
|
56
|
+
readonly initialResponseSchema?: ApiJsonSchema | undefined;
|
|
57
|
+
readonly requestSchema?: ApiJsonSchema | undefined;
|
|
58
|
+
readonly responseSchema?: ApiJsonSchema | undefined;
|
|
59
|
+
constructor(direction: StreamDirection, initialRequestSchema?: ApiJsonSchema | undefined, initialResponseSchema?: ApiJsonSchema | undefined, requestSchema?: ApiJsonSchema | undefined, responseSchema?: ApiJsonSchema | undefined);
|
|
87
60
|
}
|
|
88
61
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
|
|
92
|
-
|
|
62
|
+
* Declares direction; the method signature owns all four DTO types. Install its generated
|
|
63
|
+
* stream-Contract-schemas.json with registerStreamingCatalog before binding the contract.
|
|
64
|
+
*/
|
|
65
|
+
export declare function WpStream(direction: StreamDirection): MethodDecorator;
|
|
66
|
+
/**
|
|
67
|
+
* Installs the schemas emitted by the build-time API model. Kept separate from `@WpStream` so the
|
|
68
|
+
* contract declaration has one source of truth: its TypeScript signature.
|
|
93
69
|
*/
|
|
94
|
-
export declare function
|
|
70
|
+
export declare function registerStreamingSchemas(apiClass: Function, methodName: string, schemas: Pick<StreamingEndpointMetadata, 'initialRequestSchema' | 'initialResponseSchema' | 'requestSchema' | 'responseSchema'>): void;
|
|
95
71
|
export declare function getStreamingEndpoint(apiClass: Function, methodName: string): StreamingEndpointMetadata | undefined;
|
|
96
72
|
/** Validate a typed event at every adapter boundary, with one consistent diagnostic. */
|
|
97
73
|
export declare class StreamEventValidator {
|
|
@@ -99,20 +75,20 @@ export declare class StreamEventValidator {
|
|
|
99
75
|
validate(schema: ApiJsonSchema, value: unknown, direction: 'request' | 'response'): void;
|
|
100
76
|
}
|
|
101
77
|
/** Awaiting the sink acknowledges the write, so adapters cannot outrun their consumer. */
|
|
102
|
-
export declare class StreamWriter<T> implements
|
|
78
|
+
export declare class StreamWriter<T, TInitialResponse = T> implements ResponseStream<T> {
|
|
103
79
|
private readonly sink;
|
|
104
80
|
private readonly validateEvent?;
|
|
105
|
-
private readonly
|
|
81
|
+
private readonly initialResponse?;
|
|
82
|
+
private readonly cancelSink?;
|
|
106
83
|
private terminal;
|
|
107
84
|
private cancelled;
|
|
85
|
+
private failure?;
|
|
108
86
|
private pending;
|
|
109
|
-
|
|
110
|
-
|
|
87
|
+
constructor(sink: (envelope: StreamEnvelope<T>) => Promise<void>, validateEvent?: ((value: T) => void) | undefined, initialResponse?: TInitialResponse | undefined, cancelSink?: ((error?: Error) => Promise<void>) | undefined);
|
|
88
|
+
getInitialResponse(): TInitialResponse;
|
|
111
89
|
event(value: T, correlation?: StreamCorrelation): Promise<void>;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
onCancel(handler: (reason?: unknown) => void | Promise<void>): void;
|
|
115
|
-
cancel(reason?: unknown): Promise<void>;
|
|
90
|
+
close(): Promise<void>;
|
|
91
|
+
cancel(error?: Error): Promise<void>;
|
|
116
92
|
private requireWritable;
|
|
117
93
|
private enqueue;
|
|
118
94
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.StreamWriter = exports.StreamEventValidator = exports.StreamingEndpointMetadata = exports.StreamTransportError = exports.StreamEnvelope = exports.StreamCorrelation = void 0;
|
|
3
|
+
exports.StreamWriter = exports.StreamEventValidator = exports.StreamingEndpointMetadata = exports.StreamTransportError = exports.StreamEnvelope = exports.StreamDirection = exports.StreamCorrelation = void 0;
|
|
4
4
|
exports.WpStream = WpStream;
|
|
5
|
+
exports.registerStreamingSchemas = registerStreamingSchemas;
|
|
5
6
|
exports.getStreamingEndpoint = getStreamingEndpoint;
|
|
6
7
|
require("reflect-metadata");
|
|
7
8
|
const ApiError_1 = require("../errors/ApiError");
|
|
@@ -19,7 +20,14 @@ class StreamCorrelation {
|
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
exports.StreamCorrelation = StreamCorrelation;
|
|
22
|
-
/**
|
|
23
|
+
/** Server-to-client half of a typed stream. Every write is an explicit backpressure boundary. */
|
|
24
|
+
var StreamDirection;
|
|
25
|
+
(function (StreamDirection) {
|
|
26
|
+
StreamDirection["RESPONSE"] = "response";
|
|
27
|
+
StreamDirection["REQUEST"] = "request";
|
|
28
|
+
StreamDirection["FULL"] = "full";
|
|
29
|
+
})(StreamDirection || (exports.StreamDirection = StreamDirection = {}));
|
|
30
|
+
/** Internal acknowledgement records; these are never serialized around application JSONL DTOs. */
|
|
23
31
|
class StreamEnvelope {
|
|
24
32
|
kind;
|
|
25
33
|
value;
|
|
@@ -49,39 +57,49 @@ exports.StreamTransportError = StreamTransportError;
|
|
|
49
57
|
/**
|
|
50
58
|
* Runtime schema and wire-policy metadata for one streaming method.
|
|
51
59
|
*
|
|
52
|
-
*
|
|
53
|
-
* their schemas from `@WpDtoField` at decoration time; that decorator is deleted, because the
|
|
54
|
-
* compiler already knows every fact it restated (#983). A schema is the thing a boundary validates
|
|
55
|
-
* against, so it is the thing the metadata carries.
|
|
60
|
+
* The build-time API model derives initial and event schemas independently from the signature.
|
|
56
61
|
*/
|
|
57
62
|
class StreamingEndpointMetadata {
|
|
63
|
+
direction;
|
|
64
|
+
initialRequestSchema;
|
|
65
|
+
initialResponseSchema;
|
|
58
66
|
requestSchema;
|
|
59
67
|
responseSchema;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
constructor(direction, initialRequestSchema, initialResponseSchema, requestSchema, responseSchema) {
|
|
69
|
+
this.direction = direction;
|
|
70
|
+
this.initialRequestSchema = initialRequestSchema;
|
|
71
|
+
this.initialResponseSchema = initialResponseSchema;
|
|
64
72
|
this.requestSchema = requestSchema;
|
|
65
73
|
this.responseSchema = responseSchema;
|
|
66
|
-
this.supportsNonTerminalFailures = supportsNonTerminalFailures;
|
|
67
74
|
}
|
|
68
75
|
}
|
|
69
76
|
exports.StreamingEndpointMetadata = StreamingEndpointMetadata;
|
|
70
77
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* Build them with {@link ObjectSchemaBuilder}, or read them out of the build's generated model.
|
|
78
|
+
* Declares direction; the method signature owns all four DTO types. Install its generated
|
|
79
|
+
* stream-Contract-schemas.json with registerStreamingCatalog before binding the contract.
|
|
75
80
|
*/
|
|
76
|
-
// webpieces-disable no-function-outside-class -- decorator factory
|
|
77
|
-
function WpStream(
|
|
81
|
+
// webpieces-disable no-function-outside-class -- decorator factory is the public annotation API
|
|
82
|
+
function WpStream(direction) {
|
|
78
83
|
return (target, propertyKey) => {
|
|
79
84
|
const apiClass = target.constructor;
|
|
80
85
|
const methods = Reflect.getMetadata(decorators_1.METADATA_KEYS.STREAM_ENDPOINTS, apiClass) ?? {};
|
|
81
|
-
methods[String(propertyKey)] = new StreamingEndpointMetadata(
|
|
86
|
+
methods[String(propertyKey)] = new StreamingEndpointMetadata(direction);
|
|
82
87
|
Reflect.defineMetadata(decorators_1.METADATA_KEYS.STREAM_ENDPOINTS, methods, apiClass);
|
|
83
88
|
};
|
|
84
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Installs the schemas emitted by the build-time API model. Kept separate from `@WpStream` so the
|
|
92
|
+
* contract declaration has one source of truth: its TypeScript signature.
|
|
93
|
+
*/
|
|
94
|
+
// webpieces-disable no-function-outside-class -- generated wiring calls this metadata registration boundary
|
|
95
|
+
function registerStreamingSchemas(apiClass, methodName, schemas) {
|
|
96
|
+
const current = getStreamingEndpoint(apiClass, methodName);
|
|
97
|
+
if (!current)
|
|
98
|
+
throw new Error(`${apiClass.name}.${methodName} is not decorated with @WpStream.`);
|
|
99
|
+
const methods = Reflect.getMetadata(decorators_1.METADATA_KEYS.STREAM_ENDPOINTS, apiClass) ?? {};
|
|
100
|
+
methods[methodName] = new StreamingEndpointMetadata(current.direction, schemas.initialRequestSchema, schemas.initialResponseSchema, schemas.requestSchema, schemas.responseSchema);
|
|
101
|
+
Reflect.defineMetadata(decorators_1.METADATA_KEYS.STREAM_ENDPOINTS, methods, apiClass);
|
|
102
|
+
}
|
|
85
103
|
// webpieces-disable no-function-outside-class -- metadata reader paired with WpStream
|
|
86
104
|
function getStreamingEndpoint(apiClass, methodName) {
|
|
87
105
|
const methods = Reflect.getMetadata(decorators_1.METADATA_KEYS.STREAM_ENDPOINTS, apiClass) ?? {};
|
|
@@ -102,58 +120,71 @@ exports.StreamEventValidator = StreamEventValidator;
|
|
|
102
120
|
class StreamWriter {
|
|
103
121
|
sink;
|
|
104
122
|
validateEvent;
|
|
105
|
-
|
|
123
|
+
initialResponse;
|
|
124
|
+
cancelSink;
|
|
106
125
|
terminal = false;
|
|
107
126
|
cancelled = false;
|
|
127
|
+
failure;
|
|
108
128
|
pending = Promise.resolve();
|
|
109
|
-
|
|
110
|
-
cancelHandlers = [];
|
|
111
|
-
constructor(sink, validateEvent, allowNonTerminalFailures = true) {
|
|
129
|
+
constructor(sink, validateEvent, initialResponse, cancelSink) {
|
|
112
130
|
this.sink = sink;
|
|
113
131
|
this.validateEvent = validateEvent;
|
|
114
|
-
this.
|
|
132
|
+
this.initialResponse = initialResponse;
|
|
133
|
+
this.cancelSink = cancelSink;
|
|
134
|
+
}
|
|
135
|
+
getInitialResponse() {
|
|
136
|
+
if (this.initialResponse === undefined) {
|
|
137
|
+
throw new StreamTransportError('RequestStream initial response is unavailable.');
|
|
138
|
+
}
|
|
139
|
+
return this.initialResponse;
|
|
115
140
|
}
|
|
116
141
|
async event(value, correlation) {
|
|
117
142
|
this.requireWritable();
|
|
118
143
|
this.validateEvent?.(value);
|
|
119
144
|
await this.enqueue(new StreamEnvelope('event', value, undefined, correlation));
|
|
120
145
|
}
|
|
121
|
-
async
|
|
122
|
-
this.requireWritable();
|
|
123
|
-
const terminal = (options?.terminal ?? true) || !this.allowNonTerminalFailures;
|
|
124
|
-
if (terminal)
|
|
125
|
-
this.terminal = true;
|
|
126
|
-
await this.enqueue(new StreamEnvelope('failure', undefined, ApiErrorCodec_1.ApiErrorCodec.encode(error), correlation, terminal));
|
|
127
|
-
}
|
|
128
|
-
async complete() {
|
|
146
|
+
async close() {
|
|
129
147
|
this.requireWritable();
|
|
130
148
|
this.terminal = true;
|
|
131
149
|
await this.enqueue(new StreamEnvelope('complete'));
|
|
132
150
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
this.cancelHandlers.push(handler);
|
|
136
|
-
}
|
|
137
|
-
// webpieces-disable no-any-unknown -- cancellation reasons are deliberately transport-neutral
|
|
138
|
-
async cancel(reason) {
|
|
139
|
-
if (this.cancelled || this.terminal)
|
|
151
|
+
async cancel(error) {
|
|
152
|
+
if (this.cancelled)
|
|
140
153
|
return;
|
|
141
154
|
this.cancelled = true;
|
|
155
|
+
this.failure = error;
|
|
142
156
|
this.terminal = true;
|
|
143
|
-
|
|
144
|
-
await
|
|
157
|
+
if (this.cancelSink) {
|
|
158
|
+
await this.cancelSink(error);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (error) {
|
|
162
|
+
await this.enqueue(new StreamEnvelope('failure', undefined, ApiErrorCodec_1.ApiErrorCodec.encode(error), undefined, true));
|
|
163
|
+
}
|
|
145
164
|
}
|
|
146
165
|
requireWritable() {
|
|
147
|
-
if (this.
|
|
166
|
+
if (this.failure)
|
|
167
|
+
throw this.failure;
|
|
168
|
+
if (this.terminal)
|
|
148
169
|
throw new StreamTransportError('Cannot write after stream termination.');
|
|
149
170
|
}
|
|
150
171
|
enqueue(envelope) {
|
|
151
|
-
const delivery = this.pending.then(async () =>
|
|
172
|
+
const delivery = this.pending.then(async () => {
|
|
173
|
+
if (this.failure && envelope.kind !== 'failure')
|
|
174
|
+
throw this.failure;
|
|
175
|
+
await this.sink(envelope);
|
|
176
|
+
});
|
|
152
177
|
this.pending = delivery.catch(() => undefined);
|
|
153
178
|
// webpieces-disable no-any-unknown -- transport boundary normalizes arbitrary sink failures
|
|
154
179
|
return delivery.catch((err) => {
|
|
155
180
|
this.terminal = true;
|
|
156
181
|
const error = (0, errorUtils_1.toError)(err);
|
|
182
|
+
this.failure = error;
|
|
183
|
+
if (this.cancelSink) {
|
|
184
|
+
return this.cancel(error).then(() => {
|
|
185
|
+
throw error;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
157
188
|
if (error instanceof StreamTransportError)
|
|
158
189
|
throw error;
|
|
159
190
|
throw new StreamTransportError('Stream transport could not acknowledge a write.', envelope.correlation, envelope.correlation?.requestId, error);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StreamingContract.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/StreamingContract.ts"],"names":[],"mappings":"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","sourcesContent":["import 'reflect-metadata';\nimport { ApiConnectionError } from '../errors/ApiError';\nimport { ApiErrorCodec, ApiErrorPayload } from '../errors/ApiErrorCodec';\nimport { ApiJsonSchema, ApiJsonSchemaValidator, DtoValue } from '../mcp/DtoSchema';\nimport { toError } from '../lib/errorUtils';\nimport { METADATA_KEYS } from './decorators';\n\n/** Stable application-level key for relating stream events and failures. */\nexport class StreamCorrelation {\n constructor(\n public readonly key: string | number,\n public readonly requestId?: string,\n ) {}\n}\n\nexport interface StreamFailureOptions {\n /** Defaults to true. Wire adapters may promote false to true when continuation is unsafe. */\n terminal?: boolean;\n}\n\n/** Server-to-client half of a typed stream. Every write is an explicit backpressure boundary. */\nexport interface ResponseStream<T> {\n event(value: T, correlation?: StreamCorrelation): Promise<void>;\n /**\n * Publish a failure on this stream. Takes `Error`, the parent of everything: `ApiError` is a\n * CONVENIENCE taxonomy webpieces ships so the common cases are easy, never something the\n * framework may demand from an application. An app's own `MyLibError extends Error` is\n * published as kind `implementation` with generic text, exactly as on every other boundary.\n *\n * This is the FAITHFUL {@link ApiErrorCodec} encode, NOT `ApiErrorBoundary.encode`, and that is\n * deliberate: a stream failure is delivered IN BAND to a peer that is reading the stream, so a\n * {@link StreamTransportError} (an `ApiConnectionError`) must arrive as kind `connection` for\n * the peer to reconstruct it as the transport failure it is. Republishing it as\n * `implementation` — right at an HTTP/MCP edge, where the caller is asking us to do work — would\n * tell the peer the wrong thing about a stream it is holding one end of.\n */\n fail(\n error: Error,\n correlation?: StreamCorrelation,\n options?: StreamFailureOptions,\n ): Promise<void>;\n complete(): Promise<void>;\n // webpieces-disable no-any-unknown -- cancellation reasons are deliberately transport-neutral\n onCancel(handler: (reason?: unknown) => void | Promise<void>): void;\n}\n\n/** Client-to-server half. Cancellation exists immediately and is independent of writer readiness. */\nexport interface RequestStream<T> {\n event(value: T, correlation?: StreamCorrelation): Promise<void>;\n /**\n * Publish a failure on this stream. Takes `Error`, the parent of everything: `ApiError` is a\n * CONVENIENCE taxonomy webpieces ships so the common cases are easy, never something the\n * framework may demand from an application. An app's own `MyLibError extends Error` is\n * published as kind `implementation` with generic text, exactly as on every other boundary.\n *\n * This is the FAITHFUL {@link ApiErrorCodec} encode, NOT `ApiErrorBoundary.encode`, and that is\n * deliberate: a stream failure is delivered IN BAND to a peer that is reading the stream, so a\n * {@link StreamTransportError} (an `ApiConnectionError`) must arrive as kind `connection` for\n * the peer to reconstruct it as the transport failure it is. Republishing it as\n * `implementation` — right at an HTTP/MCP edge, where the caller is asking us to do work — would\n * tell the peer the wrong thing about a stream it is holding one end of.\n */\n fail(\n error: Error,\n correlation?: StreamCorrelation,\n options?: StreamFailureOptions,\n ): Promise<void>;\n complete(): Promise<void>;\n // webpieces-disable no-any-unknown -- cancellation reasons are deliberately transport-neutral\n cancel(reason?: unknown): Promise<void>;\n}\n\nexport type StreamEnvelopeKind = 'event' | 'failure' | 'complete';\n\n/** Transport-neutral record used by NDJSON and SSE adapters. */\nexport class StreamEnvelope<T = DtoValue> {\n constructor(\n public readonly kind: StreamEnvelopeKind,\n public readonly value?: T,\n public readonly error?: ApiErrorPayload,\n public readonly correlation?: StreamCorrelation,\n public readonly terminal?: boolean,\n ) {}\n}\n\n/** Local failure where no legal in-band envelope could be sent or decoded. */\nexport class StreamTransportError extends ApiConnectionError {\n constructor(\n message: string,\n public readonly correlation?: StreamCorrelation,\n public readonly requestId?: string,\n cause?: Error,\n ) {\n super(message, cause);\n }\n}\n\n/**\n * Runtime schema and wire-policy metadata for one streaming method.\n *\n * It holds the two event SCHEMAS rather than two DTO classes. Until #984 it held classes and built\n * their schemas from `@WpDtoField` at decoration time; that decorator is deleted, because the\n * compiler already knows every fact it restated (#983). A schema is the thing a boundary validates\n * against, so it is the thing the metadata carries.\n */\nexport class StreamingEndpointMetadata {\n constructor(\n public readonly requestSchema: ApiJsonSchema,\n public readonly responseSchema: ApiJsonSchema,\n /** Generic NDJSON/SSE supports non-terminal failures in both directions. */\n public readonly supportsNonTerminalFailures = true,\n ) {}\n}\n\n/**\n * Marks `(ResponseStream<ResponseEvent>) => Promise<RequestStream<RequestEvent>>`, with the two\n * event schemas the wire adapters validate against.\n *\n * Build them with {@link ObjectSchemaBuilder}, or read them out of the build's generated model.\n */\n// webpieces-disable no-function-outside-class -- decorator factory\nexport function WpStream(\n requestEventSchema: ApiJsonSchema,\n responseEventSchema: ApiJsonSchema,\n): MethodDecorator {\n return (target: object, propertyKey: string | symbol): void => {\n const apiClass = target.constructor;\n const methods: Record<string, StreamingEndpointMetadata> =\n Reflect.getMetadata(METADATA_KEYS.STREAM_ENDPOINTS, apiClass) ?? {};\n methods[String(propertyKey)] = new StreamingEndpointMetadata(\n requestEventSchema,\n responseEventSchema,\n );\n Reflect.defineMetadata(METADATA_KEYS.STREAM_ENDPOINTS, methods, apiClass);\n };\n}\n\n// webpieces-disable no-function-outside-class -- metadata reader paired with WpStream\nexport function getStreamingEndpoint(\n apiClass: Function,\n methodName: string,\n): StreamingEndpointMetadata | undefined {\n const methods: Record<string, StreamingEndpointMetadata> =\n Reflect.getMetadata(METADATA_KEYS.STREAM_ENDPOINTS, apiClass) ?? {};\n return methods[methodName];\n}\n\n/** Validate a typed event at every adapter boundary, with one consistent diagnostic. */\nexport class StreamEventValidator {\n private readonly schemas = new ApiJsonSchemaValidator();\n\n // webpieces-disable no-any-unknown -- DTO validation is the runtime narrowing boundary\n validate(schema: ApiJsonSchema, value: unknown, direction: 'request' | 'response'): void {\n const failure = this.schemas.validate(schema, value as DtoValue);\n if (failure)\n throw new StreamTransportError(`Invalid ${direction} stream event: ${failure.message}`);\n }\n}\n\n/** Awaiting the sink acknowledges the write, so adapters cannot outrun their consumer. */\nexport class StreamWriter<T> implements RequestStream<T>, ResponseStream<T> {\n private terminal = false;\n private cancelled = false;\n private pending: Promise<void> = Promise.resolve();\n // webpieces-disable no-any-unknown -- cancellation reasons are deliberately transport-neutral\n private readonly cancelHandlers: Array<(reason?: unknown) => void | Promise<void>> = [];\n\n constructor(\n private readonly sink: (envelope: StreamEnvelope<T>) => Promise<void>,\n private readonly validateEvent?: (value: T) => void,\n private readonly allowNonTerminalFailures = true,\n ) {}\n\n async event(value: T, correlation?: StreamCorrelation): Promise<void> {\n this.requireWritable();\n this.validateEvent?.(value);\n await this.enqueue(new StreamEnvelope('event', value, undefined, correlation));\n }\n\n async fail(\n error: Error,\n correlation?: StreamCorrelation,\n options?: StreamFailureOptions,\n ): Promise<void> {\n this.requireWritable();\n const terminal = (options?.terminal ?? true) || !this.allowNonTerminalFailures;\n if (terminal) this.terminal = true;\n await this.enqueue(\n new StreamEnvelope<T>(\n 'failure',\n undefined,\n ApiErrorCodec.encode(error),\n correlation,\n terminal,\n ),\n );\n }\n\n async complete(): Promise<void> {\n this.requireWritable();\n this.terminal = true;\n await this.enqueue(new StreamEnvelope<T>('complete'));\n }\n\n // webpieces-disable no-any-unknown -- cancellation reasons are deliberately transport-neutral\n onCancel(handler: (reason?: unknown) => void | Promise<void>): void {\n this.cancelHandlers.push(handler);\n }\n\n // webpieces-disable no-any-unknown -- cancellation reasons are deliberately transport-neutral\n async cancel(reason?: unknown): Promise<void> {\n if (this.cancelled || this.terminal) return;\n this.cancelled = true;\n this.terminal = true;\n for (const handler of this.cancelHandlers) await handler(reason);\n }\n\n private requireWritable(): void {\n if (this.terminal || this.cancelled)\n throw new StreamTransportError('Cannot write after stream termination.');\n }\n\n private enqueue(envelope: StreamEnvelope<T>): Promise<void> {\n const delivery = this.pending.then(async () => this.sink(envelope));\n this.pending = delivery.catch(() => undefined);\n // webpieces-disable no-any-unknown -- transport boundary normalizes arbitrary sink failures\n return delivery.catch((err: unknown) => {\n this.terminal = true;\n const error = toError(err);\n if (error instanceof StreamTransportError) throw error;\n throw new StreamTransportError(\n 'Stream transport could not acknowledge a write.',\n envelope.correlation,\n envelope.correlation?.requestId,\n error,\n );\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"StreamingContract.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/StreamingContract.ts"],"names":[],"mappings":"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","sourcesContent":["import 'reflect-metadata';\nimport { ApiConnectionError } from '../errors/ApiError';\nimport { ApiErrorCodec, ApiErrorPayload } from '../errors/ApiErrorCodec';\nimport { ApiJsonSchema, ApiJsonSchemaValidator, DtoValue } from '../mcp/DtoSchema';\nimport { toError } from '../lib/errorUtils';\nimport { METADATA_KEYS } from './decorators';\n\n/** Stable application-level key for relating stream events and failures. */\nexport class StreamCorrelation {\n constructor(\n public readonly key: string | number,\n public readonly requestId?: string,\n ) {}\n}\n\n/** Server-to-client half of a typed stream. Every write is an explicit backpressure boundary. */\nexport enum StreamDirection {\n RESPONSE = 'response',\n REQUEST = 'request',\n FULL = 'full',\n}\n\nexport interface ResponseStream<T> {\n event(value: T, correlation?: StreamCorrelation): Promise<void>;\n /** Graceful directional EOF after all queued records are acknowledged. */\n close(): Promise<void>;\n /** Abnormal terminal cancellation of the logical exchange. */\n cancel(error?: Error): Promise<void>;\n}\n\n/** Client-to-server half. Cancellation exists immediately and is independent of writer readiness. */\nexport interface RequestStream<TInitialResponse, T> {\n /** The typed opening acknowledgement received before later request events are writable. */\n getInitialResponse(): TInitialResponse;\n event(value: T, correlation?: StreamCorrelation): Promise<void>;\n close(): Promise<void>;\n cancel(error?: Error): Promise<void>;\n}\n\nexport type StreamEnvelopeKind = 'event' | 'failure' | 'complete';\n\n/** Internal acknowledgement records; these are never serialized around application JSONL DTOs. */\nexport class StreamEnvelope<T = DtoValue> {\n constructor(\n public readonly kind: StreamEnvelopeKind,\n public readonly value?: T,\n public readonly error?: ApiErrorPayload,\n public readonly correlation?: StreamCorrelation,\n public readonly terminal?: boolean,\n ) {}\n}\n\n/** Local failure where no legal in-band envelope could be sent or decoded. */\nexport class StreamTransportError extends ApiConnectionError {\n constructor(\n message: string,\n public readonly correlation?: StreamCorrelation,\n public readonly requestId?: string,\n cause?: Error,\n ) {\n super(message, cause);\n }\n}\n\n/**\n * Runtime schema and wire-policy metadata for one streaming method.\n *\n * The build-time API model derives initial and event schemas independently from the signature.\n */\nexport class StreamingEndpointMetadata {\n constructor(\n public readonly direction: StreamDirection,\n public readonly initialRequestSchema?: ApiJsonSchema,\n public readonly initialResponseSchema?: ApiJsonSchema,\n public readonly requestSchema?: ApiJsonSchema,\n public readonly responseSchema?: ApiJsonSchema,\n ) {}\n}\n\n/**\n * Declares direction; the method signature owns all four DTO types. Install its generated\n * stream-Contract-schemas.json with registerStreamingCatalog before binding the contract.\n */\n// webpieces-disable no-function-outside-class -- decorator factory is the public annotation API\nexport function WpStream(direction: StreamDirection): MethodDecorator {\n return (target: object, propertyKey: string | symbol): void => {\n const apiClass = target.constructor;\n const methods: Record<string, StreamingEndpointMetadata> =\n Reflect.getMetadata(METADATA_KEYS.STREAM_ENDPOINTS, apiClass) ?? {};\n methods[String(propertyKey)] = new StreamingEndpointMetadata(direction);\n Reflect.defineMetadata(METADATA_KEYS.STREAM_ENDPOINTS, methods, apiClass);\n };\n}\n\n/**\n * Installs the schemas emitted by the build-time API model. Kept separate from `@WpStream` so the\n * contract declaration has one source of truth: its TypeScript signature.\n */\n// webpieces-disable no-function-outside-class -- generated wiring calls this metadata registration boundary\nexport function registerStreamingSchemas(\n apiClass: Function,\n methodName: string,\n schemas: Pick<\n StreamingEndpointMetadata,\n 'initialRequestSchema' | 'initialResponseSchema' | 'requestSchema' | 'responseSchema'\n >,\n): void {\n const current = getStreamingEndpoint(apiClass, methodName);\n if (!current)\n throw new Error(`${apiClass.name}.${methodName} is not decorated with @WpStream.`);\n const methods: Record<string, StreamingEndpointMetadata> =\n Reflect.getMetadata(METADATA_KEYS.STREAM_ENDPOINTS, apiClass) ?? {};\n methods[methodName] = new StreamingEndpointMetadata(\n current.direction,\n schemas.initialRequestSchema,\n schemas.initialResponseSchema,\n schemas.requestSchema,\n schemas.responseSchema,\n );\n Reflect.defineMetadata(METADATA_KEYS.STREAM_ENDPOINTS, methods, apiClass);\n}\n\n// webpieces-disable no-function-outside-class -- metadata reader paired with WpStream\nexport function getStreamingEndpoint(\n apiClass: Function,\n methodName: string,\n): StreamingEndpointMetadata | undefined {\n const methods: Record<string, StreamingEndpointMetadata> =\n Reflect.getMetadata(METADATA_KEYS.STREAM_ENDPOINTS, apiClass) ?? {};\n return methods[methodName];\n}\n\n/** Validate a typed event at every adapter boundary, with one consistent diagnostic. */\nexport class StreamEventValidator {\n private readonly schemas = new ApiJsonSchemaValidator();\n\n // webpieces-disable no-any-unknown -- DTO validation is the runtime narrowing boundary\n validate(schema: ApiJsonSchema, value: unknown, direction: 'request' | 'response'): void {\n const failure = this.schemas.validate(schema, value as DtoValue);\n if (failure)\n throw new StreamTransportError(`Invalid ${direction} stream event: ${failure.message}`);\n }\n}\n\n/** Awaiting the sink acknowledges the write, so adapters cannot outrun their consumer. */\nexport class StreamWriter<T, TInitialResponse = T> implements ResponseStream<T> {\n private terminal = false;\n private cancelled = false;\n private failure?: Error;\n private pending: Promise<void> = Promise.resolve();\n\n constructor(\n private readonly sink: (envelope: StreamEnvelope<T>) => Promise<void>,\n private readonly validateEvent?: (value: T) => void,\n private readonly initialResponse?: TInitialResponse,\n private readonly cancelSink?: (error?: Error) => Promise<void>,\n ) {}\n\n getInitialResponse(): TInitialResponse {\n if (this.initialResponse === undefined) {\n throw new StreamTransportError('RequestStream initial response is unavailable.');\n }\n return this.initialResponse;\n }\n\n async event(value: T, correlation?: StreamCorrelation): Promise<void> {\n this.requireWritable();\n this.validateEvent?.(value);\n await this.enqueue(new StreamEnvelope('event', value, undefined, correlation));\n }\n\n async close(): Promise<void> {\n this.requireWritable();\n this.terminal = true;\n await this.enqueue(new StreamEnvelope<T>('complete'));\n }\n\n async cancel(error?: Error): Promise<void> {\n if (this.cancelled) return;\n this.cancelled = true;\n this.failure = error;\n this.terminal = true;\n if (this.cancelSink) {\n await this.cancelSink(error);\n return;\n }\n if (error) {\n await this.enqueue(\n new StreamEnvelope<T>(\n 'failure',\n undefined,\n ApiErrorCodec.encode(error),\n undefined,\n true,\n ),\n );\n }\n }\n\n private requireWritable(): void {\n if (this.failure) throw this.failure;\n if (this.terminal) throw new StreamTransportError('Cannot write after stream termination.');\n }\n\n private enqueue(envelope: StreamEnvelope<T>): Promise<void> {\n const delivery = this.pending.then(async () => {\n if (this.failure && envelope.kind !== 'failure') throw this.failure;\n await this.sink(envelope);\n });\n this.pending = delivery.catch(() => undefined);\n // webpieces-disable no-any-unknown -- transport boundary normalizes arbitrary sink failures\n return delivery.catch((err: unknown) => {\n this.terminal = true;\n const error = toError(err);\n this.failure = error;\n if (this.cancelSink) {\n return this.cancel(error).then((): never => {\n throw error;\n });\n }\n if (error instanceof StreamTransportError) throw error;\n throw new StreamTransportError(\n 'Stream transport could not acknowledge a write.',\n envelope.correlation,\n envelope.correlation?.requestId,\n error,\n );\n });\n }\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { StreamingEndpointMetadata } from './StreamingContract';
|
|
2
|
+
/** Portable build output, imported as JSON identically by browser and server bootstraps. */
|
|
3
|
+
export declare class StreamingSchemaCatalog {
|
|
4
|
+
readonly contractName: string;
|
|
5
|
+
readonly methods: Record<string, StreamingEndpointMetadata>;
|
|
6
|
+
constructor(contractName: string, methods: Record<string, StreamingEndpointMetadata>);
|
|
7
|
+
}
|
|
8
|
+
/** Installs generated runtime schemas; contract authors never repeat their DTO types here. */
|
|
9
|
+
export declare function registerStreamingCatalog(apiClass: Function, catalog: StreamingSchemaCatalog): void;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StreamingSchemaCatalog = void 0;
|
|
4
|
+
exports.registerStreamingCatalog = registerStreamingCatalog;
|
|
5
|
+
const decorators_1 = require("./decorators");
|
|
6
|
+
const StreamingContract_1 = require("./StreamingContract");
|
|
7
|
+
/** Portable build output, imported as JSON identically by browser and server bootstraps. */
|
|
8
|
+
class StreamingSchemaCatalog {
|
|
9
|
+
contractName;
|
|
10
|
+
methods;
|
|
11
|
+
constructor(contractName, methods) {
|
|
12
|
+
this.contractName = contractName;
|
|
13
|
+
this.methods = methods;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.StreamingSchemaCatalog = StreamingSchemaCatalog;
|
|
17
|
+
/** Installs generated runtime schemas; contract authors never repeat their DTO types here. */
|
|
18
|
+
// webpieces-disable no-function-outside-class -- generated catalog bootstrap is a public metadata boundary
|
|
19
|
+
function registerStreamingCatalog(apiClass, catalog) {
|
|
20
|
+
if (catalog.contractName !== apiClass.name) {
|
|
21
|
+
throw new Error(`Streaming schema catalog for ${catalog.contractName} cannot bind ${apiClass.name}.`);
|
|
22
|
+
}
|
|
23
|
+
const methods = {};
|
|
24
|
+
const declaredMethods = Reflect.getMetadata(decorators_1.METADATA_KEYS.STREAM_ENDPOINTS, apiClass) ?? {};
|
|
25
|
+
for (const methodName of Object.keys(declaredMethods)) {
|
|
26
|
+
if (!catalog.methods[methodName]) {
|
|
27
|
+
throw new Error(`Streaming catalog is missing ${apiClass.name}.${methodName}; regenerate it.`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
for (const entry of Object.entries(catalog.methods)) {
|
|
31
|
+
const declared = (0, StreamingContract_1.getStreamingEndpoint)(apiClass, entry[0]);
|
|
32
|
+
if (!declared || declared.direction !== entry[1].direction) {
|
|
33
|
+
throw new Error(`Streaming catalog direction disagrees with ${apiClass.name}.${entry[0]}; regenerate it.`);
|
|
34
|
+
}
|
|
35
|
+
methods[entry[0]] = entry[1];
|
|
36
|
+
}
|
|
37
|
+
Reflect.defineMetadata(decorators_1.METADATA_KEYS.STREAM_ENDPOINTS, methods, apiClass);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=StreamingSchemaCatalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StreamingSchemaCatalog.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/StreamingSchemaCatalog.ts"],"names":[],"mappings":";;;AAaA,4DA6BC;AA1CD,6CAA6C;AAC7C,2DAAsF;AAEtF,4FAA4F;AAC5F,MAAa,sBAAsB;IAElB;IACA;IAFb,YACa,YAAoB,EACpB,OAAkD;QADlD,iBAAY,GAAZ,YAAY,CAAQ;QACpB,YAAO,GAAP,OAAO,CAA2C;IAC5D,CAAC;CACP;AALD,wDAKC;AAED,8FAA8F;AAC9F,2GAA2G;AAC3G,SAAgB,wBAAwB,CACpC,QAAkB,EAClB,OAA+B;IAE/B,IAAI,OAAO,CAAC,YAAY,KAAK,QAAQ,CAAC,IAAI,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CACX,gCAAgC,OAAO,CAAC,YAAY,gBAAgB,QAAQ,CAAC,IAAI,GAAG,CACvF,CAAC;IACN,CAAC;IACD,MAAM,OAAO,GAA8C,EAAE,CAAC;IAC9D,MAAM,eAAe,GACjB,OAAO,CAAC,WAAW,CAAC,0BAAa,CAAC,gBAAgB,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;IACxE,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;QACpD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CACX,gCAAgC,QAAQ,CAAC,IAAI,IAAI,UAAU,kBAAkB,CAChF,CAAC;QACN,CAAC;IACL,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAClD,MAAM,QAAQ,GAAG,IAAA,wCAAoB,EAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1D,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;YACzD,MAAM,IAAI,KAAK,CACX,8CAA8C,QAAQ,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,kBAAkB,CAC5F,CAAC;QACN,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,CAAC,cAAc,CAAC,0BAAa,CAAC,gBAAgB,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;AAC9E,CAAC","sourcesContent":["import { METADATA_KEYS } from './decorators';\nimport { getStreamingEndpoint, StreamingEndpointMetadata } from './StreamingContract';\n\n/** Portable build output, imported as JSON identically by browser and server bootstraps. */\nexport class StreamingSchemaCatalog {\n constructor(\n readonly contractName: string,\n readonly methods: Record<string, StreamingEndpointMetadata>,\n ) {}\n}\n\n/** Installs generated runtime schemas; contract authors never repeat their DTO types here. */\n// webpieces-disable no-function-outside-class -- generated catalog bootstrap is a public metadata boundary\nexport function registerStreamingCatalog(\n apiClass: Function,\n catalog: StreamingSchemaCatalog,\n): void {\n if (catalog.contractName !== apiClass.name) {\n throw new Error(\n `Streaming schema catalog for ${catalog.contractName} cannot bind ${apiClass.name}.`,\n );\n }\n const methods: Record<string, StreamingEndpointMetadata> = {};\n const declaredMethods: Record<string, StreamingEndpointMetadata> =\n Reflect.getMetadata(METADATA_KEYS.STREAM_ENDPOINTS, apiClass) ?? {};\n for (const methodName of Object.keys(declaredMethods)) {\n if (!catalog.methods[methodName]) {\n throw new Error(\n `Streaming catalog is missing ${apiClass.name}.${methodName}; regenerate it.`,\n );\n }\n }\n for (const entry of Object.entries(catalog.methods)) {\n const declared = getStreamingEndpoint(apiClass, entry[0]);\n if (!declared || declared.direction !== entry[1].direction) {\n throw new Error(\n `Streaming catalog direction disagrees with ${apiClass.name}.${entry[0]}; regenerate it.`,\n );\n }\n methods[entry[0]] = entry[1];\n }\n Reflect.defineMetadata(METADATA_KEYS.STREAM_ENDPOINTS, methods, apiClass);\n}\n"]}
|
|
@@ -35,6 +35,8 @@ export declare class WebpiecesCoreHeaders {
|
|
|
35
35
|
* and still adopts an id when a caller somehow had none.
|
|
36
36
|
*/
|
|
37
37
|
static readonly REQUEST_ID: ContextKey<string, "untrusted">;
|
|
38
|
+
/** Framework-minted identity for one inbound streaming event; never accepted from HTTP. */
|
|
39
|
+
static readonly STREAM_EVENT_ID: ContextKey<string, "untrusted">;
|
|
38
40
|
/**
|
|
39
41
|
* WHICH SERVICE MINTED {@link REQUEST_ID} — the name from {@link ServiceInfo}, stamped by
|
|
40
42
|
* `RequestContextHeaders.fillFromRequest` ONLY on the branch that generates a new id (i.e. when
|
|
@@ -40,6 +40,8 @@ class WebpiecesCoreHeaders {
|
|
|
40
40
|
/*isLogged*/ true,
|
|
41
41
|
/*responseHeader*/ 'x-request-id',
|
|
42
42
|
/*responseMerge*/ 'first');
|
|
43
|
+
/** Framework-minted identity for one inbound streaming event; never accepted from HTTP. */
|
|
44
|
+
static STREAM_EVENT_ID = ContextKey_1.ContextKey.untrusted('streamEventId', undefined, false, true);
|
|
43
45
|
/**
|
|
44
46
|
* WHICH SERVICE MINTED {@link REQUEST_ID} — the name from {@link ServiceInfo}, stamped by
|
|
45
47
|
* `RequestContextHeaders.fillFromRequest` ONLY on the branch that generates a new id (i.e. when
|
|
@@ -285,6 +287,7 @@ class WebpiecesCoreHeaders {
|
|
|
285
287
|
*/
|
|
286
288
|
static ALL_HEADERS = [
|
|
287
289
|
WebpiecesCoreHeaders.REQUEST_ID,
|
|
290
|
+
WebpiecesCoreHeaders.STREAM_EVENT_ID,
|
|
288
291
|
WebpiecesCoreHeaders.REQUEST_ID_SOURCE,
|
|
289
292
|
WebpiecesCoreHeaders.CLIENT_VERSION,
|
|
290
293
|
WebpiecesCoreHeaders.ACTION_ID,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebpiecesCoreHeaders.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WebpiecesCoreHeaders.ts"],"names":[],"mappings":"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","sourcesContent":["import { ContextKey, AnyContextKey } from '../ContextKey';\nimport { ApiCallInfo } from './ApiCallInfo';\nimport { Surface } from './Surface';\n\n/**\n * Core framework context keys — the minimum the WebPieces framework needs to correlate one\n * request across every service it touches, and across every log line each of them writes.\n *\n * ONE id, propagated unchanged. The first service to see a request without an `x-request-id`\n * generates one (RequestContextHeaders.fillFromRequest); every hop copies it onward verbatim. Grep that id and you\n * have the whole call tree. There is no per-hop id and no parent pointer: a chain of ids you must\n * stitch back together buys nothing a single shared id does not already give you.\n *\n * Lives in core-util (browser-safe) so both the http clients and http-server can reference it.\n *\n * Exposed as {@link HeaderRegistry.DEFAULT_HEADERS} — a service opts into these by\n * passing `platformHeaders=true` to `HeaderRegistry.configure(...)`.\n *\n * Each key's `name` is the logical/log name; `httpHeader` is the wire name.\n */\nexport class WebpiecesCoreHeaders {\n /**\n * The id that correlates every hop of one request, and every log line of every hop.\n * Generated by the first service to see a request without one; propagated unchanged after that.\n *\n * - `httpHeader` `x-request-id` → travels DOWN the call tree on every outbound request.\n * - `responseHeader` `x-request-id` → and back UP on every response, which is the header a\n * support desk quotes. This used to be a hard-coded `res.setHeader` in `ExpressWrapper`\n * (`stampTransactionId`), reachable by no other key. It is an ordinary registry entry now: the\n * server's ONE response choke point writes every key in\n * {@link HeaderRegistry.getResponseTxfrKeys}, so a second key joins with no framework edit.\n * - `responseMerge` `first` → and NOT `collect`. A callee echoes OUR id back at us, so the\n * caller's context must KEEP what it already has. `collect` would turn `requestId` into a\n * growing array on every client call and break every log line of the request; `last` would\n * rewrite it with an echo. `first` makes reading it back a provable no-op for the normal case\n * and still adopts an id when a caller somehow had none.\n */\n static readonly REQUEST_ID = ContextKey.untrusted<string>(\n 'requestId',\n 'x-request-id',\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n /*responseHeader*/ 'x-request-id',\n /*responseMerge*/ 'first',\n );\n\n /**\n * WHICH SERVICE MINTED {@link REQUEST_ID} — the name from {@link ServiceInfo}, stamped by\n * `RequestContextHeaders.fillFromRequest` ONLY on the branch that generates a new id (i.e. when\n * the inbound request carried no `x-request-id`). It answers the question the id alone cannot:\n * \"this trace starts here — is that right?\" An id appearing with no source means it came from\n * outside; an id sourced by a service that should never be an entry point is a routing bug.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire, and that is the WHOLE POINT. If it\n * travelled, hop 2 would inherit it, hop 3 would inherit it, and \"who started this trace\"\n * would be indistinguishable from \"who passed it along\" — the origin, the one fact this key\n * carries, would be lost. It is absent on every hop that did NOT mint the id, which is exactly\n * the signal: present == I am the origin.\n * - `isLogged` TRUE → emitted as a plain string at `jsonPayload.requestIdSource`.\n */\n static readonly REQUEST_ID_SOURCE = ContextKey.untrusted<string>(\n 'requestIdSource',\n /*httpHeader*/ undefined,\n );\n\n /**\n * The CALLER's build version — so a downstream server's logs record which build of the client\n * called it (surfaces as `jsonPayload.clientVersion`). Distinct from the log line's own `version`\n * (this service's build): `version` answers \"which build wrote this line?\", `clientVersion`\n * answers \"which build asked us to?\".\n *\n * - `httpHeader` SET → transferred over the wire, BUT unlike a normal transferred key it is NOT\n * copied from the context onward. Each hop OVERWRITES it with its OWN `ServiceInfo.getVersion()`\n * as it becomes the client to the next hop (see `buildOutboundHeaders`), so on any given server\n * `clientVersion` is always the IMMEDIATE caller's version, never a stale grand-caller's.\n * - `isLogged` TRUE → the inbound value lands in the context and flows through the normal log\n * field map; no backend change needed.\n */\n static readonly CLIENT_VERSION = ContextKey.untrusted<string>(\n 'clientVersion',\n 'x-webpieces-client-version',\n );\n\n /**\n * A frontend/app-minted correlation id that groups every request triggered by ONE user ACTION.\n *\n * An \"action\" is a single thing the user did in the GUI — a CLICK on a button/link, or TYPING in a\n * field — or a background poller tick: anything that may fan out into MULTIPLE remote calls. That one\n * action fires 1..N browser HTTP calls, each of which gets its own framework-minted {@link REQUEST_ID}\n * (one per HTTP call, shared within that call's server→server subtree). `actionId` sits ABOVE\n * `requestId` and is what stitches those N requests back to the single action that caused them:\n *\n * actionId (app-minted, ONE per user action, rides EVERY call of that action)\n * └── 1..N requestId (framework-minted, ONE per HTTP call)\n *\n * Grep one `actionId` in the logs → every `requestId` it spawned, and every log line of the whole\n * action. Minted and refreshed by the app (a UI concern), carried under `x-webpieces-actionid`.\n *\n * Browser/app-minted ONLY: unlike {@link REQUEST_ID}, the framework transfers and logs it but must\n * NOT auto-mint one server-side. Absent `actionId` ⇒ a non-action flow (system / cron / task), which\n * is the correct signal.\n *\n * - `httpHeader` SET → transferred: copied off the inbound request into context and re-emitted on\n * outbound hops, so the id follows the action across services.\n * - `isLogged` TRUE → emitted as a plain string on every log line of the request.\n */\n static readonly ACTION_ID = ContextKey.untrusted<string>('actionId', 'x-webpieces-actionid');\n\n /**\n * WHO the request is acting as, and WHAT they may do. All three are TRUSTED keys: they are the\n * inputs to authorization decisions, so a reader must be able to tell \"the framework proved\n * this\" from \"the caller typed this\" — see the trust section of the {@link ContextKey} doc.\n *\n * They keep their `httpHeader`, because propagating a verified identity to the next internal\n * service is the point. What makes that safe is not the header being absent, it is WHO is\n * allowed to have set it: an inbound value is held PENDING by\n * `RequestContextHeaders.fillFromRequest` and admitted by `AuthFilter` only on a route that\n * verified its CALLER (`@WpAuthOidc` / `@WpAuthSharedSecret`). On a browser-reachable route\n * (`@WpAuthJwt` / public) the value must match what the authenticator itself derived, or the\n * request is rejected.\n *\n * `provenance` says \"an app-bound JwtHook\" rather than naming one hook, because the framework\n * default ({@link DefaultJwtHook}) stamps NO context entries at all — an app supplies a hook that\n * returns {@link ContextTuple}s for the keys it can vouch for. Any of these three that an app's\n * hook does NOT stamp will be rejected when a caller supplies it, which is the correct and loud\n * outcome: nothing is vouching for it.\n */\n static readonly ORG_ID = ContextKey.trusted<string>(\n 'orgId',\n 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthenticatedCaller)',\n 'x-org-id',\n );\n\n static readonly USER_ID = ContextKey.trusted<string>(\n 'userId',\n 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthenticatedCaller)',\n 'x-user-id',\n );\n\n static readonly USER_ROLES = ContextKey.trusted<string>(\n 'roles',\n 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthenticatedCaller)',\n 'x-webpieces-roles',\n );\n\n /**\n * WHICH KIND OF CALLER is driving this request — `gui`, `llm` or `public-api`. See\n * {@link Surface} for the table and for why it is a property of the CALLER and not of the server.\n *\n * TRUSTED, and that is load-bearing twice over. It is what makes an app safe to branch on it in\n * its OWN authorization logic (\"this endpoint is not exposed to partners\"), and it is what makes\n * the value in {@link SurfaceEndUserStatus} believable: webpieces sets it itself, from the auth\n * mode that matched, never from caller input. A caller that simply SENDS `x-wp-surface` at an\n * endpoint whose auth did not establish one is rejected by `AuthFilter.reconcileWireTrust`, like\n * every other unvouched trusted header.\n *\n * It keeps its `httpHeader` because PROPAGATION is the point: the surface belongs to the ORIGINAL\n * caller, so `GUI -> api1 -> api2` must leave api2 seeing `gui`. An internal `@WpAuthOidc` hop\n * verifies its caller, so the propagated value is admitted verbatim — and the auth filter only\n * ever SETS a surface when none arrived, so an inherited one is never overwritten.\n */\n static readonly SURFACE = ContextKey.trusted<Surface>(\n 'surface',\n 'set by the webpieces AuthFilter from the auth mode that matched the incoming request, or inherited from the edge hop that did',\n 'x-wp-surface',\n );\n\n /**\n * Turns on test-case recording for this request (Java: x-webpieces-recording).\n * Transferred so recording follows the request across service hops.\n */\n static readonly RECORDING = ContextKey.untrusted<string>('recording', 'x-webpieces-recording');\n\n /**\n * The structured API-call tag ({@link ApiCallInfo}) stamped by {@link LogApiCallImpl} around every\n * outbound (client) / inbound (server) call. It rides the magic context so EVERY log line emitted\n * during the call inherits a filterable `api` object, surfacing in GCP as nested\n * `jsonPayload.api.{side,type,result,path,method}`.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire. Per-hop only: each server/client hop\n * stamps its own tag, so a downstream server records `side:'server'`, never the caller's `side:'client'`.\n * - `isLogged` TRUE → emitted by the logging backends. It carries an OBJECT value, so the backends\n * read it via {@link HeaderRegistry.buildStructuredLogFields} (object-aware); the flat\n * `buildLogFields()` string map deliberately skips it (typeof-string guard).\n */\n static readonly API_CALL_INFO = ContextKey.untrusted<ApiCallInfo>(\n 'api',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * The inbound request's HTTP method and path, stamped ONCE from the {@link HttpRequest} by\n * `RequestContextHeaders.fillFromRequest` (the atomic inbound choke point every transport funnels\n * through). They surface as top-level `jsonPayload.httpMethod` / `jsonPayload.requestPath` so every\n * log line of the request carries them — they used to ride inside {@link ApiCallInfo} (`api.path` /\n * `api.method`) but that coupled a per-CALL logger to the per-REQUEST transport shape.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire: a downstream hop stamps its OWN inbound\n * method/path, never the caller's. Outbound client calls never set these (no inbound path).\n * - `isLogged` TRUE → emitted by the logging backends as plain strings.\n */\n static readonly HTTP_METHOD = ContextKey.untrusted<string>(\n 'httpMethod',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n static readonly REQUEST_PATH = ContextKey.untrusted<string>(\n 'requestPath',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * The routed endpoint's IMPLEMENTATION identity: the concrete controller class name\n * ({@link RouteMetadata.controllerClassName}, e.g. `LoginController`) and the handler method NAME\n * ({@link RouteMetadata.methodName}, e.g. `login`), stamped once per request by {@link LogApiFilter}\n * after route matching so every subsequent log line of the request carries them.\n *\n * These say WHICH CODE ran, which is what you actually grep for — far more useful than the raw\n * `requestPath`. They are the top-level, filterable twin of what previously only lived nested in\n * {@link ApiCallInfo} (`api.method.controllerName` / `api.method.methodName`). The local console\n * formatters render them together as a compact `[Controller.method]` bracket; GCP keeps them as two\n * separate `jsonPayload.controller` / `jsonPayload.method` fields.\n *\n * NOTE: `method` here is the CODE method name (e.g. `login`), NOT the HTTP verb — that is\n * {@link HTTP_METHOD} (`httpMethod`).\n *\n * - `httpHeader` UNDEFINED → NOT transferred: each hop stamps its OWN routed controller/method.\n * - `isLogged` TRUE → emitted by the logging backends as plain strings.\n */\n static readonly CONTROLLER = ContextKey.untrusted<string>(\n 'controller',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n static readonly METHOD = ContextKey.untrusted<string>(\n 'method',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * The base URL ONE outbound call should go to, overriding whatever the client's `ClientConfig`\n * bound at construction — the answer to \"POST our published contract to a URL the PARTNER\n * registered at runtime\" (an `OrganizationWebhook.url` column, an OAuth callback, a per-tenant\n * or self-hosted host). The destination is DATA, not deployment, so it cannot be a svcName and\n * there is nothing to register in {@link ClientRegistry}.\n *\n * ```ts\n * RequestContext.run(() => {\n * RequestContext.putUntrusted(WebpiecesCoreHeaders.OVERRIDE_BASE_URL, webhook.url);\n * return partnerWebhookClient.deliver(envelope);\n * });\n * ```\n *\n * The contract's path IS appended to it (base + `@ApiPath` + `@Endpoint`). When the stored value\n * is a complete url — the usual partner-webhook row — use {@link OVERRIDE_FULL_URL} instead.\n *\n * ONLY a client carrying a `ContextBaseUrlFilter` reads it. Every other client IGNORES this key\n * entirely, which is what stops an ambient value re-pointing every other client in the same\n * fan-out loop at a partner's server. Installing that ONE filter IS the opt-in, so\n * `grep -rn ContextBaseUrlFilter` enumerates every client that can be re-pointed at all.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire, and that is load-bearing. This value\n * names where THIS hop goes. If it travelled, the callee would inherit it and re-point ITS\n * own outbound calls at the same host — one partner-supplied URL turning into an SSRF pivot\n * across the whole call tree. It is per-hop, always.\n * - `isLogged` TRUE → the destination of a partner delivery is exactly what you want in the log\n * line when one fails.\n *\n * UNTRUSTED, necessarily: it comes from a database column a partner edited. That is precisely\n * why re-pointing a request ARMS the framework's SSRF guard, automatically, rather than trusting\n * it — the guard sits beneath every app filter and reads the fact that the request was moved.\n */\n static readonly OVERRIDE_BASE_URL = ContextKey.untrusted<string>(\n 'overrideBaseUrl',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * The COMPLETE url — host, path AND query — ONE outbound call should be sent to, verbatim. The\n * sibling of {@link OVERRIDE_BASE_URL} for the other shape of runtime destination: a partner\n * that does not implement our contract at their own host, but hands us one opaque url to POST\n * to (`https://hooks.partner.example/in/abc?token=xyz`, straight out of a webhook row). With a\n * base-URL override the contract's path would be appended to it and the url would be wrong; with\n * this one the contract's path is NOT appended and nothing is inserted.\n *\n * ```ts\n * RequestContext.run(() => {\n * RequestContext.putUntrusted(WebpiecesCoreHeaders.OVERRIDE_FULL_URL, webhook.url);\n * return partnerWebhookClient.deliver(envelope);\n * });\n * ```\n *\n * ONLY a client carrying a `ContextFullUrlFilter` reads it, for exactly the reasons given on\n * {@link OVERRIDE_BASE_URL}: installing that filter IS the opt-in, and every other client ignores\n * the key. Not transferred over the wire (per-hop, or it becomes an SSRF pivot across the call\n * tree), logged (it is what you want in a failed-delivery log line), and UNTRUSTED (a partner\n * edited it) — which is why re-pointing at it arms the framework's SSRF guard automatically.\n */\n static readonly OVERRIDE_FULL_URL = ContextKey.untrusted<string>(\n 'overrideFullUrl',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * NO CREDENTIAL KEYS LIVE HERE.\n *\n * `authorization` and `x-webpieces-shared-secret` used to be ContextKeys. That made them\n * TRANSFERRED keys, so the inbound transfer copied them off the request into the\n * RequestContext, and every outbound RPC call and enqueued Cloud Task then carried the\n * caller's credential onward — to services that had no business seeing it.\n *\n * A credential belongs to ONE request hop. It is read straight off the {@link HttpRequest}\n * by the framework AuthFilter, and written straight onto the outbound request by the client\n * that mints it (NodeProxyClient, GcpTaskInvoker, InMemoryTaskInvoker). It never enters the\n * magic context, so nothing can propagate it by accident.\n *\n * An app that genuinely wants a credential to travel can still register its own ContextKey for\n * it — but that is now an explicit, visible decision rather than the default.\n */\n\n /**\n * All core context keys (the platform DEFAULT_HEADERS set). A `static readonly` CONSTANT, not a\n * method: it is compile-time data — a list of the key definitions above — read once at the startup\n * composition root (`HeaderRegistry.configure` / `HeaderRegistry.DEFAULT_HEADERS`). A method here\n * would be un-injectable behavior the DI design graph can't reach; a constant is honest data.\n */\n static readonly ALL_HEADERS: AnyContextKey[] = [\n WebpiecesCoreHeaders.REQUEST_ID,\n WebpiecesCoreHeaders.REQUEST_ID_SOURCE,\n WebpiecesCoreHeaders.CLIENT_VERSION,\n WebpiecesCoreHeaders.ACTION_ID,\n WebpiecesCoreHeaders.USER_ID,\n WebpiecesCoreHeaders.ORG_ID,\n WebpiecesCoreHeaders.USER_ROLES,\n WebpiecesCoreHeaders.SURFACE,\n WebpiecesCoreHeaders.RECORDING,\n WebpiecesCoreHeaders.API_CALL_INFO,\n WebpiecesCoreHeaders.HTTP_METHOD,\n WebpiecesCoreHeaders.REQUEST_PATH,\n WebpiecesCoreHeaders.CONTROLLER,\n WebpiecesCoreHeaders.METHOD,\n WebpiecesCoreHeaders.OVERRIDE_BASE_URL,\n WebpiecesCoreHeaders.OVERRIDE_FULL_URL,\n ];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"WebpiecesCoreHeaders.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WebpiecesCoreHeaders.ts"],"names":[],"mappings":";;;AAAA,8CAA0D;AAI1D;;;;;;;;;;;;;;;GAeG;AACH,MAAa,oBAAoB;IAC7B;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAU,UAAU,GAAG,uBAAU,CAAC,SAAS,CAC7C,WAAW,EACX,cAAc;IACd,cAAc,CAAC,KAAK;IACpB,YAAY,CAAC,IAAI;IACjB,kBAAkB,CAAC,cAAc;IACjC,iBAAiB,CAAC,OAAO,CAC5B,CAAC;IAEF,2FAA2F;IAC3F,MAAM,CAAU,eAAe,GAAG,uBAAU,CAAC,SAAS,CAClD,eAAe,EACf,SAAS,EACT,KAAK,EACL,IAAI,CACP,CAAC;IAEF;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAU,iBAAiB,GAAG,uBAAU,CAAC,SAAS,CACpD,iBAAiB;IACjB,cAAc,CAAC,SAAS,CAC3B,CAAC;IAEF;;;;;;;;;;;;OAYG;IACH,MAAM,CAAU,cAAc,GAAG,uBAAU,CAAC,SAAS,CACjD,eAAe,EACf,4BAA4B,CAC/B,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,MAAM,CAAU,SAAS,GAAG,uBAAU,CAAC,SAAS,CAAS,UAAU,EAAE,sBAAsB,CAAC,CAAC;IAE7F;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAU,MAAM,GAAG,uBAAU,CAAC,OAAO,CACvC,OAAO,EACP,oGAAoG,EACpG,UAAU,CACb,CAAC;IAEF,MAAM,CAAU,OAAO,GAAG,uBAAU,CAAC,OAAO,CACxC,QAAQ,EACR,oGAAoG,EACpG,WAAW,CACd,CAAC;IAEF,MAAM,CAAU,UAAU,GAAG,uBAAU,CAAC,OAAO,CAC3C,OAAO,EACP,oGAAoG,EACpG,mBAAmB,CACtB,CAAC;IAEF;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAU,OAAO,GAAG,uBAAU,CAAC,OAAO,CACxC,SAAS,EACT,+HAA+H,EAC/H,cAAc,CACjB,CAAC;IAEF;;;OAGG;IACH,MAAM,CAAU,SAAS,GAAG,uBAAU,CAAC,SAAS,CAAS,WAAW,EAAE,uBAAuB,CAAC,CAAC;IAE/F;;;;;;;;;;;OAWG;IACH,MAAM,CAAU,aAAa,GAAG,uBAAU,CAAC,SAAS,CAChD,KAAK;IACL,cAAc,CAAC,SAAS;IACxB,cAAc,CAAC,KAAK;IACpB,YAAY,CAAC,IAAI,CACpB,CAAC;IAEF;;;;;;;;;;OAUG;IACH,MAAM,CAAU,WAAW,GAAG,uBAAU,CAAC,SAAS,CAC9C,YAAY;IACZ,cAAc,CAAC,SAAS;IACxB,cAAc,CAAC,KAAK;IACpB,YAAY,CAAC,IAAI,CACpB,CAAC;IAEF,MAAM,CAAU,YAAY,GAAG,uBAAU,CAAC,SAAS,CAC/C,aAAa;IACb,cAAc,CAAC,SAAS;IACxB,cAAc,CAAC,KAAK;IACpB,YAAY,CAAC,IAAI,CACpB,CAAC;IAEF;;;;;;;;;;;;;;;;;OAiBG;IACH,MAAM,CAAU,UAAU,GAAG,uBAAU,CAAC,SAAS,CAC7C,YAAY;IACZ,cAAc,CAAC,SAAS;IACxB,cAAc,CAAC,KAAK;IACpB,YAAY,CAAC,IAAI,CACpB,CAAC;IAEF,MAAM,CAAU,MAAM,GAAG,uBAAU,CAAC,SAAS,CACzC,QAAQ;IACR,cAAc,CAAC,SAAS;IACxB,cAAc,CAAC,KAAK;IACpB,YAAY,CAAC,IAAI,CACpB,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACH,MAAM,CAAU,iBAAiB,GAAG,uBAAU,CAAC,SAAS,CACpD,iBAAiB;IACjB,cAAc,CAAC,SAAS;IACxB,cAAc,CAAC,KAAK;IACpB,YAAY,CAAC,IAAI,CACpB,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,MAAM,CAAU,iBAAiB,GAAG,uBAAU,CAAC,SAAS,CACpD,iBAAiB;IACjB,cAAc,CAAC,SAAS;IACxB,cAAc,CAAC,KAAK;IACpB,YAAY,CAAC,IAAI,CACpB,CAAC;IAEF;;;;;;;;;;;;;;;OAeG;IAEH;;;;;OAKG;IACH,MAAM,CAAU,WAAW,GAAoB;QAC3C,oBAAoB,CAAC,UAAU;QAC/B,oBAAoB,CAAC,eAAe;QACpC,oBAAoB,CAAC,iBAAiB;QACtC,oBAAoB,CAAC,cAAc;QACnC,oBAAoB,CAAC,SAAS;QAC9B,oBAAoB,CAAC,OAAO;QAC5B,oBAAoB,CAAC,MAAM;QAC3B,oBAAoB,CAAC,UAAU;QAC/B,oBAAoB,CAAC,OAAO;QAC5B,oBAAoB,CAAC,SAAS;QAC9B,oBAAoB,CAAC,aAAa;QAClC,oBAAoB,CAAC,WAAW;QAChC,oBAAoB,CAAC,YAAY;QACjC,oBAAoB,CAAC,UAAU;QAC/B,oBAAoB,CAAC,MAAM;QAC3B,oBAAoB,CAAC,iBAAiB;QACtC,oBAAoB,CAAC,iBAAiB;KACzC,CAAC;;AA1VN,oDA2VC","sourcesContent":["import { ContextKey, AnyContextKey } from '../ContextKey';\nimport { ApiCallInfo } from './ApiCallInfo';\nimport { Surface } from './Surface';\n\n/**\n * Core framework context keys — the minimum the WebPieces framework needs to correlate one\n * request across every service it touches, and across every log line each of them writes.\n *\n * ONE id, propagated unchanged. The first service to see a request without an `x-request-id`\n * generates one (RequestContextHeaders.fillFromRequest); every hop copies it onward verbatim. Grep that id and you\n * have the whole call tree. There is no per-hop id and no parent pointer: a chain of ids you must\n * stitch back together buys nothing a single shared id does not already give you.\n *\n * Lives in core-util (browser-safe) so both the http clients and http-server can reference it.\n *\n * Exposed as {@link HeaderRegistry.DEFAULT_HEADERS} — a service opts into these by\n * passing `platformHeaders=true` to `HeaderRegistry.configure(...)`.\n *\n * Each key's `name` is the logical/log name; `httpHeader` is the wire name.\n */\nexport class WebpiecesCoreHeaders {\n /**\n * The id that correlates every hop of one request, and every log line of every hop.\n * Generated by the first service to see a request without one; propagated unchanged after that.\n *\n * - `httpHeader` `x-request-id` → travels DOWN the call tree on every outbound request.\n * - `responseHeader` `x-request-id` → and back UP on every response, which is the header a\n * support desk quotes. This used to be a hard-coded `res.setHeader` in `ExpressWrapper`\n * (`stampTransactionId`), reachable by no other key. It is an ordinary registry entry now: the\n * server's ONE response choke point writes every key in\n * {@link HeaderRegistry.getResponseTxfrKeys}, so a second key joins with no framework edit.\n * - `responseMerge` `first` → and NOT `collect`. A callee echoes OUR id back at us, so the\n * caller's context must KEEP what it already has. `collect` would turn `requestId` into a\n * growing array on every client call and break every log line of the request; `last` would\n * rewrite it with an echo. `first` makes reading it back a provable no-op for the normal case\n * and still adopts an id when a caller somehow had none.\n */\n static readonly REQUEST_ID = ContextKey.untrusted<string>(\n 'requestId',\n 'x-request-id',\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n /*responseHeader*/ 'x-request-id',\n /*responseMerge*/ 'first',\n );\n\n /** Framework-minted identity for one inbound streaming event; never accepted from HTTP. */\n static readonly STREAM_EVENT_ID = ContextKey.untrusted<string>(\n 'streamEventId',\n undefined,\n false,\n true,\n );\n\n /**\n * WHICH SERVICE MINTED {@link REQUEST_ID} — the name from {@link ServiceInfo}, stamped by\n * `RequestContextHeaders.fillFromRequest` ONLY on the branch that generates a new id (i.e. when\n * the inbound request carried no `x-request-id`). It answers the question the id alone cannot:\n * \"this trace starts here — is that right?\" An id appearing with no source means it came from\n * outside; an id sourced by a service that should never be an entry point is a routing bug.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire, and that is the WHOLE POINT. If it\n * travelled, hop 2 would inherit it, hop 3 would inherit it, and \"who started this trace\"\n * would be indistinguishable from \"who passed it along\" — the origin, the one fact this key\n * carries, would be lost. It is absent on every hop that did NOT mint the id, which is exactly\n * the signal: present == I am the origin.\n * - `isLogged` TRUE → emitted as a plain string at `jsonPayload.requestIdSource`.\n */\n static readonly REQUEST_ID_SOURCE = ContextKey.untrusted<string>(\n 'requestIdSource',\n /*httpHeader*/ undefined,\n );\n\n /**\n * The CALLER's build version — so a downstream server's logs record which build of the client\n * called it (surfaces as `jsonPayload.clientVersion`). Distinct from the log line's own `version`\n * (this service's build): `version` answers \"which build wrote this line?\", `clientVersion`\n * answers \"which build asked us to?\".\n *\n * - `httpHeader` SET → transferred over the wire, BUT unlike a normal transferred key it is NOT\n * copied from the context onward. Each hop OVERWRITES it with its OWN `ServiceInfo.getVersion()`\n * as it becomes the client to the next hop (see `buildOutboundHeaders`), so on any given server\n * `clientVersion` is always the IMMEDIATE caller's version, never a stale grand-caller's.\n * - `isLogged` TRUE → the inbound value lands in the context and flows through the normal log\n * field map; no backend change needed.\n */\n static readonly CLIENT_VERSION = ContextKey.untrusted<string>(\n 'clientVersion',\n 'x-webpieces-client-version',\n );\n\n /**\n * A frontend/app-minted correlation id that groups every request triggered by ONE user ACTION.\n *\n * An \"action\" is a single thing the user did in the GUI — a CLICK on a button/link, or TYPING in a\n * field — or a background poller tick: anything that may fan out into MULTIPLE remote calls. That one\n * action fires 1..N browser HTTP calls, each of which gets its own framework-minted {@link REQUEST_ID}\n * (one per HTTP call, shared within that call's server→server subtree). `actionId` sits ABOVE\n * `requestId` and is what stitches those N requests back to the single action that caused them:\n *\n * actionId (app-minted, ONE per user action, rides EVERY call of that action)\n * └── 1..N requestId (framework-minted, ONE per HTTP call)\n *\n * Grep one `actionId` in the logs → every `requestId` it spawned, and every log line of the whole\n * action. Minted and refreshed by the app (a UI concern), carried under `x-webpieces-actionid`.\n *\n * Browser/app-minted ONLY: unlike {@link REQUEST_ID}, the framework transfers and logs it but must\n * NOT auto-mint one server-side. Absent `actionId` ⇒ a non-action flow (system / cron / task), which\n * is the correct signal.\n *\n * - `httpHeader` SET → transferred: copied off the inbound request into context and re-emitted on\n * outbound hops, so the id follows the action across services.\n * - `isLogged` TRUE → emitted as a plain string on every log line of the request.\n */\n static readonly ACTION_ID = ContextKey.untrusted<string>('actionId', 'x-webpieces-actionid');\n\n /**\n * WHO the request is acting as, and WHAT they may do. All three are TRUSTED keys: they are the\n * inputs to authorization decisions, so a reader must be able to tell \"the framework proved\n * this\" from \"the caller typed this\" — see the trust section of the {@link ContextKey} doc.\n *\n * They keep their `httpHeader`, because propagating a verified identity to the next internal\n * service is the point. What makes that safe is not the header being absent, it is WHO is\n * allowed to have set it: an inbound value is held PENDING by\n * `RequestContextHeaders.fillFromRequest` and admitted by `AuthFilter` only on a route that\n * verified its CALLER (`@WpAuthOidc` / `@WpAuthSharedSecret`). On a browser-reachable route\n * (`@WpAuthJwt` / public) the value must match what the authenticator itself derived, or the\n * request is rejected.\n *\n * `provenance` says \"an app-bound JwtHook\" rather than naming one hook, because the framework\n * default ({@link DefaultJwtHook}) stamps NO context entries at all — an app supplies a hook that\n * returns {@link ContextTuple}s for the keys it can vouch for. Any of these three that an app's\n * hook does NOT stamp will be rejected when a caller supplies it, which is the correct and loud\n * outcome: nothing is vouching for it.\n */\n static readonly ORG_ID = ContextKey.trusted<string>(\n 'orgId',\n 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthenticatedCaller)',\n 'x-org-id',\n );\n\n static readonly USER_ID = ContextKey.trusted<string>(\n 'userId',\n 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthenticatedCaller)',\n 'x-user-id',\n );\n\n static readonly USER_ROLES = ContextKey.trusted<string>(\n 'roles',\n 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthenticatedCaller)',\n 'x-webpieces-roles',\n );\n\n /**\n * WHICH KIND OF CALLER is driving this request — `gui`, `llm` or `public-api`. See\n * {@link Surface} for the table and for why it is a property of the CALLER and not of the server.\n *\n * TRUSTED, and that is load-bearing twice over. It is what makes an app safe to branch on it in\n * its OWN authorization logic (\"this endpoint is not exposed to partners\"), and it is what makes\n * the value in {@link SurfaceEndUserStatus} believable: webpieces sets it itself, from the auth\n * mode that matched, never from caller input. A caller that simply SENDS `x-wp-surface` at an\n * endpoint whose auth did not establish one is rejected by `AuthFilter.reconcileWireTrust`, like\n * every other unvouched trusted header.\n *\n * It keeps its `httpHeader` because PROPAGATION is the point: the surface belongs to the ORIGINAL\n * caller, so `GUI -> api1 -> api2` must leave api2 seeing `gui`. An internal `@WpAuthOidc` hop\n * verifies its caller, so the propagated value is admitted verbatim — and the auth filter only\n * ever SETS a surface when none arrived, so an inherited one is never overwritten.\n */\n static readonly SURFACE = ContextKey.trusted<Surface>(\n 'surface',\n 'set by the webpieces AuthFilter from the auth mode that matched the incoming request, or inherited from the edge hop that did',\n 'x-wp-surface',\n );\n\n /**\n * Turns on test-case recording for this request (Java: x-webpieces-recording).\n * Transferred so recording follows the request across service hops.\n */\n static readonly RECORDING = ContextKey.untrusted<string>('recording', 'x-webpieces-recording');\n\n /**\n * The structured API-call tag ({@link ApiCallInfo}) stamped by {@link LogApiCallImpl} around every\n * outbound (client) / inbound (server) call. It rides the magic context so EVERY log line emitted\n * during the call inherits a filterable `api` object, surfacing in GCP as nested\n * `jsonPayload.api.{side,type,result,path,method}`.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire. Per-hop only: each server/client hop\n * stamps its own tag, so a downstream server records `side:'server'`, never the caller's `side:'client'`.\n * - `isLogged` TRUE → emitted by the logging backends. It carries an OBJECT value, so the backends\n * read it via {@link HeaderRegistry.buildStructuredLogFields} (object-aware); the flat\n * `buildLogFields()` string map deliberately skips it (typeof-string guard).\n */\n static readonly API_CALL_INFO = ContextKey.untrusted<ApiCallInfo>(\n 'api',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * The inbound request's HTTP method and path, stamped ONCE from the {@link HttpRequest} by\n * `RequestContextHeaders.fillFromRequest` (the atomic inbound choke point every transport funnels\n * through). They surface as top-level `jsonPayload.httpMethod` / `jsonPayload.requestPath` so every\n * log line of the request carries them — they used to ride inside {@link ApiCallInfo} (`api.path` /\n * `api.method`) but that coupled a per-CALL logger to the per-REQUEST transport shape.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire: a downstream hop stamps its OWN inbound\n * method/path, never the caller's. Outbound client calls never set these (no inbound path).\n * - `isLogged` TRUE → emitted by the logging backends as plain strings.\n */\n static readonly HTTP_METHOD = ContextKey.untrusted<string>(\n 'httpMethod',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n static readonly REQUEST_PATH = ContextKey.untrusted<string>(\n 'requestPath',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * The routed endpoint's IMPLEMENTATION identity: the concrete controller class name\n * ({@link RouteMetadata.controllerClassName}, e.g. `LoginController`) and the handler method NAME\n * ({@link RouteMetadata.methodName}, e.g. `login`), stamped once per request by {@link LogApiFilter}\n * after route matching so every subsequent log line of the request carries them.\n *\n * These say WHICH CODE ran, which is what you actually grep for — far more useful than the raw\n * `requestPath`. They are the top-level, filterable twin of what previously only lived nested in\n * {@link ApiCallInfo} (`api.method.controllerName` / `api.method.methodName`). The local console\n * formatters render them together as a compact `[Controller.method]` bracket; GCP keeps them as two\n * separate `jsonPayload.controller` / `jsonPayload.method` fields.\n *\n * NOTE: `method` here is the CODE method name (e.g. `login`), NOT the HTTP verb — that is\n * {@link HTTP_METHOD} (`httpMethod`).\n *\n * - `httpHeader` UNDEFINED → NOT transferred: each hop stamps its OWN routed controller/method.\n * - `isLogged` TRUE → emitted by the logging backends as plain strings.\n */\n static readonly CONTROLLER = ContextKey.untrusted<string>(\n 'controller',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n static readonly METHOD = ContextKey.untrusted<string>(\n 'method',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * The base URL ONE outbound call should go to, overriding whatever the client's `ClientConfig`\n * bound at construction — the answer to \"POST our published contract to a URL the PARTNER\n * registered at runtime\" (an `OrganizationWebhook.url` column, an OAuth callback, a per-tenant\n * or self-hosted host). The destination is DATA, not deployment, so it cannot be a svcName and\n * there is nothing to register in {@link ClientRegistry}.\n *\n * ```ts\n * RequestContext.run(() => {\n * RequestContext.putUntrusted(WebpiecesCoreHeaders.OVERRIDE_BASE_URL, webhook.url);\n * return partnerWebhookClient.deliver(envelope);\n * });\n * ```\n *\n * The contract's path IS appended to it (base + `@ApiPath` + `@Endpoint`). When the stored value\n * is a complete url — the usual partner-webhook row — use {@link OVERRIDE_FULL_URL} instead.\n *\n * ONLY a client carrying a `ContextBaseUrlFilter` reads it. Every other client IGNORES this key\n * entirely, which is what stops an ambient value re-pointing every other client in the same\n * fan-out loop at a partner's server. Installing that ONE filter IS the opt-in, so\n * `grep -rn ContextBaseUrlFilter` enumerates every client that can be re-pointed at all.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire, and that is load-bearing. This value\n * names where THIS hop goes. If it travelled, the callee would inherit it and re-point ITS\n * own outbound calls at the same host — one partner-supplied URL turning into an SSRF pivot\n * across the whole call tree. It is per-hop, always.\n * - `isLogged` TRUE → the destination of a partner delivery is exactly what you want in the log\n * line when one fails.\n *\n * UNTRUSTED, necessarily: it comes from a database column a partner edited. That is precisely\n * why re-pointing a request ARMS the framework's SSRF guard, automatically, rather than trusting\n * it — the guard sits beneath every app filter and reads the fact that the request was moved.\n */\n static readonly OVERRIDE_BASE_URL = ContextKey.untrusted<string>(\n 'overrideBaseUrl',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * The COMPLETE url — host, path AND query — ONE outbound call should be sent to, verbatim. The\n * sibling of {@link OVERRIDE_BASE_URL} for the other shape of runtime destination: a partner\n * that does not implement our contract at their own host, but hands us one opaque url to POST\n * to (`https://hooks.partner.example/in/abc?token=xyz`, straight out of a webhook row). With a\n * base-URL override the contract's path would be appended to it and the url would be wrong; with\n * this one the contract's path is NOT appended and nothing is inserted.\n *\n * ```ts\n * RequestContext.run(() => {\n * RequestContext.putUntrusted(WebpiecesCoreHeaders.OVERRIDE_FULL_URL, webhook.url);\n * return partnerWebhookClient.deliver(envelope);\n * });\n * ```\n *\n * ONLY a client carrying a `ContextFullUrlFilter` reads it, for exactly the reasons given on\n * {@link OVERRIDE_BASE_URL}: installing that filter IS the opt-in, and every other client ignores\n * the key. Not transferred over the wire (per-hop, or it becomes an SSRF pivot across the call\n * tree), logged (it is what you want in a failed-delivery log line), and UNTRUSTED (a partner\n * edited it) — which is why re-pointing at it arms the framework's SSRF guard automatically.\n */\n static readonly OVERRIDE_FULL_URL = ContextKey.untrusted<string>(\n 'overrideFullUrl',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * NO CREDENTIAL KEYS LIVE HERE.\n *\n * `authorization` and `x-webpieces-shared-secret` used to be ContextKeys. That made them\n * TRANSFERRED keys, so the inbound transfer copied them off the request into the\n * RequestContext, and every outbound RPC call and enqueued Cloud Task then carried the\n * caller's credential onward — to services that had no business seeing it.\n *\n * A credential belongs to ONE request hop. It is read straight off the {@link HttpRequest}\n * by the framework AuthFilter, and written straight onto the outbound request by the client\n * that mints it (NodeProxyClient, GcpTaskInvoker, InMemoryTaskInvoker). It never enters the\n * magic context, so nothing can propagate it by accident.\n *\n * An app that genuinely wants a credential to travel can still register its own ContextKey for\n * it — but that is now an explicit, visible decision rather than the default.\n */\n\n /**\n * All core context keys (the platform DEFAULT_HEADERS set). A `static readonly` CONSTANT, not a\n * method: it is compile-time data — a list of the key definitions above — read once at the startup\n * composition root (`HeaderRegistry.configure` / `HeaderRegistry.DEFAULT_HEADERS`). A method here\n * would be un-injectable behavior the DI design graph can't reach; a constant is honest data.\n */\n static readonly ALL_HEADERS: AnyContextKey[] = [\n WebpiecesCoreHeaders.REQUEST_ID,\n WebpiecesCoreHeaders.STREAM_EVENT_ID,\n WebpiecesCoreHeaders.REQUEST_ID_SOURCE,\n WebpiecesCoreHeaders.CLIENT_VERSION,\n WebpiecesCoreHeaders.ACTION_ID,\n WebpiecesCoreHeaders.USER_ID,\n WebpiecesCoreHeaders.ORG_ID,\n WebpiecesCoreHeaders.USER_ROLES,\n WebpiecesCoreHeaders.SURFACE,\n WebpiecesCoreHeaders.RECORDING,\n WebpiecesCoreHeaders.API_CALL_INFO,\n WebpiecesCoreHeaders.HTTP_METHOD,\n WebpiecesCoreHeaders.REQUEST_PATH,\n WebpiecesCoreHeaders.CONTROLLER,\n WebpiecesCoreHeaders.METHOD,\n WebpiecesCoreHeaders.OVERRIDE_BASE_URL,\n WebpiecesCoreHeaders.OVERRIDE_FULL_URL,\n ];\n}\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -27,9 +27,11 @@ export { WpInternal, WpIpcEndpoint, isInternalApi, getIpcApiId, getIpcEndpoints,
|
|
|
27
27
|
export type { IpcApiType, IpcEndpointKind, IpcEndpointOptions } from './ipc/IpcDecorators';
|
|
28
28
|
export { RouteMetadata } from './http/RouteMetadata';
|
|
29
29
|
export { RouteMetadataFactory } from './http/RouteMetadataFactory';
|
|
30
|
+
export { StreamingSchemaCatalog, registerStreamingCatalog } from './http/StreamingSchemaCatalog';
|
|
31
|
+
export { StreamErrorControl, StreamProtocolError, StreamDisconnectedError, } from './http/StreamErrorControl';
|
|
30
32
|
export { BoundHttpRequest, HttpContractMapper, HttpParameterBinding, HttpParameterDeclaration, } from './http/HttpContract';
|
|
31
|
-
export { StreamCorrelation, StreamEnvelope, StreamingEndpointMetadata, StreamEventValidator, StreamTransportError, StreamWriter, WpStream, getStreamingEndpoint, } from './http/StreamingContract';
|
|
32
|
-
export type { RequestStream, ResponseStream, StreamEnvelopeKind
|
|
33
|
+
export { StreamCorrelation, StreamEnvelope, StreamingEndpointMetadata, StreamEventValidator, StreamTransportError, StreamWriter, StreamDirection, WpStream, getStreamingEndpoint, registerStreamingSchemas, } from './http/StreamingContract';
|
|
34
|
+
export type { RequestStream, ResponseStream, StreamEnvelopeKind } from './http/StreamingContract';
|
|
33
35
|
export type { EndpointResponseType, HttpMethod, HttpParameterSource, HttpParameterValueType, } from './http/HttpContract';
|
|
34
36
|
export { GET, POST } from './http/HttpContract';
|
|
35
37
|
export type { EndpointKind, EndpointOperation, EndpointOptions, ExternalEndpointOptions, } from './http/decorators';
|
|
@@ -39,9 +41,9 @@ export type { WpMcpMethodDecorator } from './mcp/McpMetadata';
|
|
|
39
41
|
export { WpMcpAuthJwt, WpMcpJwtAuthMetadata, getWpMcpAuthJwt } from './mcp/McpAuthMetadata';
|
|
40
42
|
export { ApiJsonSchema, ApiJsonSchemaDiscriminator, ApiJsonSchemaValidator, DtoValidationFailure, ObjectSchemaBuilder, } from './mcp/DtoSchema';
|
|
41
43
|
export type { ApiJsonSchemaType, DtoValue } from './mcp/DtoSchema';
|
|
42
|
-
export { McpToolCatalogFile, McpToolCatalogError, McpToolDefinition } from './mcp/McpToolCatalogFile';
|
|
44
|
+
export { McpToolCatalogFile, McpToolCatalogError, McpToolDefinition, } from './mcp/McpToolCatalogFile';
|
|
43
45
|
export { GeneratedApiDocsLayout, GenerateLayoutProblem, GenerateOutputLookup, GenerateOutputTarget, } from './mcp/GeneratedApiDocsLayout';
|
|
44
|
-
export type { LayoutDependsOn, LayoutDependsOnObject, LayoutTarget, LayoutTargets } from './mcp/GeneratedApiDocsLayout';
|
|
46
|
+
export type { LayoutDependsOn, LayoutDependsOnObject, LayoutTarget, LayoutTargets, } from './mcp/GeneratedApiDocsLayout';
|
|
45
47
|
export { AuthMeta } from './http/auth-mode';
|
|
46
48
|
export type { AuthMode, ApiKeyCredential, ApiKeyCredentials, JwtRoles, JwtRequirement, } from './http/auth-mode';
|
|
47
49
|
export { Rpc, PubSub, Queue, ENDPOINT_KINDS_BY_API_KIND, getApiKind, assertApiKind, assertPubSubConventions, getQueueName, } from './http/api-kind';
|
package/src/index.js
CHANGED
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.WpMax = exports.WpMin = exports.WpInt = exports.getHttpParameterDeclarations = exports.QueryParam = exports.PathParam = exports.METADATA_KEYS = exports.validateNoConflictingDecorators = exports.assertEveryWebhookEndpointRetainsRawBody = exports.assertEveryExternalEndpointDeclaresCaller = exports.assertEveryEndpointHasAuthMode = exports.getAuthMode = exports.getAuthMeta = exports.isApiPath = exports.isRawBody = exports.isFormPost = exports.getMaskSpec = exports.getEndpointKinds = exports.getEndpointKind = exports.getEndpointOperation = exports.getEndpointOptions = exports.getEndpointHttpMethod = exports.getEndpoints = exports.getApiPath = exports.MaskLog = exports.WpAuthLocalOnly = exports.WpAuthApiKey = exports.WpAuthWebhook = exports.WpAuthSharedSecret = exports.WpAuthOidc = exports.MISSING_AUTH_DECORATOR_FIX = exports.rolesRequired = exports.WpAuthJwt = exports.WpAuthPublic = exports.Endpoint = exports.ApiPath = exports.GCP_LOG_BUDGET_BYTES = exports.MAX_GCP_LOG_BYTES = exports.LogChunkInfo = exports.LogChunkerImpl = exports.LogChunker = exports.LogManager = exports.ConsoleLoggerFactory = exports.ConsoleLogger = exports.DESIGN_METADATA_KEYS = exports.isDocumentDesign = exports.DocumentDesign = exports.ContextTuple = exports.ContextKey = exports.toError = void 0;
|
|
12
|
-
exports.
|
|
13
|
-
exports.
|
|
14
|
-
exports.
|
|
15
|
-
exports.ApiErrorHttpStatus = exports.ApiErrorBoundary = exports.ApiErrorPayload = exports.ApiErrorCodec = exports.ApiCodedError = exports.ApiNotImplementedError = exports.ApiUnsupportedMediaTypeError = exports.ApiPreconditionFailedError = exports.ApiUnprocessableError = exports.ApiConflictError = exports.ApiEndpointNotFoundError = void 0;
|
|
12
|
+
exports.WRITE = exports.WRITE_IDEMPOTENT = exports.READ = exports.EXTERNAL = exports.CRON = exports.CLOUDTASKS = exports.RPC = exports.POST = exports.GET = exports.registerStreamingSchemas = exports.getStreamingEndpoint = exports.WpStream = exports.StreamDirection = exports.StreamWriter = exports.StreamTransportError = exports.StreamEventValidator = exports.StreamingEndpointMetadata = exports.StreamEnvelope = exports.StreamCorrelation = exports.HttpParameterDeclaration = exports.HttpParameterBinding = exports.HttpContractMapper = exports.BoundHttpRequest = exports.StreamDisconnectedError = exports.StreamProtocolError = exports.StreamErrorControl = exports.registerStreamingCatalog = exports.StreamingSchemaCatalog = exports.RouteMetadataFactory = exports.RouteMetadata = exports.IPC_METADATA_KEYS = exports.assertNotInternalApi = exports.assertInternalApi = exports.getIpcMaskSpec = exports.getIpcEndpointKind = exports.getIpcEndpoints = exports.getIpcApiId = exports.isInternalApi = exports.WpIpcEndpoint = exports.WpInternal = exports.assertApiTypeMatchesMcpTools = exports.getApiTypeEndpointNames = exports.getApiTypes = exports.DEFAULT_API_TYPES = exports.API_TYPE_METADATA_KEY = exports.MCP = exports.EXTERNAL_CUSTOMER = exports.SVC_TO_SVC = exports.ApiType = exports.NUMERIC_METADATA_KEYS = void 0;
|
|
13
|
+
exports.DateUtil = exports.InstantUtil = exports.NetworkRejectClassifier = exports.NO_REG_CODE = exports.WRONG_COMPANY = exports.WRONG_DOMAIN = exports.EMAIL_NOT_CONFIRMED = exports.NOT_APPROVED = exports.WRONG_LOGIN = exports.WRONG_LOGIN_TYPE = exports.ENTITY_NOT_FOUND = exports.SECRETS = exports.Secrets = exports.getEndpointCaller = exports.isExternalSystemKind = exports.ExternalCaller = exports.DEFAULT_CALLER_KIND = exports.EXTERNAL_SYSTEM_KINDS = exports.getQueueName = exports.assertPubSubConventions = exports.assertApiKind = exports.getApiKind = exports.ENDPOINT_KINDS_BY_API_KIND = exports.Queue = exports.PubSub = exports.Rpc = exports.AuthMeta = exports.GenerateOutputTarget = exports.GenerateOutputLookup = exports.GenerateLayoutProblem = exports.GeneratedApiDocsLayout = exports.McpToolDefinition = exports.McpToolCatalogError = exports.McpToolCatalogFile = exports.ObjectSchemaBuilder = exports.DtoValidationFailure = exports.ApiJsonSchemaValidator = exports.ApiJsonSchemaDiscriminator = exports.ApiJsonSchema = exports.getWpMcpAuthJwt = exports.WpMcpJwtAuthMetadata = exports.WpMcpAuthJwt = exports.mcpHintsForOperation = exports.getWpMcpTools = exports.getInvalidEndpointsForMcp = exports.WpMcpToolMetadata = exports.WpMcpToolHints = exports.WpMcpTool = exports.InvalidEndpointForMcpMetadata = exports.InvalidEndpointForMcp = void 0;
|
|
14
|
+
exports.ApiRateLimitedError = exports.ApiImplementationError = exports.ApiDependencyTimeoutError = exports.ApiUnavailableError = exports.ApiBadGatewayError = exports.ApiDependencyError = exports.ApiNotFoundError = exports.ApiError = exports.FilterChain = exports.Filter = exports.SerializedError = exports.SerializedMap = exports.RecordSerializer = exports.getDoNotRecordFields = exports.DoNotRecord = exports.RecordedTestCase = exports.RecordedError = exports.RecordedEndpoint = exports.RecorderKeys = exports.ApiMethodInfo = exports.LOG_API_CALL_LOGGER_NAME = exports.ApiCallLogNameImpl = exports.ApiCallLogName = exports.ApiCallInfo = exports.MaskSpec = exports.LogApiCallImpl = exports.ContextMgr = exports.DestinationTrust = exports.WebpiecesCoreHeaders = exports.templateDeriver = exports.WEBPIECES_DEFAULT_FAILURE_CLASSIFIER = exports.WebpiecesDefaultFailureClassifier = exports.KeyedFailureClassifier = exports.ReceivedApiErrorRule = exports.SurfaceEndUserStatus = exports.WEBPIECES_DEFAULT_ERROR_TRANSLATOR = exports.WebpiecesDefaultErrorTranslator = exports.HttpResponseDto = exports.HttpResponseStatus = exports.HttpHeader = exports.RuntimeLocality = exports.ServiceInfo = exports.ApiCallTimeoutError = exports.CallContext = exports.CallDeadline = exports.CallRegistry = exports.ClientRegistry = exports.HeaderRegistry = exports.DateTimeUtil = exports.TimeUtil = void 0;
|
|
15
|
+
exports.ApiErrorHttpStatus = exports.ApiErrorBoundary = exports.ApiErrorPayload = exports.ApiErrorCodec = exports.ApiCodedError = exports.ApiNotImplementedError = exports.ApiUnsupportedMediaTypeError = exports.ApiPreconditionFailedError = exports.ApiUnprocessableError = exports.ApiConflictError = exports.ApiEndpointNotFoundError = exports.ApiConnectionError = exports.ApiDependencyBackoffError = exports.ApiUnauthorizedError = exports.ApiBadRequestError = exports.ApiEndUserError = exports.ApiRequestTimeoutError = exports.ApiForbiddenError = void 0;
|
|
16
16
|
var errorUtils_1 = require("./lib/errorUtils");
|
|
17
17
|
Object.defineProperty(exports, "toError", { enumerable: true, get: function () { return errorUtils_1.toError; } });
|
|
18
18
|
var ContextKey_1 = require("./ContextKey");
|
|
@@ -111,6 +111,13 @@ var RouteMetadata_1 = require("./http/RouteMetadata");
|
|
|
111
111
|
Object.defineProperty(exports, "RouteMetadata", { enumerable: true, get: function () { return RouteMetadata_1.RouteMetadata; } });
|
|
112
112
|
var RouteMetadataFactory_1 = require("./http/RouteMetadataFactory");
|
|
113
113
|
Object.defineProperty(exports, "RouteMetadataFactory", { enumerable: true, get: function () { return RouteMetadataFactory_1.RouteMetadataFactory; } });
|
|
114
|
+
var StreamingSchemaCatalog_1 = require("./http/StreamingSchemaCatalog");
|
|
115
|
+
Object.defineProperty(exports, "StreamingSchemaCatalog", { enumerable: true, get: function () { return StreamingSchemaCatalog_1.StreamingSchemaCatalog; } });
|
|
116
|
+
Object.defineProperty(exports, "registerStreamingCatalog", { enumerable: true, get: function () { return StreamingSchemaCatalog_1.registerStreamingCatalog; } });
|
|
117
|
+
var StreamErrorControl_1 = require("./http/StreamErrorControl");
|
|
118
|
+
Object.defineProperty(exports, "StreamErrorControl", { enumerable: true, get: function () { return StreamErrorControl_1.StreamErrorControl; } });
|
|
119
|
+
Object.defineProperty(exports, "StreamProtocolError", { enumerable: true, get: function () { return StreamErrorControl_1.StreamProtocolError; } });
|
|
120
|
+
Object.defineProperty(exports, "StreamDisconnectedError", { enumerable: true, get: function () { return StreamErrorControl_1.StreamDisconnectedError; } });
|
|
114
121
|
var HttpContract_1 = require("./http/HttpContract");
|
|
115
122
|
Object.defineProperty(exports, "BoundHttpRequest", { enumerable: true, get: function () { return HttpContract_1.BoundHttpRequest; } });
|
|
116
123
|
Object.defineProperty(exports, "HttpContractMapper", { enumerable: true, get: function () { return HttpContract_1.HttpContractMapper; } });
|
|
@@ -123,8 +130,10 @@ Object.defineProperty(exports, "StreamingEndpointMetadata", { enumerable: true,
|
|
|
123
130
|
Object.defineProperty(exports, "StreamEventValidator", { enumerable: true, get: function () { return StreamingContract_1.StreamEventValidator; } });
|
|
124
131
|
Object.defineProperty(exports, "StreamTransportError", { enumerable: true, get: function () { return StreamingContract_1.StreamTransportError; } });
|
|
125
132
|
Object.defineProperty(exports, "StreamWriter", { enumerable: true, get: function () { return StreamingContract_1.StreamWriter; } });
|
|
133
|
+
Object.defineProperty(exports, "StreamDirection", { enumerable: true, get: function () { return StreamingContract_1.StreamDirection; } });
|
|
126
134
|
Object.defineProperty(exports, "WpStream", { enumerable: true, get: function () { return StreamingContract_1.WpStream; } });
|
|
127
135
|
Object.defineProperty(exports, "getStreamingEndpoint", { enumerable: true, get: function () { return StreamingContract_1.getStreamingEndpoint; } });
|
|
136
|
+
Object.defineProperty(exports, "registerStreamingSchemas", { enumerable: true, get: function () { return StreamingContract_1.registerStreamingSchemas; } });
|
|
128
137
|
var HttpContract_2 = require("./http/HttpContract");
|
|
129
138
|
Object.defineProperty(exports, "GET", { enumerable: true, get: function () { return HttpContract_2.GET; } });
|
|
130
139
|
Object.defineProperty(exports, "POST", { enumerable: true, get: function () { return HttpContract_2.POST; } });
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/core/core-util/src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @webpieces/core-util\n *\n * Utility functions for WebPieces applications.\n * This package works in both browser and Node.js environments.\n *\n * @packageDocumentation\n */\n\nexport { toError } from './lib/errorUtils';\nexport { ContextKey } from './ContextKey';\nexport type {\n AnyContextKey,\n AnyTrustedContextKey,\n AnyUntrustedContextKey,\n Trust,\n} from './ContextKey';\nexport { ContextTuple } from './ContextTuple';\n\n// @DocumentDesign — DI-design-root marker. Applies to ANY project kind (server\n// controllers AND library impl classes), so it lives here (browser + Node) rather\n// than in a server-only routing package.\nexport { DocumentDesign, isDocumentDesign, DESIGN_METADATA_KEYS } from './DocumentDesign';\n\n// Logging (merged from former @webpieces/wp-logging).\n// Pluggable logging interface + a browser-safe console default; apps plug in\n// bunyan/winston/pino/etc. via LogManager.setFactory(...). Browser + Node.\nexport type { Logger, LogLevel } from './logging/Logger';\nexport type { LoggerFactory } from './logging/LoggerFactory';\nexport { ConsoleLogger } from './logging/ConsoleLogger';\nexport { ConsoleLoggerFactory } from './logging/ConsoleLoggerFactory';\nexport { LogManager } from './logging/LogManager';\nexport {\n LogChunker,\n LogChunkerImpl,\n LogChunkInfo,\n MAX_GCP_LOG_BYTES,\n GCP_LOG_BUDGET_BYTES,\n} from './logging/LogChunker';\n\n// HTTP API contract (merged from former @webpieces/http-api).\n// Shared HTTP API definition consumed by both client and server: REST\n// decorators, the API error taxonomy, datetime DTOs, platform-header\n// registry/readers, ValidateImplementation, and the test-case recorder\n// contract. Pure definitions — express-free, browser + Node safe.\n\n// API definition decorators\nexport {\n ApiPath,\n Endpoint,\n // Auth mode decorators (clean service-to-service + user JWT model)\n WpAuthPublic,\n WpAuthJwt,\n rolesRequired,\n MISSING_AUTH_DECORATOR_FIX,\n WpAuthOidc,\n WpAuthSharedSecret,\n WpAuthWebhook,\n WpAuthApiKey,\n WpAuthLocalOnly,\n MaskLog,\n getApiPath,\n getEndpoints,\n getEndpointHttpMethod,\n getEndpointOptions,\n getEndpointOperation,\n getEndpointKind,\n getEndpointKinds,\n getMaskSpec,\n isFormPost,\n isRawBody,\n isApiPath,\n getAuthMeta,\n getAuthMode,\n assertEveryEndpointHasAuthMode,\n assertEveryExternalEndpointDeclaresCaller,\n assertEveryWebhookEndpointRetainsRawBody,\n validateNoConflictingDecorators,\n METADATA_KEYS,\n} from './http/decorators';\nexport {\n PathParam,\n QueryParam,\n getHttpParameterDeclarations,\n} from './http/http-parameter-decorators';\nexport { WpInt, WpMin, WpMax, NUMERIC_METADATA_KEYS } from './http/numeric-field-decorators';\nexport type { Integer } from './http/numeric-field-decorators';\nexport {\n ApiType,\n SVC_TO_SVC,\n EXTERNAL_CUSTOMER,\n MCP,\n API_TYPE_METADATA_KEY,\n DEFAULT_API_TYPES,\n getApiTypes,\n getApiTypeEndpointNames,\n assertApiTypeMatchesMcpTools,\n} from './http/api-type';\nexport type { ApiTypeKind } from './http/api-type';\nexport {\n WpInternal,\n WpIpcEndpoint,\n isInternalApi,\n getIpcApiId,\n getIpcEndpoints,\n getIpcEndpointKind,\n getIpcMaskSpec,\n assertInternalApi,\n assertNotInternalApi,\n IPC_METADATA_KEYS,\n} from './ipc/IpcDecorators';\nexport type { IpcApiType, IpcEndpointKind, IpcEndpointOptions } from './ipc/IpcDecorators';\n// The runtime representation of ONE route (split out of decorators.ts for file size only).\nexport { RouteMetadata } from './http/RouteMetadata';\nexport { RouteMetadataFactory } from './http/RouteMetadataFactory';\nexport {\n BoundHttpRequest,\n HttpContractMapper,\n HttpParameterBinding,\n HttpParameterDeclaration,\n} from './http/HttpContract';\nexport {\n StreamCorrelation,\n StreamEnvelope,\n StreamingEndpointMetadata,\n StreamEventValidator,\n StreamTransportError,\n StreamWriter,\n WpStream,\n getStreamingEndpoint,\n} from './http/StreamingContract';\nexport type {\n RequestStream,\n ResponseStream,\n StreamEnvelopeKind,\n StreamFailureOptions,\n} from './http/StreamingContract';\nexport type {\n EndpointResponseType,\n HttpMethod,\n HttpParameterSource,\n HttpParameterValueType,\n} from './http/HttpContract';\nexport { GET, POST } from './http/HttpContract';\nexport type {\n EndpointKind,\n EndpointOperation,\n EndpointOptions,\n ExternalEndpointOptions,\n} from './http/decorators';\nexport { RPC, CLOUDTASKS, CRON, EXTERNAL, READ, WRITE_IDEMPOTENT, WRITE } from './http/decorators';\nexport {\n InvalidEndpointForMcp,\n InvalidEndpointForMcpMetadata,\n WpMcpTool,\n WpMcpToolHints,\n WpMcpToolMetadata,\n getInvalidEndpointsForMcp,\n getWpMcpTools,\n mcpHintsForOperation,\n} from './mcp/McpMetadata';\nexport type { WpMcpMethodDecorator } from './mcp/McpMetadata';\nexport { WpMcpAuthJwt, WpMcpJwtAuthMetadata, getWpMcpAuthJwt } from './mcp/McpAuthMetadata';\nexport {\n ApiJsonSchema,\n ApiJsonSchemaDiscriminator,\n ApiJsonSchemaValidator,\n DtoValidationFailure,\n ObjectSchemaBuilder,\n} from './mcp/DtoSchema';\nexport type { ApiJsonSchemaType, DtoValue } from './mcp/DtoSchema';\nexport { McpToolCatalogFile, McpToolCatalogError, McpToolDefinition } from './mcp/McpToolCatalogFile';\nexport {\n GeneratedApiDocsLayout,\n GenerateLayoutProblem,\n GenerateOutputLookup,\n GenerateOutputTarget,\n} from './mcp/GeneratedApiDocsLayout';\nexport type { LayoutDependsOn, LayoutDependsOnObject, LayoutTarget, LayoutTargets } from './mcp/GeneratedApiDocsLayout';\n// The TYPE layer of the auth surface — likewise split out of decorators.ts for file size only.\nexport { AuthMeta } from './http/auth-mode';\nexport type {\n AuthMode,\n ApiKeyCredential,\n ApiKeyCredentials,\n JwtRoles,\n JwtRequirement,\n} from './http/auth-mode';\n// API kind (RPC vs PubSub/Cloud Tasks) + queue naming. Split out of decorators.ts for file size only;\n// one-way dependency api-kind -> decorators, and the barrel keeps the surface identical.\nexport {\n Rpc,\n PubSub,\n Queue,\n ENDPOINT_KINDS_BY_API_KIND,\n getApiKind,\n assertApiKind,\n assertPubSubConventions,\n getQueueName,\n} from './http/api-kind';\nexport type { ApiKind } from './http/api-kind';\n// WHO calls an `external` endpoint — the caller declaration\n// @Endpoint(POST, ..., WRITE, EXTERNAL, { calledBy })\n// requires, and the reader for it.\nexport {\n EXTERNAL_SYSTEM_KINDS,\n DEFAULT_CALLER_KIND,\n ExternalCaller,\n isExternalSystemKind,\n getEndpointCaller,\n} from './http/external-caller';\nexport type { ExternalSystemKind } from './http/external-caller';\n// Client-side shared-secret store (the value THIS service sends per @WpAuthSharedSecret key).\nexport { Secrets, SECRETS } from './http/Secrets';\n\n// Type validators\nexport { ValidateImplementation } from './http/validators';\n\n// Authentication subtype constants.\nexport {\n ENTITY_NOT_FOUND,\n WRONG_LOGIN_TYPE,\n WRONG_LOGIN,\n NOT_APPROVED,\n EMAIL_NOT_CONFIRMED,\n WRONG_DOMAIN,\n WRONG_COMPANY,\n NO_REG_CODE,\n} from './errors';\n\nexport { NetworkRejectClassifier } from './http/networkReject';\n\n// Date/Time DTOs and Utilities (inspired by Java Time / JSR-310)\nexport {\n InstantDto,\n DateDto,\n TimeDto,\n DateTimeDto,\n InstantUtil,\n DateUtil,\n TimeUtil,\n DateTimeUtil,\n} from './http/datetime';\n\n// Context keys + registry (the global magic-context header system)\nexport { HeaderRegistry } from './http/HeaderRegistry';\nexport { ClientRegistry } from './http/ClientRegistry';\nexport { CallRegistry } from './http/CallRegistry';\nexport { CallDeadline } from './http/CallDeadline';\nexport { CallContext } from './http/CallStrategy';\nexport type { Attempt, CallStrategy } from './http/CallStrategy';\nexport { ApiCallTimeoutError } from './http/ApiCallTimeoutError';\nexport type { ServiceUrlDeriver } from './http/ClientRegistry';\n\n// \"What service am I\" — set once at startup, read by the logging backends and by\n// RequestContextHeaders (to stamp requestIdSource on ids this service mints).\nexport { ServiceInfo } from './http/ServiceInfo';\n// \"Where am I running\" — declared once at startup (setupRuntime, from RuntimeSetupOptions.locality).\n// The ONE input to @WpAuthLocalOnly enforcement. Undeclared reads as DEPLOYED (fail safe).\nexport { RuntimeLocality } from './http/RuntimeLocality';\nexport type { Locality } from './http/RuntimeLocality';\n// The ENTIRE HTTP response as pure data — the ONE form both transports (express, fetch) are\n// normalised into, so an ErrorTranslator implementation is written once and serves both.\nexport { HttpHeader, HttpResponseStatus, HttpResponseDto } from './http/HttpResponseDto';\n// Pluggable, bidirectional error translation (app exception <-> the WHOLE response). Set on\n// ClientRegistry at startup; consulted before the built-in webpieces mapping on BOTH sides.\nexport type { ErrorTranslator } from './http/ErrorTranslator';\n// THE webpieces default translator — always installed, and what an app's own translator delegates to\n// in order to decline an error. ONE class, both halves, node and browser alike.\nexport {\n WebpiecesDefaultErrorTranslator,\n WEBPIECES_DEFAULT_ERROR_TRANSLATOR,\n} from './http/WebpiecesDefaultErrorTranslator';\n// WHICH KIND OF CALLER drove this request (gui / llm / public-api), and the per-request end-user\n// status derived from it — the replacement for the deleted per-router EndUserStatus.\nexport { SurfaceEndUserStatus } from './http/Surface';\nexport type { Surface } from './http/Surface';\n// The ONE rule for an error RECEIVED from another process, shared verbatim by HTTP and IPC.\nexport { ReceivedApiErrorRule } from './errors/ReceivedApiErrorRule';\n// Pluggable per-client failure classification (is a thrown API-call error a real failure or an\n// expected non-failure?). Registered on ClientRegistry at startup; consulted by LogApiCall.\nexport type { FailureClassifier } from './http/FailureClassifier';\nexport { KeyedFailureClassifier } from './http/FailureClassifier';\nexport {\n WebpiecesDefaultFailureClassifier,\n WEBPIECES_DEFAULT_FAILURE_CLASSIFIER,\n} from './http/WebpiecesDefaultFailureClassifier';\nexport { templateDeriver } from './http/templateDeriver';\nexport { WebpiecesCoreHeaders } from './http/WebpiecesCoreHeaders';\nexport { ContextReader } from './http/ContextReader';\nexport { ContextWriter, ContextStore } from './http/ContextWriter';\n\n// The OUTBOUND half of the trust model: whether a TRUSTED context key may ride to the endpoint being\n// called. Built ONLY from the destination endpoint's AuthMode — see the class doc.\nexport { DestinationTrust } from './http/DestinationTrust';\n\n// BROWSER-ONLY outbound-header propagation (app-held store + registry -> outbound HTTP headers).\n// Only @webpieces/http-client-browser may name it; the server reads RequestContext directly via\n// RequestContextHeaders in the Node-only @webpieces/core-context.\nexport { ContextMgr } from './http/ContextMgr';\n\n// API-call logging helper (uses LogManager above). NOT a singleton: construct one per environment\n// with that environment's ApiCallContext — `new LogApiCallImpl(new RequestContextApiCallContext())`\n// on node, `new LogApiCallImpl(new BrowserApiCallContext())` in a browser.\nexport { LogApiCallImpl } from './http/LogApiCall';\n\n// Opt-in field masking for the LogApiCall log path — declare per-api sensitive fields so secrets\n// (OAuth refresh tokens, id-token JWTs) are masked in the logs while the real value stays on the wire.\nexport { MaskSpec } from './http/LogFieldMask';\nexport type { MaskMode } from './http/LogFieldMask';\n\n// The structured `api` tag + the context-writer seam LogApiCall stamps through. The Node\n// RequestContext-backed impl lives in @webpieces/core-context, the browser one in\n// @webpieces/http-client-browser; each is CONSTRUCTED by its package, never installed globally.\nexport { ApiCallInfo } from './http/ApiCallInfo';\nexport type { ApiCallDirection, ApiResult } from './http/ApiCallInfo';\n// Console-render bridge: turns LogApiCall's [LogApiCall] bracket into [API.{side}.{phase}] locally.\nexport {\n ApiCallLogName,\n ApiCallLogNameImpl,\n LOG_API_CALL_LOGGER_NAME,\n} from './http/ApiCallLogName';\nexport { ApiMethodInfo } from './http/ApiMethodInfo';\nexport type { ApiSide } from './http/ApiMethodInfo';\nexport type { ApiCallContext } from './http/ApiCallContext';\n\n// Test-case recording contract (impl lives in http-server; hooks in http-client)\nexport { TestCaseRecorder, RecorderKeys } from './http/recorder/TestCaseRecorder';\nexport {\n RecordedEndpoint,\n RecordedError,\n RecordedTestCase,\n} from './http/recorder/RecordedEndpoint';\nexport { DoNotRecord, getDoNotRecordFields } from './http/recorder/DoNotRecord';\nexport { RecordSerializer, SerializedMap, SerializedError } from './http/recorder/RecordSerializer';\n\n// ---------------------------------------------------------------------------------------------\n// Filter-chain primitives, shared by BOTH chains: the inbound server chain\n// (`Filter<MethodMeta, WpResponse<unknown>>`, @webpieces/http-routing) and the outbound client\n// chain (`Filter<ClientRequest, Response>`, @webpieces/http-client-core). Declared once, here, in\n// the package both depend on — see the class doc for why a second pair would be a shim.\nexport { Filter } from './filters/Filter';\nexport type { Service } from './filters/Filter';\nexport { FilterChain } from './filters/FilterChain';\n\nexport {\n ApiError,\n ApiNotFoundError,\n ApiDependencyError,\n ApiBadGatewayError,\n ApiUnavailableError,\n ApiDependencyTimeoutError,\n ApiImplementationError,\n ApiRateLimitedError,\n ApiForbiddenError,\n ApiRequestTimeoutError,\n ApiEndUserError,\n ApiBadRequestError,\n ApiUnauthorizedError,\n ApiDependencyBackoffError,\n ApiConnectionError,\n ApiEndpointNotFoundError,\n ApiConflictError,\n ApiUnprocessableError,\n ApiPreconditionFailedError,\n ApiUnsupportedMediaTypeError,\n ApiNotImplementedError,\n ApiCodedError,\n ApiErrorCodec,\n ApiErrorPayload,\n ApiErrorBoundary,\n} from './errors';\nexport type { ApiErrorKind, ApiStatusCode, EdgeHttpStatus } from './errors';\nexport { ApiErrorHttpStatus } from './http/ApiErrorHttpStatus';\nexport type { PublishedKind, PublishedApiError } from './http/ApiErrorHttpStatus';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/core/core-util/src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @webpieces/core-util\n *\n * Utility functions for WebPieces applications.\n * This package works in both browser and Node.js environments.\n *\n * @packageDocumentation\n */\n\nexport { toError } from './lib/errorUtils';\nexport { ContextKey } from './ContextKey';\nexport type {\n AnyContextKey,\n AnyTrustedContextKey,\n AnyUntrustedContextKey,\n Trust,\n} from './ContextKey';\nexport { ContextTuple } from './ContextTuple';\n\n// @DocumentDesign — DI-design-root marker. Applies to ANY project kind (server\n// controllers AND library impl classes), so it lives here (browser + Node) rather\n// than in a server-only routing package.\nexport { DocumentDesign, isDocumentDesign, DESIGN_METADATA_KEYS } from './DocumentDesign';\n\n// Logging (merged from former @webpieces/wp-logging).\n// Pluggable logging interface + a browser-safe console default; apps plug in\n// bunyan/winston/pino/etc. via LogManager.setFactory(...). Browser + Node.\nexport type { Logger, LogLevel } from './logging/Logger';\nexport type { LoggerFactory } from './logging/LoggerFactory';\nexport { ConsoleLogger } from './logging/ConsoleLogger';\nexport { ConsoleLoggerFactory } from './logging/ConsoleLoggerFactory';\nexport { LogManager } from './logging/LogManager';\nexport {\n LogChunker,\n LogChunkerImpl,\n LogChunkInfo,\n MAX_GCP_LOG_BYTES,\n GCP_LOG_BUDGET_BYTES,\n} from './logging/LogChunker';\n\n// HTTP API contract (merged from former @webpieces/http-api).\n// Shared HTTP API definition consumed by both client and server: REST\n// decorators, the API error taxonomy, datetime DTOs, platform-header\n// registry/readers, ValidateImplementation, and the test-case recorder\n// contract. Pure definitions — express-free, browser + Node safe.\n\n// API definition decorators\nexport {\n ApiPath,\n Endpoint,\n // Auth mode decorators (clean service-to-service + user JWT model)\n WpAuthPublic,\n WpAuthJwt,\n rolesRequired,\n MISSING_AUTH_DECORATOR_FIX,\n WpAuthOidc,\n WpAuthSharedSecret,\n WpAuthWebhook,\n WpAuthApiKey,\n WpAuthLocalOnly,\n MaskLog,\n getApiPath,\n getEndpoints,\n getEndpointHttpMethod,\n getEndpointOptions,\n getEndpointOperation,\n getEndpointKind,\n getEndpointKinds,\n getMaskSpec,\n isFormPost,\n isRawBody,\n isApiPath,\n getAuthMeta,\n getAuthMode,\n assertEveryEndpointHasAuthMode,\n assertEveryExternalEndpointDeclaresCaller,\n assertEveryWebhookEndpointRetainsRawBody,\n validateNoConflictingDecorators,\n METADATA_KEYS,\n} from './http/decorators';\nexport {\n PathParam,\n QueryParam,\n getHttpParameterDeclarations,\n} from './http/http-parameter-decorators';\nexport { WpInt, WpMin, WpMax, NUMERIC_METADATA_KEYS } from './http/numeric-field-decorators';\nexport type { Integer } from './http/numeric-field-decorators';\nexport {\n ApiType,\n SVC_TO_SVC,\n EXTERNAL_CUSTOMER,\n MCP,\n API_TYPE_METADATA_KEY,\n DEFAULT_API_TYPES,\n getApiTypes,\n getApiTypeEndpointNames,\n assertApiTypeMatchesMcpTools,\n} from './http/api-type';\nexport type { ApiTypeKind } from './http/api-type';\nexport {\n WpInternal,\n WpIpcEndpoint,\n isInternalApi,\n getIpcApiId,\n getIpcEndpoints,\n getIpcEndpointKind,\n getIpcMaskSpec,\n assertInternalApi,\n assertNotInternalApi,\n IPC_METADATA_KEYS,\n} from './ipc/IpcDecorators';\nexport type { IpcApiType, IpcEndpointKind, IpcEndpointOptions } from './ipc/IpcDecorators';\n// The runtime representation of ONE route (split out of decorators.ts for file size only).\nexport { RouteMetadata } from './http/RouteMetadata';\nexport { RouteMetadataFactory } from './http/RouteMetadataFactory';\nexport { StreamingSchemaCatalog, registerStreamingCatalog } from './http/StreamingSchemaCatalog';\nexport {\n StreamErrorControl,\n StreamProtocolError,\n StreamDisconnectedError,\n} from './http/StreamErrorControl';\nexport {\n BoundHttpRequest,\n HttpContractMapper,\n HttpParameterBinding,\n HttpParameterDeclaration,\n} from './http/HttpContract';\nexport {\n StreamCorrelation,\n StreamEnvelope,\n StreamingEndpointMetadata,\n StreamEventValidator,\n StreamTransportError,\n StreamWriter,\n StreamDirection,\n WpStream,\n getStreamingEndpoint,\n registerStreamingSchemas,\n} from './http/StreamingContract';\nexport type { RequestStream, ResponseStream, StreamEnvelopeKind } from './http/StreamingContract';\nexport type {\n EndpointResponseType,\n HttpMethod,\n HttpParameterSource,\n HttpParameterValueType,\n} from './http/HttpContract';\nexport { GET, POST } from './http/HttpContract';\nexport type {\n EndpointKind,\n EndpointOperation,\n EndpointOptions,\n ExternalEndpointOptions,\n} from './http/decorators';\nexport { RPC, CLOUDTASKS, CRON, EXTERNAL, READ, WRITE_IDEMPOTENT, WRITE } from './http/decorators';\nexport {\n InvalidEndpointForMcp,\n InvalidEndpointForMcpMetadata,\n WpMcpTool,\n WpMcpToolHints,\n WpMcpToolMetadata,\n getInvalidEndpointsForMcp,\n getWpMcpTools,\n mcpHintsForOperation,\n} from './mcp/McpMetadata';\nexport type { WpMcpMethodDecorator } from './mcp/McpMetadata';\nexport { WpMcpAuthJwt, WpMcpJwtAuthMetadata, getWpMcpAuthJwt } from './mcp/McpAuthMetadata';\nexport {\n ApiJsonSchema,\n ApiJsonSchemaDiscriminator,\n ApiJsonSchemaValidator,\n DtoValidationFailure,\n ObjectSchemaBuilder,\n} from './mcp/DtoSchema';\nexport type { ApiJsonSchemaType, DtoValue } from './mcp/DtoSchema';\nexport {\n McpToolCatalogFile,\n McpToolCatalogError,\n McpToolDefinition,\n} from './mcp/McpToolCatalogFile';\nexport {\n GeneratedApiDocsLayout,\n GenerateLayoutProblem,\n GenerateOutputLookup,\n GenerateOutputTarget,\n} from './mcp/GeneratedApiDocsLayout';\nexport type {\n LayoutDependsOn,\n LayoutDependsOnObject,\n LayoutTarget,\n LayoutTargets,\n} from './mcp/GeneratedApiDocsLayout';\n// The TYPE layer of the auth surface — likewise split out of decorators.ts for file size only.\nexport { AuthMeta } from './http/auth-mode';\nexport type {\n AuthMode,\n ApiKeyCredential,\n ApiKeyCredentials,\n JwtRoles,\n JwtRequirement,\n} from './http/auth-mode';\n// API kind (RPC vs PubSub/Cloud Tasks) + queue naming. Split out of decorators.ts for file size only;\n// one-way dependency api-kind -> decorators, and the barrel keeps the surface identical.\nexport {\n Rpc,\n PubSub,\n Queue,\n ENDPOINT_KINDS_BY_API_KIND,\n getApiKind,\n assertApiKind,\n assertPubSubConventions,\n getQueueName,\n} from './http/api-kind';\nexport type { ApiKind } from './http/api-kind';\n// WHO calls an `external` endpoint — the caller declaration\n// @Endpoint(POST, ..., WRITE, EXTERNAL, { calledBy })\n// requires, and the reader for it.\nexport {\n EXTERNAL_SYSTEM_KINDS,\n DEFAULT_CALLER_KIND,\n ExternalCaller,\n isExternalSystemKind,\n getEndpointCaller,\n} from './http/external-caller';\nexport type { ExternalSystemKind } from './http/external-caller';\n// Client-side shared-secret store (the value THIS service sends per @WpAuthSharedSecret key).\nexport { Secrets, SECRETS } from './http/Secrets';\n\n// Type validators\nexport { ValidateImplementation } from './http/validators';\n\n// Authentication subtype constants.\nexport {\n ENTITY_NOT_FOUND,\n WRONG_LOGIN_TYPE,\n WRONG_LOGIN,\n NOT_APPROVED,\n EMAIL_NOT_CONFIRMED,\n WRONG_DOMAIN,\n WRONG_COMPANY,\n NO_REG_CODE,\n} from './errors';\n\nexport { NetworkRejectClassifier } from './http/networkReject';\n\n// Date/Time DTOs and Utilities (inspired by Java Time / JSR-310)\nexport {\n InstantDto,\n DateDto,\n TimeDto,\n DateTimeDto,\n InstantUtil,\n DateUtil,\n TimeUtil,\n DateTimeUtil,\n} from './http/datetime';\n\n// Context keys + registry (the global magic-context header system)\nexport { HeaderRegistry } from './http/HeaderRegistry';\nexport { ClientRegistry } from './http/ClientRegistry';\nexport { CallRegistry } from './http/CallRegistry';\nexport { CallDeadline } from './http/CallDeadline';\nexport { CallContext } from './http/CallStrategy';\nexport type { Attempt, CallStrategy } from './http/CallStrategy';\nexport { ApiCallTimeoutError } from './http/ApiCallTimeoutError';\nexport type { ServiceUrlDeriver } from './http/ClientRegistry';\n\n// \"What service am I\" — set once at startup, read by the logging backends and by\n// RequestContextHeaders (to stamp requestIdSource on ids this service mints).\nexport { ServiceInfo } from './http/ServiceInfo';\n// \"Where am I running\" — declared once at startup (setupRuntime, from RuntimeSetupOptions.locality).\n// The ONE input to @WpAuthLocalOnly enforcement. Undeclared reads as DEPLOYED (fail safe).\nexport { RuntimeLocality } from './http/RuntimeLocality';\nexport type { Locality } from './http/RuntimeLocality';\n// The ENTIRE HTTP response as pure data — the ONE form both transports (express, fetch) are\n// normalised into, so an ErrorTranslator implementation is written once and serves both.\nexport { HttpHeader, HttpResponseStatus, HttpResponseDto } from './http/HttpResponseDto';\n// Pluggable, bidirectional error translation (app exception <-> the WHOLE response). Set on\n// ClientRegistry at startup; consulted before the built-in webpieces mapping on BOTH sides.\nexport type { ErrorTranslator } from './http/ErrorTranslator';\n// THE webpieces default translator — always installed, and what an app's own translator delegates to\n// in order to decline an error. ONE class, both halves, node and browser alike.\nexport {\n WebpiecesDefaultErrorTranslator,\n WEBPIECES_DEFAULT_ERROR_TRANSLATOR,\n} from './http/WebpiecesDefaultErrorTranslator';\n// WHICH KIND OF CALLER drove this request (gui / llm / public-api), and the per-request end-user\n// status derived from it — the replacement for the deleted per-router EndUserStatus.\nexport { SurfaceEndUserStatus } from './http/Surface';\nexport type { Surface } from './http/Surface';\n// The ONE rule for an error RECEIVED from another process, shared verbatim by HTTP and IPC.\nexport { ReceivedApiErrorRule } from './errors/ReceivedApiErrorRule';\n// Pluggable per-client failure classification (is a thrown API-call error a real failure or an\n// expected non-failure?). Registered on ClientRegistry at startup; consulted by LogApiCall.\nexport type { FailureClassifier } from './http/FailureClassifier';\nexport { KeyedFailureClassifier } from './http/FailureClassifier';\nexport {\n WebpiecesDefaultFailureClassifier,\n WEBPIECES_DEFAULT_FAILURE_CLASSIFIER,\n} from './http/WebpiecesDefaultFailureClassifier';\nexport { templateDeriver } from './http/templateDeriver';\nexport { WebpiecesCoreHeaders } from './http/WebpiecesCoreHeaders';\nexport { ContextReader } from './http/ContextReader';\nexport { ContextWriter, ContextStore } from './http/ContextWriter';\n\n// The OUTBOUND half of the trust model: whether a TRUSTED context key may ride to the endpoint being\n// called. Built ONLY from the destination endpoint's AuthMode — see the class doc.\nexport { DestinationTrust } from './http/DestinationTrust';\n\n// BROWSER-ONLY outbound-header propagation (app-held store + registry -> outbound HTTP headers).\n// Only @webpieces/http-client-browser may name it; the server reads RequestContext directly via\n// RequestContextHeaders in the Node-only @webpieces/core-context.\nexport { ContextMgr } from './http/ContextMgr';\n\n// API-call logging helper (uses LogManager above). NOT a singleton: construct one per environment\n// with that environment's ApiCallContext — `new LogApiCallImpl(new RequestContextApiCallContext())`\n// on node, `new LogApiCallImpl(new BrowserApiCallContext())` in a browser.\nexport { LogApiCallImpl } from './http/LogApiCall';\n\n// Opt-in field masking for the LogApiCall log path — declare per-api sensitive fields so secrets\n// (OAuth refresh tokens, id-token JWTs) are masked in the logs while the real value stays on the wire.\nexport { MaskSpec } from './http/LogFieldMask';\nexport type { MaskMode } from './http/LogFieldMask';\n\n// The structured `api` tag + the context-writer seam LogApiCall stamps through. The Node\n// RequestContext-backed impl lives in @webpieces/core-context, the browser one in\n// @webpieces/http-client-browser; each is CONSTRUCTED by its package, never installed globally.\nexport { ApiCallInfo } from './http/ApiCallInfo';\nexport type { ApiCallDirection, ApiResult } from './http/ApiCallInfo';\n// Console-render bridge: turns LogApiCall's [LogApiCall] bracket into [API.{side}.{phase}] locally.\nexport {\n ApiCallLogName,\n ApiCallLogNameImpl,\n LOG_API_CALL_LOGGER_NAME,\n} from './http/ApiCallLogName';\nexport { ApiMethodInfo } from './http/ApiMethodInfo';\nexport type { ApiSide } from './http/ApiMethodInfo';\nexport type { ApiCallContext } from './http/ApiCallContext';\n\n// Test-case recording contract (impl lives in http-server; hooks in http-client)\nexport { TestCaseRecorder, RecorderKeys } from './http/recorder/TestCaseRecorder';\nexport {\n RecordedEndpoint,\n RecordedError,\n RecordedTestCase,\n} from './http/recorder/RecordedEndpoint';\nexport { DoNotRecord, getDoNotRecordFields } from './http/recorder/DoNotRecord';\nexport { RecordSerializer, SerializedMap, SerializedError } from './http/recorder/RecordSerializer';\n\n// ---------------------------------------------------------------------------------------------\n// Filter-chain primitives, shared by BOTH chains: the inbound server chain\n// (`Filter<MethodMeta, WpResponse<unknown>>`, @webpieces/http-routing) and the outbound client\n// chain (`Filter<ClientRequest, Response>`, @webpieces/http-client-core). Declared once, here, in\n// the package both depend on — see the class doc for why a second pair would be a shim.\nexport { Filter } from './filters/Filter';\nexport type { Service } from './filters/Filter';\nexport { FilterChain } from './filters/FilterChain';\n\nexport {\n ApiError,\n ApiNotFoundError,\n ApiDependencyError,\n ApiBadGatewayError,\n ApiUnavailableError,\n ApiDependencyTimeoutError,\n ApiImplementationError,\n ApiRateLimitedError,\n ApiForbiddenError,\n ApiRequestTimeoutError,\n ApiEndUserError,\n ApiBadRequestError,\n ApiUnauthorizedError,\n ApiDependencyBackoffError,\n ApiConnectionError,\n ApiEndpointNotFoundError,\n ApiConflictError,\n ApiUnprocessableError,\n ApiPreconditionFailedError,\n ApiUnsupportedMediaTypeError,\n ApiNotImplementedError,\n ApiCodedError,\n ApiErrorCodec,\n ApiErrorPayload,\n ApiErrorBoundary,\n} from './errors';\nexport type { ApiErrorKind, ApiStatusCode, EdgeHttpStatus } from './errors';\nexport { ApiErrorHttpStatus } from './http/ApiErrorHttpStatus';\nexport type { PublishedKind, PublishedApiError } from './http/ApiErrorHttpStatus';\n"]}
|