@webpieces/core-util 0.4.957 → 0.4.958
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/package.json +1 -1
- package/src/http/ApiCallInfo.d.ts +71 -11
- package/src/http/ApiCallInfo.js +81 -7
- package/src/http/ApiCallInfo.js.map +1 -1
- package/src/http/ContextMgr.js +12 -2
- package/src/http/ContextMgr.js.map +1 -1
- package/src/http/DeletedAuthCompileAssertions.js +3 -1
- package/src/http/DeletedAuthCompileAssertions.js.map +1 -1
- package/src/http/DestinationTrust.d.ts +33 -4
- package/src/http/DestinationTrust.js +54 -11
- package/src/http/DestinationTrust.js.map +1 -1
- package/src/http/EndpointCompileAssertions.js +12 -6
- package/src/http/EndpointCompileAssertions.js.map +1 -1
- package/src/http/ExternalContextKeys.d.ts +31 -0
- package/src/http/ExternalContextKeys.js +77 -0
- package/src/http/ExternalContextKeys.js.map +1 -0
- package/src/http/HttpContract.d.ts +45 -7
- package/src/http/HttpContract.js +84 -6
- package/src/http/HttpContract.js.map +1 -1
- package/src/http/HttpEndpointOptions.d.ts +22 -3
- package/src/http/HttpEndpointOptions.js +8 -1
- package/src/http/HttpEndpointOptions.js.map +1 -1
- package/src/http/LogApiCall.d.ts +4 -1
- package/src/http/LogApiCall.js +7 -5
- package/src/http/LogApiCall.js.map +1 -1
- package/src/http/QueryParams.d.ts +47 -0
- package/src/http/QueryParams.js +89 -0
- package/src/http/QueryParams.js.map +1 -0
- package/src/http/RouteMetadata.d.ts +16 -2
- package/src/http/RouteMetadata.js +19 -3
- package/src/http/RouteMetadata.js.map +1 -1
- package/src/http/RouteMetadataFactory.d.ts +25 -0
- package/src/http/RouteMetadataFactory.js +97 -12
- package/src/http/RouteMetadataFactory.js.map +1 -1
- package/src/http/WpAuthApiKeyCompileAssertions.js +2 -2
- package/src/http/WpAuthApiKeyCompileAssertions.js.map +1 -1
- package/src/http/api-kind.d.ts +3 -2
- package/src/http/api-kind.js +5 -4
- package/src/http/api-kind.js.map +1 -1
- package/src/http/auth-mode.d.ts +8 -2
- package/src/http/auth-mode.js +11 -5
- package/src/http/auth-mode.js.map +1 -1
- package/src/http/decorators.d.ts +39 -21
- package/src/http/decorators.js +69 -37
- package/src/http/decorators.js.map +1 -1
- package/src/http/external-caller.d.ts +12 -17
- package/src/http/external-caller.js +12 -17
- package/src/http/external-caller.js.map +1 -1
- package/src/http/http-parameter-decorators.d.ts +17 -3
- package/src/http/http-parameter-decorators.js +27 -6
- package/src/http/http-parameter-decorators.js.map +1 -1
- package/src/index.d.ts +11 -7
- package/src/index.js +30 -12
- package/src/index.js.map +1 -1
|
@@ -6,8 +6,16 @@ import { RouteMetadata } from './RouteMetadata';
|
|
|
6
6
|
*/
|
|
7
7
|
export declare class RouteMetadataFactory {
|
|
8
8
|
static create(apiClass: Function, methodName: string, controllerClassName?: string): RouteMetadata;
|
|
9
|
+
/** Each declaration resolved against its argument's reflected type, in argument order. */
|
|
10
|
+
private static bindingsOf;
|
|
9
11
|
private static endpointPath;
|
|
10
12
|
private static validateEndpointOptions;
|
|
13
|
+
/**
|
|
14
|
+
* A CLOUDTASKS or CRON endpoint must be POST: Cloud Tasks, Cloud Scheduler and the in-memory task
|
|
15
|
+
* invoker all deliver with POST, so any other verb would register a route nothing ever reaches
|
|
16
|
+
* (a 404 at delivery time, not at startup).
|
|
17
|
+
*/
|
|
18
|
+
private static validateTriggerVerb;
|
|
11
19
|
private static validateStreamingParameters;
|
|
12
20
|
/**
|
|
13
21
|
* Refuse a contract in which two endpoints resolve to the same HTTP method + path.
|
|
@@ -36,6 +44,23 @@ export declare class RouteMetadataFactory {
|
|
|
36
44
|
*/
|
|
37
45
|
private static joinPath;
|
|
38
46
|
private static responseType;
|
|
47
|
+
/**
|
|
48
|
+
* The EXTERNAL_* rules ({@link assertExternalEndpointKinds}), then whether the contract is an
|
|
49
|
+
* outside system's API. `@ExternalContextKeys` says which context an OUTSIDE system may see, so it
|
|
50
|
+
* is refused on a contract this repo serves, which already receives the platform context.
|
|
51
|
+
*/
|
|
52
|
+
private static externalOutbound;
|
|
53
|
+
/**
|
|
54
|
+
* `@QueryString()` (a free-form {@link QueryParams}) is the query spelling of an EXTERNAL_OUTBOUND
|
|
55
|
+
* contract ONLY — our own contracts keep the typed `@QueryParam` — and its argument must BE a
|
|
56
|
+
* `QueryParams`. An item type (`@QueryParam('ids', Number)`) only means something on a list.
|
|
57
|
+
*/
|
|
58
|
+
private static validateQueryDeclarations;
|
|
59
|
+
/**
|
|
60
|
+
* `responseBody: NONE` means the success response is 0 bytes, so it cannot combine with a
|
|
61
|
+
* caller-owned full response or a streaming endpoint (both of which DEFINE a response).
|
|
62
|
+
*/
|
|
63
|
+
private static noResponseBody;
|
|
39
64
|
private static parameterTypes;
|
|
40
65
|
private static validateParameters;
|
|
41
66
|
private static valueType;
|
|
@@ -5,9 +5,12 @@ const decorators_1 = require("./decorators");
|
|
|
5
5
|
const http_parameter_decorators_1 = require("./http-parameter-decorators");
|
|
6
6
|
const HttpContract_1 = require("./HttpContract");
|
|
7
7
|
const RouteMetadata_1 = require("./RouteMetadata");
|
|
8
|
+
const QueryParams_1 = require("./QueryParams");
|
|
9
|
+
const HttpEndpointOptions_1 = require("./HttpEndpointOptions");
|
|
8
10
|
const StreamingContract_1 = require("./StreamingContract");
|
|
9
11
|
const authorization_1 = require("./authorization");
|
|
10
12
|
const decorators_2 = require("./decorators");
|
|
13
|
+
const ExternalContextKeys_1 = require("./ExternalContextKeys");
|
|
11
14
|
const StreamingContract_2 = require("./StreamingContract");
|
|
12
15
|
/**
|
|
13
16
|
* Build the one runtime route model consumed by incoming routing, in-process clients, and both
|
|
@@ -18,26 +21,36 @@ class RouteMetadataFactory {
|
|
|
18
21
|
// webpieces-disable no-function-outside-class -- deterministic decorator metadata factory shared by DI-free browser and server runtimes
|
|
19
22
|
static create(apiClass, methodName, controllerClassName) {
|
|
20
23
|
(0, decorators_2.assertEveryEndpointHasAuthMode)(apiClass);
|
|
24
|
+
const externalOutbound = this.externalOutbound(apiClass);
|
|
21
25
|
const fullPath = this.joinPath((0, decorators_1.getApiPath)(apiClass) ?? '', this.endpointPath(apiClass, methodName));
|
|
22
26
|
const options = (0, decorators_1.getEndpointOptions)(apiClass, methodName);
|
|
23
27
|
const streaming = (0, StreamingContract_1.getStreamingEndpoint)(apiClass, methodName);
|
|
24
28
|
const httpMethod = (0, decorators_1.getEndpointHttpMethod)(apiClass, methodName);
|
|
25
29
|
const operation = (0, decorators_1.getEndpointOperation)(apiClass, methodName);
|
|
26
30
|
this.validateEndpointOptions(apiClass, methodName, httpMethod, options.formPost, options.rawBody);
|
|
31
|
+
this.validateTriggerVerb(apiClass, methodName, httpMethod);
|
|
27
32
|
const parameterTypes = this.parameterTypes(apiClass, methodName);
|
|
28
33
|
const declarations = (0, http_parameter_decorators_1.getHttpParameterDeclarations)(apiClass, methodName);
|
|
34
|
+
this.validateQueryDeclarations(apiClass, methodName, externalOutbound, parameterTypes, declarations);
|
|
29
35
|
const bodyParameterIndex = streaming
|
|
30
36
|
? this.validateStreamingParameters(apiClass, methodName, httpMethod, parameterTypes, declarations, streaming)
|
|
31
37
|
: this.validateParameters(apiClass, methodName, fullPath, httpMethod, parameterTypes, declarations);
|
|
32
|
-
const bindings = declarations
|
|
33
|
-
|
|
34
|
-
.sort((a, b) => a.index - b.index);
|
|
35
|
-
const route = new RouteMetadata_1.RouteMetadata(httpMethod, fullPath, methodName, operation, controllerClassName, (0, decorators_1.getAuthMeta)(apiClass, methodName), apiClass.name || 'UnknownApi', options.formPost === true, (0, decorators_1.getMaskSpec)(apiClass, methodName), options.rawBody === true, bindings, bodyParameterIndex, this.responseType(options.responseType, apiClass, methodName), streaming, options.hideProgress === true, options.noLogging === true, options.allowUpgradeInFlight === true);
|
|
38
|
+
const bindings = this.bindingsOf(declarations, parameterTypes);
|
|
39
|
+
const route = new RouteMetadata_1.RouteMetadata(httpMethod, fullPath, methodName, operation, controllerClassName, (0, decorators_1.getAuthMeta)(apiClass, methodName), apiClass.name || 'UnknownApi', options.formPost === true, (0, decorators_1.getMaskSpec)(apiClass, methodName), options.rawBody === true, bindings, bodyParameterIndex, this.responseType(options.responseType, apiClass, methodName), streaming, options.hideProgress === true, options.noLogging === true, options.allowUpgradeInFlight === true, this.noResponseBody(options, streaming, apiClass, methodName));
|
|
36
40
|
route.authorization = (0, authorization_1.getAuthorization)(apiClass, methodName);
|
|
37
41
|
route.apiClass = apiClass;
|
|
38
42
|
route.localOnly = (0, decorators_2.isLocalOnly)(apiClass, methodName);
|
|
43
|
+
route.externalOutbound = externalOutbound;
|
|
44
|
+
route.externalContextKeys = (0, ExternalContextKeys_1.getExternalContextKeys)(apiClass, methodName);
|
|
39
45
|
return route;
|
|
40
46
|
}
|
|
47
|
+
/** Each declaration resolved against its argument's reflected type, in argument order. */
|
|
48
|
+
// webpieces-disable no-function-outside-class -- private pure helper for static route metadata construction
|
|
49
|
+
static bindingsOf(declarations, parameterTypes) {
|
|
50
|
+
return declarations
|
|
51
|
+
.map((declaration) => new HttpContract_1.HttpParameterBinding(declaration.index, declaration.source, declaration.wireName, this.valueType(parameterTypes[declaration.index]), declaration.itemType))
|
|
52
|
+
.sort((a, b) => a.index - b.index);
|
|
53
|
+
}
|
|
41
54
|
// webpieces-disable no-function-outside-class -- private pure helper for static route metadata construction
|
|
42
55
|
static endpointPath(apiClass, methodName) {
|
|
43
56
|
const path = ((0, decorators_1.getEndpoints)(apiClass) ?? {})[methodName];
|
|
@@ -47,11 +60,26 @@ class RouteMetadataFactory {
|
|
|
47
60
|
}
|
|
48
61
|
// webpieces-disable no-function-outside-class -- private pure helper keeps the existing static metadata factory below the method-size limit
|
|
49
62
|
static validateEndpointOptions(apiClass, methodName, httpMethod, formPost, rawBody) {
|
|
50
|
-
if (
|
|
51
|
-
|
|
63
|
+
if ((0, HttpContract_1.verbCarriesBody)(httpMethod))
|
|
64
|
+
return;
|
|
65
|
+
if (formPost === true) {
|
|
66
|
+
throw new Error(`${apiClass.name}.${methodName} is ${httpMethod} and cannot declare formPost:true because ${httpMethod} has no request body.`);
|
|
52
67
|
}
|
|
53
|
-
if (
|
|
54
|
-
throw new Error(`${apiClass.name}.${methodName} is
|
|
68
|
+
if (rawBody === true) {
|
|
69
|
+
throw new Error(`${apiClass.name}.${methodName} is ${httpMethod} and cannot declare rawBody:true because ${httpMethod} has no request body.`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A CLOUDTASKS or CRON endpoint must be POST: Cloud Tasks, Cloud Scheduler and the in-memory task
|
|
74
|
+
* invoker all deliver with POST, so any other verb would register a route nothing ever reaches
|
|
75
|
+
* (a 404 at delivery time, not at startup).
|
|
76
|
+
*/
|
|
77
|
+
// webpieces-disable no-function-outside-class -- private pure validator for the static metadata factory
|
|
78
|
+
static validateTriggerVerb(apiClass, methodName, httpMethod) {
|
|
79
|
+
const kind = (0, decorators_1.getEndpointKind)(apiClass, methodName);
|
|
80
|
+
if ((kind === 'cloudtasks' || kind === 'cron') && httpMethod !== HttpContract_1.POST) {
|
|
81
|
+
throw new Error(`${apiClass.name || 'Unknown'}.${methodName} is a ${kind.toUpperCase()} endpoint declared ${httpMethod}; ` +
|
|
82
|
+
`task and scheduler delivery always uses POST, so declare @Endpoint(POST, ...).`);
|
|
55
83
|
}
|
|
56
84
|
}
|
|
57
85
|
// webpieces-disable no-function-outside-class -- private pure validator alongside existing contract validators
|
|
@@ -63,7 +91,7 @@ class RouteMetadataFactory {
|
|
|
63
91
|
(streaming.direction !== StreamingContract_2.StreamDirection.REQUEST && !streaming.responseSchema)) {
|
|
64
92
|
throw new Error(`${label} requires generated streaming schemas. Run wp-openapi and registerStreamingCatalog(${apiClass.name}, catalog) before binding.`);
|
|
65
93
|
}
|
|
66
|
-
if (httpMethod !==
|
|
94
|
+
if (httpMethod !== HttpContract_1.POST)
|
|
67
95
|
throw new Error(`${label} is streaming and must use POST.`);
|
|
68
96
|
if (declarations.length > 0)
|
|
69
97
|
throw new Error(`${label} is streaming and cannot declare path/query parameters.`);
|
|
@@ -135,6 +163,63 @@ class RouteMetadataFactory {
|
|
|
135
163
|
return responseType;
|
|
136
164
|
throw new Error(`${apiClass.name}.${methodName} declares unsupported responseType '${String(responseType)}'; use body or full.`);
|
|
137
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* The EXTERNAL_* rules ({@link assertExternalEndpointKinds}), then whether the contract is an
|
|
168
|
+
* outside system's API. `@ExternalContextKeys` says which context an OUTSIDE system may see, so it
|
|
169
|
+
* is refused on a contract this repo serves, which already receives the platform context.
|
|
170
|
+
*/
|
|
171
|
+
// webpieces-disable no-function-outside-class -- private pure validator for the static metadata factory
|
|
172
|
+
static externalOutbound(apiClass) {
|
|
173
|
+
(0, decorators_2.assertExternalEndpointKinds)(apiClass);
|
|
174
|
+
const externalOutbound = (0, decorators_2.isExternalOutboundApi)(apiClass);
|
|
175
|
+
if (!externalOutbound && (0, ExternalContextKeys_1.declaresExternalContextKeys)(apiClass)) {
|
|
176
|
+
throw new Error(`${apiClass.name || 'Unknown'} declares @ExternalContextKeys but is not an EXTERNAL_OUTBOUND contract. ` +
|
|
177
|
+
'The list says which context an OUTSIDE system may see; a contract served in this repo already ' +
|
|
178
|
+
'receives the platform context.');
|
|
179
|
+
}
|
|
180
|
+
return externalOutbound;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* `@QueryString()` (a free-form {@link QueryParams}) is the query spelling of an EXTERNAL_OUTBOUND
|
|
184
|
+
* contract ONLY — our own contracts keep the typed `@QueryParam` — and its argument must BE a
|
|
185
|
+
* `QueryParams`. An item type (`@QueryParam('ids', Number)`) only means something on a list.
|
|
186
|
+
*/
|
|
187
|
+
// webpieces-disable no-function-outside-class -- private pure validator for the static metadata factory
|
|
188
|
+
static validateQueryDeclarations(apiClass, methodName, externalOutbound, parameterTypes, declarations) {
|
|
189
|
+
const label = `${apiClass.name || 'Unknown'}.${methodName}`;
|
|
190
|
+
for (const declaration of declarations) {
|
|
191
|
+
const type = parameterTypes[declaration.index];
|
|
192
|
+
if (declaration.source === 'query-map' && !externalOutbound) {
|
|
193
|
+
throw new Error(`${label} declares @QueryString(), which only an EXTERNAL_OUTBOUND contract may use — a vendor's ` +
|
|
194
|
+
`query string is free-form. A contract this repo serves names each key: @QueryParam('name').`);
|
|
195
|
+
}
|
|
196
|
+
if (declaration.source === 'query-map' && type !== undefined && type !== QueryParams_1.QueryParams) {
|
|
197
|
+
throw new Error(`${label} argument ${declaration.index} is @QueryString() and must be typed QueryParams.`);
|
|
198
|
+
}
|
|
199
|
+
if (declaration.itemType !== undefined && type !== undefined && type !== Array) {
|
|
200
|
+
throw new Error(`${label} @QueryParam('${declaration.wireName}', ${declaration.itemType.name}) names an item type, ` +
|
|
201
|
+
`but argument ${declaration.index} is not a list.`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* `responseBody: NONE` means the success response is 0 bytes, so it cannot combine with a
|
|
207
|
+
* caller-owned full response or a streaming endpoint (both of which DEFINE a response).
|
|
208
|
+
*/
|
|
209
|
+
// webpieces-disable no-function-outside-class -- private pure helper for the static metadata factory
|
|
210
|
+
static noResponseBody(options, streaming, apiClass, methodName) {
|
|
211
|
+
if (options.responseBody === undefined)
|
|
212
|
+
return false;
|
|
213
|
+
const label = `${apiClass.name || 'Unknown'}.${methodName}`;
|
|
214
|
+
if (options.responseBody !== HttpEndpointOptions_1.NONE) {
|
|
215
|
+
throw new Error(`${label} declares unsupported responseBody '${String(options.responseBody)}'; the only value is NONE.`);
|
|
216
|
+
}
|
|
217
|
+
if (options.responseType === 'full' || streaming !== undefined) {
|
|
218
|
+
throw new Error(`${label} declares responseBody: NONE together with ${streaming ? 'a stream' : "responseType: 'full'"}; ` +
|
|
219
|
+
'a 0-byte response cannot also carry a caller-owned or streamed response.');
|
|
220
|
+
}
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
138
223
|
// webpieces-disable no-function-outside-class -- Reflect metadata lookup has no injectable dependency
|
|
139
224
|
static parameterTypes(apiClass, methodName) {
|
|
140
225
|
return Reflect.getMetadata('design:paramtypes', apiClass.prototype, methodName) ?? [];
|
|
@@ -172,11 +257,11 @@ class RouteMetadataFactory {
|
|
|
172
257
|
if (!mappedIndexes.has(index))
|
|
173
258
|
unbound.push(index);
|
|
174
259
|
}
|
|
175
|
-
if (httpMethod
|
|
176
|
-
throw new Error(`${label} is
|
|
260
|
+
if (!(0, HttpContract_1.verbCarriesBody)(httpMethod) && unbound.length > 0) {
|
|
261
|
+
throw new Error(`${label} is ${httpMethod}, so every parameter must use @PathParam(...) or @QueryParam(...); argument ${unbound[0]} is unmapped.`);
|
|
177
262
|
}
|
|
178
263
|
if (unbound.length > 1) {
|
|
179
|
-
throw new Error(`${label} has ${unbound.length} unmapped parameters.
|
|
264
|
+
throw new Error(`${label} has ${unbound.length} unmapped parameters. ${httpMethod} permits exactly one unannotated request body; map every other parameter explicitly.`);
|
|
180
265
|
}
|
|
181
266
|
return unbound[0];
|
|
182
267
|
}
|
|
@@ -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';\nimport { getAuthorization } from './authorization';\nimport { assertEveryEndpointHasAuthMode, isLocalOnly } from './decorators';\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 assertEveryEndpointHasAuthMode(apiClass);\n const fullPath = this.joinPath(getApiPath(apiClass) ?? '', this.endpointPath(apiClass, methodName));\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(apiClass, methodName, httpMethod, options.formPost, options.rawBody);\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 const route = 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.hideProgress === true,\n options.noLogging === true,\n options.allowUpgradeInFlight === true,\n );\n route.authorization = getAuthorization(apiClass, methodName);\n route.apiClass = apiClass;\n route.localOnly = isLocalOnly(apiClass, methodName);\n return route;\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"]}
|
|
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 getEndpointKind,\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 POST,\n verbCarriesBody,\n} from './HttpContract';\nimport { RouteMetadata } from './RouteMetadata';\nimport { QueryParams } from './QueryParams';\nimport { EndpointOptions, NONE } from './HttpEndpointOptions';\nimport { getStreamingEndpoint } from './StreamingContract';\nimport { getAuthorization } from './authorization';\nimport {\n assertEveryEndpointHasAuthMode,\n assertExternalEndpointKinds,\n isExternalOutboundApi,\n isLocalOnly,\n} from './decorators';\nimport { declaresExternalContextKeys, getExternalContextKeys } from './ExternalContextKeys';\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 assertEveryEndpointHasAuthMode(apiClass);\n const externalOutbound = this.externalOutbound(apiClass);\n const fullPath = this.joinPath(getApiPath(apiClass) ?? '', this.endpointPath(apiClass, methodName));\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(apiClass, methodName, httpMethod, options.formPost, options.rawBody);\n this.validateTriggerVerb(apiClass, methodName, httpMethod);\n const parameterTypes = this.parameterTypes(apiClass, methodName);\n const declarations = getHttpParameterDeclarations(apiClass, methodName);\n this.validateQueryDeclarations(apiClass, methodName, externalOutbound, parameterTypes, declarations);\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 = this.bindingsOf(declarations, parameterTypes);\n const route = 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.hideProgress === true,\n options.noLogging === true,\n options.allowUpgradeInFlight === true,\n this.noResponseBody(options, streaming, apiClass, methodName),\n );\n route.authorization = getAuthorization(apiClass, methodName);\n route.apiClass = apiClass;\n route.localOnly = isLocalOnly(apiClass, methodName);\n route.externalOutbound = externalOutbound;\n route.externalContextKeys = getExternalContextKeys(apiClass, methodName);\n return route;\n }\n /** Each declaration resolved against its argument's reflected type, in argument order. */\n // webpieces-disable no-function-outside-class -- private pure helper for static route metadata construction\n private static bindingsOf(\n declarations: readonly HttpParameterDeclaration[],\n parameterTypes: readonly Function[],\n ): HttpParameterBinding[] {\n return 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 declaration.itemType,\n ),\n )\n .sort((a: HttpParameterBinding, b: HttpParameterBinding) => a.index - b.index);\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 (verbCarriesBody(httpMethod)) return;\n if (formPost === true) {\n throw new Error(\n `${apiClass.name}.${methodName} is ${httpMethod} and cannot declare formPost:true because ${httpMethod} has no request body.`,\n );\n }\n if (rawBody === true) {\n throw new Error(\n `${apiClass.name}.${methodName} is ${httpMethod} and cannot declare rawBody:true because ${httpMethod} has no request body.`,\n );\n }\n }\n\n /**\n * A CLOUDTASKS or CRON endpoint must be POST: Cloud Tasks, Cloud Scheduler and the in-memory task\n * invoker all deliver with POST, so any other verb would register a route nothing ever reaches\n * (a 404 at delivery time, not at startup).\n */\n // webpieces-disable no-function-outside-class -- private pure validator for the static metadata factory\n private static validateTriggerVerb(apiClass: Function, methodName: string, httpMethod: HttpMethod): void {\n const kind = getEndpointKind(apiClass, methodName);\n if ((kind === 'cloudtasks' || kind === 'cron') && httpMethod !== POST) {\n throw new Error(\n `${apiClass.name || 'Unknown'}.${methodName} is a ${kind.toUpperCase()} endpoint declared ${httpMethod}; ` +\n `task and scheduler delivery always uses POST, so declare @Endpoint(POST, ...).`,\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 /**\n * The EXTERNAL_* rules ({@link assertExternalEndpointKinds}), then whether the contract is an\n * outside system's API. `@ExternalContextKeys` says which context an OUTSIDE system may see, so it\n * is refused on a contract this repo serves, which already receives the platform context.\n */\n // webpieces-disable no-function-outside-class -- private pure validator for the static metadata factory\n private static externalOutbound(apiClass: Function): boolean {\n assertExternalEndpointKinds(apiClass);\n const externalOutbound = isExternalOutboundApi(apiClass);\n if (!externalOutbound && declaresExternalContextKeys(apiClass)) {\n throw new Error(\n `${apiClass.name || 'Unknown'} declares @ExternalContextKeys but is not an EXTERNAL_OUTBOUND contract. ` +\n 'The list says which context an OUTSIDE system may see; a contract served in this repo already ' +\n 'receives the platform context.',\n );\n }\n return externalOutbound;\n }\n\n /**\n * `@QueryString()` (a free-form {@link QueryParams}) is the query spelling of an EXTERNAL_OUTBOUND\n * contract ONLY — our own contracts keep the typed `@QueryParam` — and its argument must BE a\n * `QueryParams`. An item type (`@QueryParam('ids', Number)`) only means something on a list.\n */\n // webpieces-disable no-function-outside-class -- private pure validator for the static metadata factory\n private static validateQueryDeclarations(\n apiClass: Function,\n methodName: string,\n externalOutbound: boolean,\n parameterTypes: readonly Function[],\n declarations: readonly HttpParameterDeclaration[],\n ): void {\n const label = `${apiClass.name || 'Unknown'}.${methodName}`;\n for (const declaration of declarations) {\n const type = parameterTypes[declaration.index];\n if (declaration.source === 'query-map' && !externalOutbound) {\n throw new Error(\n `${label} declares @QueryString(), which only an EXTERNAL_OUTBOUND contract may use — a vendor's ` +\n `query string is free-form. A contract this repo serves names each key: @QueryParam('name').`,\n );\n }\n if (declaration.source === 'query-map' && type !== undefined && type !== QueryParams) {\n throw new Error(`${label} argument ${declaration.index} is @QueryString() and must be typed QueryParams.`);\n }\n if (declaration.itemType !== undefined && type !== undefined && type !== Array) {\n throw new Error(\n `${label} @QueryParam('${declaration.wireName}', ${declaration.itemType.name}) names an item type, ` +\n `but argument ${declaration.index} is not a list.`,\n );\n }\n }\n }\n\n /**\n * `responseBody: NONE` means the success response is 0 bytes, so it cannot combine with a\n * caller-owned full response or a streaming endpoint (both of which DEFINE a response).\n */\n // webpieces-disable no-function-outside-class -- private pure helper for the static metadata factory\n private static noResponseBody(\n options: EndpointOptions,\n streaming: StreamingEndpointMetadata | undefined,\n apiClass: Function,\n methodName: string,\n ): boolean {\n if (options.responseBody === undefined) return false;\n const label = `${apiClass.name || 'Unknown'}.${methodName}`;\n if (options.responseBody !== NONE) {\n throw new Error(\n `${label} declares unsupported responseBody '${String(options.responseBody)}'; the only value is NONE.`,\n );\n }\n if (options.responseType === 'full' || streaming !== undefined) {\n throw new Error(\n `${label} declares responseBody: NONE together with ${streaming ? 'a stream' : \"responseType: 'full'\"}; ` +\n 'a 0-byte response cannot also carry a caller-owned or streamed response.',\n );\n }\n return true;\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 (!verbCarriesBody(httpMethod) && unbound.length > 0) {\n throw new Error(\n `${label} is ${httpMethod}, 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. ${httpMethod} 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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WpAuthApiKeyCompileAssertions.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WpAuthApiKeyCompileAssertions.ts"],"names":[],"mappings":"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","sourcesContent":["import { ContextKey } from '../ContextKey';\nimport { apiKey, ApiKeyCredential, AuthMode } from './auth-mode';\nimport { WpAuth } from './decorators';\n\n/**\n * COMPILE-TIME assertions for the `apikey` member of the {@link AuthMode} union and for the\n * {@link apiKey} descriptor and {@link WpAuth} signature. Each `@ts-expect-error` below FAILS THE BUILD (TS2578, \"unused\n * '@ts-expect-error' directive\") if the line it guards ever starts compiling.\n *\n * WHY THIS IS NOT A `.spec.ts` FILE — same reason as its sibling `AuthJwtCompileAssertions.ts`:\n * tsconfig.lib.json EXCLUDES specs and vitest strips types with esbuild, so a `@ts-expect-error` in a\n * spec is inert and the suite passes whether or not the guarded line really errors.\n *\n * WHAT IT PINS.\n *\n * 1. The union member is `{ kind: 'apikey'; regime: string; credentials: [...] }` and nothing looser.\n * `regime` is the lookup key selecting WHICH key regime a route belongs to, so a mode with no\n * regime — or spelled `api-key`, which is what a reader guesses from the decorator — must not\n * type-check its way into a switch that would then silently miss it.\n * 2. `credentials` is NON-EMPTY. A regime that names no credential generates a published document\n * with no security block, which is precisely the silent failure the argument exists to remove, so\n * `[]` is a compile error rather than a runtime throw (shim shape #4).\n * 3. {@link ApiKeyCredential} makes the two OpenAPI schemes MUTUALLY exclusive: a header credential\n * must carry the ContextKey whose `httpHeader` it rides under, and a bearer credential must NOT —\n * its location IS `Authorization`, so a key beside it would be a lie a generator has to guess about.\n * 4. The ONE-ARGUMENT `@apiKey('regime')` form is GONE. It could declare a key regime while\n * saying nothing about where the credential rides, which is the whole defect; per the\n * no-backwards-compatibility rule there is no overload and no optional second parameter left\n * behind, and this directive is what proves it.\n * 5. The header-name STRING spelling `{ in: 'header', name: 'x-api-key' }` is GONE (#1249). A header\n * is defined ONCE, as a ContextKey, and named by that key everywhere; a string here was a second\n * copy a rename leaves silently stale.\n *\n * The EXHAUSTIVENESS half needs no directive: `apiKeyIsCoveredExhaustively` below returns on every\n * branch with no `default`, so dropping the `apikey` case makes tsc fail with TS7030 (not all code\n * paths return a value). That is the same property `AuthFilter.verifiesCaller` and\n * `DestinationTrust.forAuthMode` rely on, asserted here where it cannot be edited away by accident.\n */\nexport class AuthApiKeyCompileAssertions {\n private static readonly API_KEY = ContextKey.untrusted<string>('apiKey', 'x-api-key', true);\n private static readonly ORG_ID = ContextKey.untrusted<string>('organizationId', 'x-organization-id');\n\n /** The one legitimate spelling must keep compiling; asserted by the ABSENCE of an error. */\n legitimate(): AuthMode {\n const mode: AuthMode = {\n kind: 'apikey',\n regime: 'onetablet-partner',\n credentials: [\n {\n in: 'header',\n key: AuthApiKeyCompileAssertions.API_KEY,\n description: 'The key issued to your integration.',\n },\n { in: 'header', key: AuthApiKeyCompileAssertions.ORG_ID },\n ],\n };\n return mode;\n }\n\n /** The bearer branch, likewise asserted by the ABSENCE of an error. */\n legitimateBearer(): ApiKeyCredential {\n const credential: ApiKeyCredential = {\n in: 'bearer',\n description: 'Send the key as a bearer token.',\n };\n return credential;\n }\n\n /** apiKey takes regime and credentials; WpAuth wraps the descriptor in a nonempty list. */\n legitimateDecorator(): MethodDecorator {\n return WpAuth([apiKey('onetablet-partner', [{ in: 'header', key: AuthApiKeyCompileAssertions.API_KEY }])]);\n }\n\n /** Every one of these must be UNWRITABLE. A directive going unused here fails the build. */\n rejected(): void {\n // @ts-expect-error `regime` is REQUIRED — it selects which key regime, so it cannot be omitted\n const noRegime: AuthMode = {\n kind: 'apikey',\n credentials: [{ in: 'header', key: AuthApiKeyCompileAssertions.API_KEY }],\n };\n void noRegime;\n // @ts-expect-error `credentials` is REQUIRED — a regime that declares no location is the defect\n const noCredentials: AuthMode = { kind: 'apikey', regime: 'onetablet-partner' };\n void noCredentials;\n // @ts-expect-error EMPTY is not a widening — it would emit a document with no security block\n const empty: AuthMode = { kind: 'apikey', regime: 'onetablet-partner', credentials: [] };\n void empty;\n const misspelled: AuthMode = {\n // @ts-expect-error the discriminant is 'apikey'; 'api-key' is not a member of the union\n kind: 'api-key',\n regime: 'onetablet-partner',\n credentials: [{ in: 'header', key: AuthApiKeyCompileAssertions.API_KEY }],\n };\n void misspelled;\n // @ts-expect-error bearer's location IS `Authorization`; a ContextKey beside it is a lie\n const bearerWithKey: ApiKeyCredential = { in: 'bearer', key: AuthApiKeyCompileAssertions.API_KEY };\n void bearerWithKey;\n // @ts-expect-error a header credential with no ContextKey cannot become a securityScheme\n const headerWithNoKey: ApiKeyCredential = { in: 'header' };\n void headerWithNoKey;\n // @ts-expect-error the header-name STRING spelling is deleted — name the ContextKey: `key: MyKeys.API_KEY`\n const headerNameString: ApiKeyCredential = { in: 'header', name: 'x-api-key' };\n void headerNameString;\n // @ts-expect-error `key` is a ContextKey, never the header name itself\n const keyAsString: ApiKeyCredential = { in: 'header', key: 'x-api-key' };\n void keyAsString;\n // @ts-expect-error 'query' is not reachable — the framework declares only these two locations\n const unknownLocation: ApiKeyCredential = { in: 'query', key: AuthApiKeyCompileAssertions.API_KEY };\n void unknownLocation;\n }\n\n /** The apiKey descriptor requires an explicit credential list; no one-argument form exists. */\n oneArgumentFormIsGone(): void {\n // @ts-expect-error apiKey requires regime and credentials\n const legacy = apiKey('onetablet-partner');\n void legacy;\n }\n\n /**\n * Exhaustiveness, stated as code: NO `default`, a return on every branch. Deleting the `apikey`\n * case turns this into TS7030 — the compile error that forces a DECISION about a new mode's trust\n * posture rather than letting it land on one by accident.\n */\n apiKeyIsCoveredExhaustively(mode: AuthMode): string {\n switch (mode.kind) {\n case 'public':\n return 'public';\n case 'jwt':\n return 'jwt';\n case 'oidc':\n return 'oidc';\n case 'shared-secret':\n return 'shared-secret';\n case 'webhook':\n return 'webhook';\n case 'apikey':\n return 'apikey';\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"WpAuthApiKeyCompileAssertions.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WpAuthApiKeyCompileAssertions.ts"],"names":[],"mappings":"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","sourcesContent":["import { ContextKey } from '../ContextKey';\nimport { apiKey, ApiKeyCredential, AuthMode } from './auth-mode';\nimport { WpAuth } from './decorators';\n\n/**\n * COMPILE-TIME assertions for the `apikey` member of the {@link AuthMode} union and for the\n * {@link apiKey} descriptor and {@link WpAuth} signature. Each `@ts-expect-error` below FAILS THE BUILD (TS2578, \"unused\n * '@ts-expect-error' directive\") if the line it guards ever starts compiling.\n *\n * WHY THIS IS NOT A `.spec.ts` FILE — same reason as its sibling `AuthJwtCompileAssertions.ts`:\n * tsconfig.lib.json EXCLUDES specs and vitest strips types with esbuild, so a `@ts-expect-error` in a\n * spec is inert and the suite passes whether or not the guarded line really errors.\n *\n * WHAT IT PINS.\n *\n * 1. The union member is `{ kind: 'apikey'; regime: string; credentials: [...] }` and nothing looser.\n * `regime` is the lookup key selecting WHICH key regime a route belongs to, so a mode with no\n * regime — or spelled `api-key`, which is what a reader guesses from the decorator — must not\n * type-check its way into a switch that would then silently miss it.\n * 2. `credentials` is NON-EMPTY. A regime that names no credential generates a published document\n * with no security block, which is precisely the silent failure the argument exists to remove, so\n * `[]` is a compile error rather than a runtime throw (shim shape #4).\n * 3. {@link ApiKeyCredential} makes the two OpenAPI schemes MUTUALLY exclusive: a header credential\n * must carry the ContextKey whose `httpHeader` it rides under, and a bearer credential must NOT —\n * its location IS `Authorization`, so a key beside it would be a lie a generator has to guess about.\n * 4. The ONE-ARGUMENT `@apiKey('regime')` form is GONE. It could declare a key regime while\n * saying nothing about where the credential rides, which is the whole defect; per the\n * no-backwards-compatibility rule there is no overload and no optional second parameter left\n * behind, and this directive is what proves it.\n * 5. The header-name STRING spelling `{ in: 'header', name: 'x-api-key' }` is GONE (#1249). A header\n * is defined ONCE, as a ContextKey, and named by that key everywhere; a string here was a second\n * copy a rename leaves silently stale.\n *\n * The EXHAUSTIVENESS half needs no directive: `apiKeyIsCoveredExhaustively` below returns on every\n * branch with no `default`, so dropping the `apikey` case makes tsc fail with TS7030 (not all code\n * paths return a value). That is the same property `AuthFilter.verifiesCaller` and\n * `DestinationTrust.forAuthMode` rely on, asserted here where it cannot be edited away by accident.\n */\nexport class AuthApiKeyCompileAssertions {\n private static readonly API_KEY = ContextKey.untrusted<string>('apiKey', 'x-api-key', true);\n private static readonly ORG_ID = ContextKey.untrusted<string>('organizationId', 'x-organization-id');\n\n /** The one legitimate spelling must keep compiling; asserted by the ABSENCE of an error. */\n legitimate(): AuthMode {\n const mode: AuthMode = {\n kind: 'apikey',\n regime: 'onetablet-partner',\n credentials: [\n {\n in: 'header',\n key: AuthApiKeyCompileAssertions.API_KEY,\n description: 'The key issued to your integration.',\n },\n { in: 'header', key: AuthApiKeyCompileAssertions.ORG_ID },\n ],\n };\n return mode;\n }\n\n /** The bearer branch, likewise asserted by the ABSENCE of an error. */\n legitimateBearer(): ApiKeyCredential {\n const credential: ApiKeyCredential = {\n in: 'bearer',\n description: 'Send the key as a bearer token.',\n };\n return credential;\n }\n\n /** apiKey takes regime and credentials; WpAuth wraps the descriptor in a nonempty list. */\n legitimateDecorator(): MethodDecorator {\n return WpAuth([apiKey('onetablet-partner', [{ in: 'header', key: AuthApiKeyCompileAssertions.API_KEY }])]);\n }\n\n /** Every one of these must be UNWRITABLE. A directive going unused here fails the build. */\n rejected(): void {\n // @ts-expect-error `regime` is REQUIRED — it selects which key regime, so it cannot be omitted\n const noRegime: AuthMode = {\n kind: 'apikey',\n credentials: [{ in: 'header', key: AuthApiKeyCompileAssertions.API_KEY }],\n };\n void noRegime;\n // @ts-expect-error `credentials` is REQUIRED — a regime that declares no location is the defect\n const noCredentials: AuthMode = { kind: 'apikey', regime: 'onetablet-partner' };\n void noCredentials;\n // @ts-expect-error EMPTY is not a widening — it would emit a document with no security block\n const empty: AuthMode = { kind: 'apikey', regime: 'onetablet-partner', credentials: [] };\n void empty;\n const misspelled: AuthMode = {\n // @ts-expect-error the discriminant is 'apikey'; 'api-key' is not a member of the union\n kind: 'api-key',\n regime: 'onetablet-partner',\n credentials: [{ in: 'header', key: AuthApiKeyCompileAssertions.API_KEY }],\n };\n void misspelled;\n // @ts-expect-error bearer's location IS `Authorization`; a ContextKey beside it is a lie\n const bearerWithKey: ApiKeyCredential = { in: 'bearer', key: AuthApiKeyCompileAssertions.API_KEY };\n void bearerWithKey;\n // @ts-expect-error a header credential with no ContextKey cannot become a securityScheme\n const headerWithNoKey: ApiKeyCredential = { in: 'header' };\n void headerWithNoKey;\n // @ts-expect-error the header-name STRING spelling is deleted — name the ContextKey: `key: MyKeys.API_KEY`\n const headerNameString: ApiKeyCredential = { in: 'header', name: 'x-api-key' };\n void headerNameString;\n // @ts-expect-error `key` is a ContextKey, never the header name itself\n const keyAsString: ApiKeyCredential = { in: 'header', key: 'x-api-key' };\n void keyAsString;\n // @ts-expect-error 'query' is not reachable — the framework declares only these two locations\n const unknownLocation: ApiKeyCredential = { in: 'query', key: AuthApiKeyCompileAssertions.API_KEY };\n void unknownLocation;\n }\n\n /** The apiKey descriptor requires an explicit credential list; no one-argument form exists. */\n oneArgumentFormIsGone(): void {\n // @ts-expect-error apiKey requires regime and credentials\n const legacy = apiKey('onetablet-partner');\n void legacy;\n }\n\n /**\n * Exhaustiveness, stated as code: NO `default`, a return on every branch. Deleting the `apikey`\n * case turns this into TS7030 — the compile error that forces a DECISION about a new mode's trust\n * posture rather than letting it land on one by accident.\n */\n apiKeyIsCoveredExhaustively(mode: AuthMode): string {\n switch (mode.kind) {\n case 'public':\n return 'public';\n case 'jwt':\n return 'jwt';\n case 'oidc':\n return 'oidc';\n case 'shared-secret':\n return 'shared-secret';\n case 'vendorAuth':\n return 'vendorAuth';\n case 'apikey':\n return 'apikey';\n }\n }\n}\n"]}
|
package/src/http/api-kind.d.ts
CHANGED
|
@@ -44,8 +44,9 @@ export declare function assertApiKind(apiClass: Function, expected: ApiKind): vo
|
|
|
44
44
|
/**
|
|
45
45
|
* Which {@link EndpointKind}s each {@link ApiKind} may declare. A @PubSub contract is delivered
|
|
46
46
|
* asynchronously by definition, so `rpc` is meaningless on it; an @Rpc contract has no queue, so
|
|
47
|
-
* `cloudtasks`/`cron` on it would name a queue/schedule nothing could ever deliver to.
|
|
48
|
-
* is legal on both — a webhook posts synchronously, a push subscription does not.
|
|
47
|
+
* `cloudtasks`/`cron` on it would name a queue/schedule nothing could ever deliver to.
|
|
48
|
+
* `external-inbound` is legal on both — a webhook posts synchronously, a push subscription does not.
|
|
49
|
+
* `external-outbound` is an outside system's synchronous API WE call, so it is @Rpc only.
|
|
49
50
|
*
|
|
50
51
|
* Shared so the wiring-time assert below and the build-time architecture scan enforce ONE rule.
|
|
51
52
|
*/
|
package/src/http/api-kind.js
CHANGED
|
@@ -71,14 +71,15 @@ function assertApiKind(apiClass, expected) {
|
|
|
71
71
|
/**
|
|
72
72
|
* Which {@link EndpointKind}s each {@link ApiKind} may declare. A @PubSub contract is delivered
|
|
73
73
|
* asynchronously by definition, so `rpc` is meaningless on it; an @Rpc contract has no queue, so
|
|
74
|
-
* `cloudtasks`/`cron` on it would name a queue/schedule nothing could ever deliver to.
|
|
75
|
-
* is legal on both — a webhook posts synchronously, a push subscription does not.
|
|
74
|
+
* `cloudtasks`/`cron` on it would name a queue/schedule nothing could ever deliver to.
|
|
75
|
+
* `external-inbound` is legal on both — a webhook posts synchronously, a push subscription does not.
|
|
76
|
+
* `external-outbound` is an outside system's synchronous API WE call, so it is @Rpc only.
|
|
76
77
|
*
|
|
77
78
|
* Shared so the wiring-time assert below and the build-time architecture scan enforce ONE rule.
|
|
78
79
|
*/
|
|
79
80
|
exports.ENDPOINT_KINDS_BY_API_KIND = {
|
|
80
|
-
rpc: [decorators_1.RPC, decorators_1.
|
|
81
|
-
pubsub: [decorators_1.CLOUDTASKS, decorators_1.CRON, decorators_1.
|
|
81
|
+
rpc: [decorators_1.RPC, decorators_1.EXTERNAL_INBOUND, decorators_1.EXTERNAL_OUTBOUND],
|
|
82
|
+
pubsub: [decorators_1.CLOUDTASKS, decorators_1.CRON, decorators_1.EXTERNAL_INBOUND],
|
|
82
83
|
};
|
|
83
84
|
/**
|
|
84
85
|
* Validate @PubSub conventions at wiring time: the class must be @ApiPath + @PubSub, declare at
|
package/src/http/api-kind.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-kind.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/api-kind.ts"],"names":[],"mappings":"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","sourcesContent":["import 'reflect-metadata';\nimport {\n EndpointKind,\n RPC,\n CLOUDTASKS,\n CRON,\n EXTERNAL,\n METADATA_KEYS,\n getEndpoints,\n getEndpointKinds,\n isApiPath,\n} from './decorators';\n\n/**\n * API KIND — whether a contract is synchronous RPC or fire-and-forget over a queue — plus the\n * queue-naming rules that only a @PubSub contract has.\n *\n * Split out of `decorators.ts` purely for size (max-file-lines); the dependency runs ONE way,\n * api-kind -> decorators, so there is no cycle. Auth modes and endpoint shape stay in\n * `decorators.ts`; everything here is re-exported from the package barrel, so no consumer import\n * changes and there is no second spelling of anything.\n */\n\n// ============================================================\n// API kind (RPC vs PubSub/Cloud Tasks) + queue naming\n// ============================================================\n\n/**\n * API kind. 'rpc' = synchronous request/response (http-client ↔ ApiRoutingFactory).\n * 'pubsub' = fire-and-forget cloud task; the enqueue client (cloudtasks-client)\n * schedules a Cloud Task that is later delivered to the SAME controller endpoint.\n */\nexport type ApiKind = 'rpc' | 'pubsub';\n\n/**\n * @Rpc() - marks an API class as synchronous request/response (the default kind).\n * Present mostly for symmetry/readability; an undecorated API is treated as 'rpc'.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function Rpc(): ClassDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any) => {\n Reflect.defineMetadata(METADATA_KEYS.API_KIND, 'rpc' as ApiKind, target);\n };\n}\n\n/**\n * @PubSub() - marks an API class as fire-and-forget over Cloud Tasks. Every method\n * MUST return Promise<void> (a compile-time contract on the abstract API). The\n * enqueue client and the controller share this one class, exactly like RPC.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function PubSub(): ClassDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any) => {\n Reflect.defineMetadata(METADATA_KEYS.API_KIND, 'pubsub' as ApiKind, target);\n };\n}\n\n/**\n * @Queue(name) - override the Cloud Tasks queue name for a @PubSub method. Default\n * (no decorator) is `${ApiClassName}-${methodName}`, matched 1:1 by Terraform.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function Queue(name: string): MethodDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any, propertyKey: string | symbol, _descriptor: PropertyDescriptor) => {\n const metadataTarget = typeof target === 'function' ? target : target.constructor;\n const overrides: Record<string, string> =\n Reflect.getMetadata(METADATA_KEYS.QUEUE_OVERRIDE, metadataTarget) || {};\n overrides[propertyKey as string] = name;\n Reflect.defineMetadata(METADATA_KEYS.QUEUE_OVERRIDE, overrides, metadataTarget);\n };\n}\n\n/**\n * Get the API kind. Defaults to 'rpc' when neither @Rpc nor @PubSub is present.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function getApiKind(apiClass: Function): ApiKind {\n return (Reflect.getMetadata(METADATA_KEYS.API_KIND, apiClass) as ApiKind) ?? 'rpc';\n}\n\n/**\n * Assert the API class is of the expected kind (used by the clients: the RPC\n * client rejects a @PubSub api and vice-versa).\n * @throws Error if the kind doesn't match.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function assertApiKind(apiClass: Function, expected: ApiKind): void {\n const actual = getApiKind(apiClass);\n if (actual !== expected) {\n const apiName = apiClass.name || 'Unknown';\n throw new Error(\n `API ${apiName} is @${actual === 'pubsub' ? 'PubSub' : 'Rpc'} but a ` +\n `${expected === 'pubsub' ? '@PubSub (cloud task)' : '@Rpc'} API was required here.`,\n );\n }\n}\n\n/**\n * Which {@link EndpointKind}s each {@link ApiKind} may declare. A @PubSub contract is delivered\n * asynchronously by definition, so `rpc` is meaningless on it; an @Rpc contract has no queue, so\n * `cloudtasks`/`cron` on it would name a queue/schedule nothing could ever deliver to. `external`\n * is legal on both — a webhook posts synchronously, a push subscription does not.\n *\n * Shared so the wiring-time assert below and the build-time architecture scan enforce ONE rule.\n */\nexport const ENDPOINT_KINDS_BY_API_KIND: Record<ApiKind, readonly EndpointKind[]> = {\n rpc: [RPC, EXTERNAL],\n pubsub: [CLOUDTASKS, CRON, EXTERNAL],\n};\n\n/**\n * Validate @PubSub conventions at wiring time: the class must be @ApiPath + @PubSub, declare at\n * least one endpoint, and every endpoint must declare a kind this api kind can actually deliver.\n * (Return-type is Promise<void>, a compile-time contract — TS erases types at runtime so it cannot\n * be re-checked here.)\n * @throws Error if conventions are violated.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function assertPubSubConventions(apiClass: Function): void {\n assertApiKind(apiClass, 'pubsub');\n const apiName = apiClass.name || 'Unknown';\n if (!isApiPath(apiClass)) {\n throw new Error(`@PubSub API ${apiName} must also be decorated with @ApiPath()`);\n }\n const endpoints = getEndpoints(apiClass) || {};\n if (Object.keys(endpoints).length === 0) {\n throw new Error(`@PubSub API ${apiName} declares no @Endpoint methods`);\n }\n const allowed = ENDPOINT_KINDS_BY_API_KIND.pubsub;\n const kinds = getEndpointKinds(apiClass);\n for (const methodName of Object.keys(endpoints)) {\n const kind = kinds[methodName];\n if (kind !== undefined && allowed.includes(kind)) continue;\n throw new Error(\n `@PubSub API ${apiName}.${methodName} declares @Endpoint(..., '${kind ?? 'missing'}') — a ` +\n `@PubSub contract is delivered through a queue, so it must be one of: ${allowed.join(' | ')}.`,\n );\n }\n}\n\n/**\n * Resolve the Cloud Tasks queue name for a @PubSub method: the @Queue override if\n * present, else `${ApiClassName}-${methodName}`.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function getQueueName(apiClass: Function, methodName: string): string {\n const overrides: Record<string, string> =\n Reflect.getMetadata(METADATA_KEYS.QUEUE_OVERRIDE, apiClass) || {};\n return overrides[methodName] ?? `${apiClass.name || 'Unknown'}-${methodName}`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"api-kind.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/api-kind.ts"],"names":[],"mappings":"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","sourcesContent":["import 'reflect-metadata';\nimport {\n EndpointKind,\n RPC,\n CLOUDTASKS,\n CRON,\n EXTERNAL_INBOUND,\n EXTERNAL_OUTBOUND,\n METADATA_KEYS,\n getEndpoints,\n getEndpointKinds,\n isApiPath,\n} from './decorators';\n\n/**\n * API KIND — whether a contract is synchronous RPC or fire-and-forget over a queue — plus the\n * queue-naming rules that only a @PubSub contract has.\n *\n * Split out of `decorators.ts` purely for size (max-file-lines); the dependency runs ONE way,\n * api-kind -> decorators, so there is no cycle. Auth modes and endpoint shape stay in\n * `decorators.ts`; everything here is re-exported from the package barrel, so no consumer import\n * changes and there is no second spelling of anything.\n */\n\n// ============================================================\n// API kind (RPC vs PubSub/Cloud Tasks) + queue naming\n// ============================================================\n\n/**\n * API kind. 'rpc' = synchronous request/response (http-client ↔ ApiRoutingFactory).\n * 'pubsub' = fire-and-forget cloud task; the enqueue client (cloudtasks-client)\n * schedules a Cloud Task that is later delivered to the SAME controller endpoint.\n */\nexport type ApiKind = 'rpc' | 'pubsub';\n\n/**\n * @Rpc() - marks an API class as synchronous request/response (the default kind).\n * Present mostly for symmetry/readability; an undecorated API is treated as 'rpc'.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function Rpc(): ClassDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any) => {\n Reflect.defineMetadata(METADATA_KEYS.API_KIND, 'rpc' as ApiKind, target);\n };\n}\n\n/**\n * @PubSub() - marks an API class as fire-and-forget over Cloud Tasks. Every method\n * MUST return Promise<void> (a compile-time contract on the abstract API). The\n * enqueue client and the controller share this one class, exactly like RPC.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function PubSub(): ClassDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any) => {\n Reflect.defineMetadata(METADATA_KEYS.API_KIND, 'pubsub' as ApiKind, target);\n };\n}\n\n/**\n * @Queue(name) - override the Cloud Tasks queue name for a @PubSub method. Default\n * (no decorator) is `${ApiClassName}-${methodName}`, matched 1:1 by Terraform.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function Queue(name: string): MethodDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any, propertyKey: string | symbol, _descriptor: PropertyDescriptor) => {\n const metadataTarget = typeof target === 'function' ? target : target.constructor;\n const overrides: Record<string, string> =\n Reflect.getMetadata(METADATA_KEYS.QUEUE_OVERRIDE, metadataTarget) || {};\n overrides[propertyKey as string] = name;\n Reflect.defineMetadata(METADATA_KEYS.QUEUE_OVERRIDE, overrides, metadataTarget);\n };\n}\n\n/**\n * Get the API kind. Defaults to 'rpc' when neither @Rpc nor @PubSub is present.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function getApiKind(apiClass: Function): ApiKind {\n return (Reflect.getMetadata(METADATA_KEYS.API_KIND, apiClass) as ApiKind) ?? 'rpc';\n}\n\n/**\n * Assert the API class is of the expected kind (used by the clients: the RPC\n * client rejects a @PubSub api and vice-versa).\n * @throws Error if the kind doesn't match.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function assertApiKind(apiClass: Function, expected: ApiKind): void {\n const actual = getApiKind(apiClass);\n if (actual !== expected) {\n const apiName = apiClass.name || 'Unknown';\n throw new Error(\n `API ${apiName} is @${actual === 'pubsub' ? 'PubSub' : 'Rpc'} but a ` +\n `${expected === 'pubsub' ? '@PubSub (cloud task)' : '@Rpc'} API was required here.`,\n );\n }\n}\n\n/**\n * Which {@link EndpointKind}s each {@link ApiKind} may declare. A @PubSub contract is delivered\n * asynchronously by definition, so `rpc` is meaningless on it; an @Rpc contract has no queue, so\n * `cloudtasks`/`cron` on it would name a queue/schedule nothing could ever deliver to.\n * `external-inbound` is legal on both — a webhook posts synchronously, a push subscription does not.\n * `external-outbound` is an outside system's synchronous API WE call, so it is @Rpc only.\n *\n * Shared so the wiring-time assert below and the build-time architecture scan enforce ONE rule.\n */\nexport const ENDPOINT_KINDS_BY_API_KIND: Record<ApiKind, readonly EndpointKind[]> = {\n rpc: [RPC, EXTERNAL_INBOUND, EXTERNAL_OUTBOUND],\n pubsub: [CLOUDTASKS, CRON, EXTERNAL_INBOUND],\n};\n\n/**\n * Validate @PubSub conventions at wiring time: the class must be @ApiPath + @PubSub, declare at\n * least one endpoint, and every endpoint must declare a kind this api kind can actually deliver.\n * (Return-type is Promise<void>, a compile-time contract — TS erases types at runtime so it cannot\n * be re-checked here.)\n * @throws Error if conventions are violated.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function assertPubSubConventions(apiClass: Function): void {\n assertApiKind(apiClass, 'pubsub');\n const apiName = apiClass.name || 'Unknown';\n if (!isApiPath(apiClass)) {\n throw new Error(`@PubSub API ${apiName} must also be decorated with @ApiPath()`);\n }\n const endpoints = getEndpoints(apiClass) || {};\n if (Object.keys(endpoints).length === 0) {\n throw new Error(`@PubSub API ${apiName} declares no @Endpoint methods`);\n }\n const allowed = ENDPOINT_KINDS_BY_API_KIND.pubsub;\n const kinds = getEndpointKinds(apiClass);\n for (const methodName of Object.keys(endpoints)) {\n const kind = kinds[methodName];\n if (kind !== undefined && allowed.includes(kind)) continue;\n throw new Error(\n `@PubSub API ${apiName}.${methodName} declares @Endpoint(..., '${kind ?? 'missing'}') — a ` +\n `@PubSub contract is delivered through a queue, so it must be one of: ${allowed.join(' | ')}.`,\n );\n }\n}\n\n/**\n * Resolve the Cloud Tasks queue name for a @PubSub method: the @Queue override if\n * present, else `${ApiClassName}-${methodName}`.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function getQueueName(apiClass: Function, methodName: string): string {\n const overrides: Record<string, string> =\n Reflect.getMetadata(METADATA_KEYS.QUEUE_OVERRIDE, apiClass) || {};\n return overrides[methodName] ?? `${apiClass.name || 'Unknown'}-${methodName}`;\n}\n"]}
|
package/src/http/auth-mode.d.ts
CHANGED
|
@@ -55,7 +55,7 @@ export type AuthMethod = {
|
|
|
55
55
|
kind: 'shared-secret';
|
|
56
56
|
secretKey: string;
|
|
57
57
|
} | {
|
|
58
|
-
kind: '
|
|
58
|
+
kind: 'vendorAuth';
|
|
59
59
|
name: string;
|
|
60
60
|
} | {
|
|
61
61
|
kind: 'apikey';
|
|
@@ -103,5 +103,11 @@ export declare function jwt(): AuthMethod;
|
|
|
103
103
|
export declare function oidcSameProject(): OidcAuthMethod;
|
|
104
104
|
export declare function oidcSvcs(callers: OidcCallers): OidcAuthMethod;
|
|
105
105
|
export declare function sharedSecret(secretKey: string): AuthMethod;
|
|
106
|
-
|
|
106
|
+
/**
|
|
107
|
+
* A credential in a VENDOR's scheme, on both ends of a hop: the app-bound `VendorAuthHook` (core-context)
|
|
108
|
+
* mints it when WE call the vendor (and refreshes it once on a 401), and verifies it when the vendor
|
|
109
|
+
* calls US. `name` selects the vendor. A served `vendorAuth(...)` endpoint requires
|
|
110
|
+
* `@Endpoint(..., { rawBody: true })` so the hook has the exact bytes to verify.
|
|
111
|
+
*/
|
|
112
|
+
export declare function vendorAuth(name: string): AuthMethod;
|
|
107
113
|
export declare function apiKey(regime: string, credentials: ApiKeyCredentials): AuthMethod;
|
package/src/http/auth-mode.js
CHANGED
|
@@ -5,7 +5,7 @@ exports.jwt = jwt;
|
|
|
5
5
|
exports.oidcSameProject = oidcSameProject;
|
|
6
6
|
exports.oidcSvcs = oidcSvcs;
|
|
7
7
|
exports.sharedSecret = sharedSecret;
|
|
8
|
-
exports.
|
|
8
|
+
exports.vendorAuth = vendorAuth;
|
|
9
9
|
exports.apiKey = apiKey;
|
|
10
10
|
/** Alternatives remain unresolved until a concrete mechanism is chosen for this hop. */
|
|
11
11
|
class AuthMeta {
|
|
@@ -74,9 +74,9 @@ class AuthDeclaration {
|
|
|
74
74
|
keys = ['kind', 'secretKey'];
|
|
75
75
|
this.requireText(method.secretKey, 'sharedSecret');
|
|
76
76
|
break;
|
|
77
|
-
case '
|
|
77
|
+
case 'vendorAuth':
|
|
78
78
|
keys = ['kind', 'name'];
|
|
79
|
-
this.requireText(method.name, '
|
|
79
|
+
this.requireText(method.name, 'vendorAuth');
|
|
80
80
|
break;
|
|
81
81
|
case 'apikey':
|
|
82
82
|
keys = ['kind', 'regime', 'credentials'];
|
|
@@ -90,7 +90,7 @@ class AuthDeclaration {
|
|
|
90
90
|
throw new Error('Invalid API-key credential location.');
|
|
91
91
|
}
|
|
92
92
|
break;
|
|
93
|
-
default: throw new Error('Unknown @WpAuth method; use jwt/oidcSameProject/oidcSvcs/sharedSecret/
|
|
93
|
+
default: throw new Error('Unknown @WpAuth method; use jwt/oidcSameProject/oidcSvcs/sharedSecret/vendorAuth/apiKey descriptors.');
|
|
94
94
|
}
|
|
95
95
|
for (const key of Object.keys(method)) {
|
|
96
96
|
if (!keys.includes(key))
|
|
@@ -145,8 +145,14 @@ function oidcSameProject() { return { kind: 'oidc', callerScope: 'same-project'
|
|
|
145
145
|
function oidcSvcs(callers) { return { kind: 'oidc', callerScope: 'services', callers }; }
|
|
146
146
|
// webpieces-disable no-function-outside-class -- canonical credential descriptor factory
|
|
147
147
|
function sharedSecret(secretKey) { return { kind: 'shared-secret', secretKey }; }
|
|
148
|
+
/**
|
|
149
|
+
* A credential in a VENDOR's scheme, on both ends of a hop: the app-bound `VendorAuthHook` (core-context)
|
|
150
|
+
* mints it when WE call the vendor (and refreshes it once on a 401), and verifies it when the vendor
|
|
151
|
+
* calls US. `name` selects the vendor. A served `vendorAuth(...)` endpoint requires
|
|
152
|
+
* `@Endpoint(..., { rawBody: true })` so the hook has the exact bytes to verify.
|
|
153
|
+
*/
|
|
148
154
|
// webpieces-disable no-function-outside-class -- canonical credential descriptor factory
|
|
149
|
-
function
|
|
155
|
+
function vendorAuth(name) { return { kind: 'vendorAuth', name }; }
|
|
150
156
|
// webpieces-disable no-function-outside-class -- canonical credential descriptor factory
|
|
151
157
|
function apiKey(regime, credentials) { return { kind: 'apikey', regime, credentials }; }
|
|
152
158
|
//# sourceMappingURL=auth-mode.js.map
|