@webpieces/core-util 0.4.769 → 0.4.771

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/core-util",
3
- "version": "0.4.769",
3
+ "version": "0.4.771",
4
4
  "description": "Utility functions for WebPieces - works in browser and Node.js",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -0,0 +1,39 @@
1
+ /** HTTP verbs currently supported by generated Webpieces request/response contracts. */
2
+ export type ContractHttpMethod = 'GET' | 'POST';
3
+ /** Whether a generated client returns the response body or the whole transport-neutral response. */
4
+ export type EndpointResponseType = 'body' | 'full';
5
+ /** The wire source for one explicitly mapped scalar/list method parameter. */
6
+ export type HttpParameterSource = 'path' | 'query';
7
+ /** Runtime conversion supported for path/query values after TypeScript design metadata is read. */
8
+ export type HttpParameterValueType = 'string' | 'number' | 'boolean' | 'array' | 'unknown';
9
+ /** Metadata written by `@PathParam` / `@QueryParam` before route construction knows the value type. */
10
+ export declare class HttpParameterDeclaration {
11
+ readonly index: number;
12
+ readonly source: HttpParameterSource;
13
+ readonly wireName: string;
14
+ constructor(index: number, source: HttpParameterSource, wireName: string);
15
+ }
16
+ /** Fully resolved parameter metadata carried by every server and client route. */
17
+ export declare class HttpParameterBinding extends HttpParameterDeclaration {
18
+ readonly valueType: HttpParameterValueType;
19
+ constructor(index: number, source: HttpParameterSource, wireName: string, valueType: HttpParameterValueType);
20
+ }
21
+ /** Result of mapping one API method call to its HTTP path/query/body. */
22
+ export declare class BoundHttpRequest {
23
+ readonly path: string;
24
+ readonly body: unknown;
25
+ constructor(path: string, body: unknown);
26
+ }
27
+ /**
28
+ * The single path/query/body mapper shared by generated clients and the incoming server adapter.
29
+ * Java Webpieces' `HttpsJsonClientInvokeHandler` is the reference model: explicit wire names,
30
+ * encoded placeholders, omitted null query values, and repeated list keys. This port deliberately
31
+ * fixes Java's list-query overwrite bug and never depends on compiled JavaScript parameter names.
32
+ */
33
+ export declare class HttpContractMapper {
34
+ /** API arguments -> encoded relative URL + body. */
35
+ static toWire(pathTemplate: string, bindings: readonly HttpParameterBinding[], bodyParameterIndex: number | undefined, args: readonly unknown[]): BoundHttpRequest;
36
+ /** Decoded path/query/body -> API arguments in declaration order. */
37
+ static fromWire(bindings: readonly HttpParameterBinding[], bodyParameterIndex: number | undefined, body: unknown, pathValues: ReadonlyMap<string, string | readonly string[]>, queryValues: ReadonlyMap<string, string | readonly string[]>): unknown[];
38
+ private static convert;
39
+ }
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HttpContractMapper = exports.BoundHttpRequest = exports.HttpParameterBinding = exports.HttpParameterDeclaration = void 0;
4
+ const ApiError_1 = require("../errors/ApiError");
5
+ /** Metadata written by `@PathParam` / `@QueryParam` before route construction knows the value type. */
6
+ class HttpParameterDeclaration {
7
+ index;
8
+ source;
9
+ wireName;
10
+ constructor(index, source, wireName) {
11
+ this.index = index;
12
+ this.source = source;
13
+ this.wireName = wireName;
14
+ }
15
+ }
16
+ exports.HttpParameterDeclaration = HttpParameterDeclaration;
17
+ /** Fully resolved parameter metadata carried by every server and client route. */
18
+ class HttpParameterBinding extends HttpParameterDeclaration {
19
+ valueType;
20
+ constructor(index, source, wireName, valueType) {
21
+ super(index, source, wireName);
22
+ this.valueType = valueType;
23
+ }
24
+ }
25
+ exports.HttpParameterBinding = HttpParameterBinding;
26
+ /** Result of mapping one API method call to its HTTP path/query/body. */
27
+ class BoundHttpRequest {
28
+ path;
29
+ body;
30
+ constructor(path,
31
+ // webpieces-disable no-any-unknown -- the contract owns the request body type
32
+ body) {
33
+ this.path = path;
34
+ this.body = body;
35
+ }
36
+ }
37
+ exports.BoundHttpRequest = BoundHttpRequest;
38
+ /**
39
+ * The single path/query/body mapper shared by generated clients and the incoming server adapter.
40
+ * Java Webpieces' `HttpsJsonClientInvokeHandler` is the reference model: explicit wire names,
41
+ * encoded placeholders, omitted null query values, and repeated list keys. This port deliberately
42
+ * fixes Java's list-query overwrite bug and never depends on compiled JavaScript parameter names.
43
+ */
44
+ class HttpContractMapper {
45
+ /** API arguments -> encoded relative URL + body. */
46
+ // webpieces-disable no-function-outside-class -- pure cross-runtime mapper used by DI-free browser clients and server adapters
47
+ static toWire(pathTemplate, bindings, bodyParameterIndex,
48
+ // webpieces-disable no-any-unknown -- arbitrary API method parameters cross this generic boundary
49
+ args) {
50
+ let path = pathTemplate;
51
+ const query = [];
52
+ for (const binding of bindings) {
53
+ const value = args[binding.index];
54
+ if (binding.source === 'path') {
55
+ if (value === undefined || value === null) {
56
+ throw new ApiError_1.ApiBadRequestError(`Missing path parameter '${binding.wireName}' at argument ${binding.index}.`);
57
+ }
58
+ const token = `{${binding.wireName}}`;
59
+ path = path.split(token).join(encodeURIComponent(String(value)));
60
+ continue;
61
+ }
62
+ if (value === undefined || value === null)
63
+ continue;
64
+ const values = Array.isArray(value) ? value : [value];
65
+ for (const item of values) {
66
+ if (item === undefined || item === null)
67
+ continue;
68
+ query.push(`${encodeURIComponent(binding.wireName)}=${encodeURIComponent(String(item))}`);
69
+ }
70
+ }
71
+ if (/\{[^{}]+\}/.test(path)) {
72
+ throw new ApiError_1.ApiBadRequestError(`Not every path placeholder was supplied for '${pathTemplate}'.`);
73
+ }
74
+ const suffix = query.length === 0 ? '' : `${path.includes('?') ? '&' : '?'}${query.join('&')}`;
75
+ return new BoundHttpRequest(`${path}${suffix}`, bodyParameterIndex === undefined ? undefined : args[bodyParameterIndex]);
76
+ }
77
+ /** Decoded path/query/body -> API arguments in declaration order. */
78
+ // webpieces-disable no-function-outside-class -- pure inverse mapper paired with toWire
79
+ static fromWire(bindings, bodyParameterIndex,
80
+ // webpieces-disable no-any-unknown -- the contract owns the request body type
81
+ body, pathValues, queryValues) {
82
+ const highestBinding = bindings.reduce((highest, binding) => Math.max(highest, binding.index), -1);
83
+ const argCount = Math.max(highestBinding, bodyParameterIndex ?? -1) + 1;
84
+ // webpieces-disable no-any-unknown -- API argument types are erased at the shared wire boundary
85
+ const args = new Array(argCount);
86
+ if (bodyParameterIndex !== undefined)
87
+ args[bodyParameterIndex] = body;
88
+ for (const binding of bindings) {
89
+ const values = binding.source === 'path' ? pathValues : queryValues;
90
+ const raw = values.get(binding.wireName);
91
+ if (raw === undefined) {
92
+ if (binding.source === 'path') {
93
+ throw new ApiError_1.ApiBadRequestError(`Missing path parameter '${binding.wireName}'.`);
94
+ }
95
+ args[binding.index] = undefined;
96
+ continue;
97
+ }
98
+ args[binding.index] = this.convert(binding, raw);
99
+ }
100
+ return args;
101
+ }
102
+ // webpieces-disable no-function-outside-class -- private pure helper for the static wire mapper
103
+ static convert(binding, raw) {
104
+ const values = Array.isArray(raw) ? raw : [raw];
105
+ if (binding.valueType === 'array')
106
+ return [...values];
107
+ if (values.length !== 1) {
108
+ throw new ApiError_1.ApiBadRequestError(`Parameter '${binding.wireName}' was repeated but is not declared as an array.`);
109
+ }
110
+ const value = values[0];
111
+ switch (binding.valueType) {
112
+ case 'string':
113
+ case 'unknown':
114
+ return value;
115
+ case 'number': {
116
+ if (value.trim() === '') {
117
+ throw new ApiError_1.ApiBadRequestError(`Parameter '${binding.wireName}' must be a number.`);
118
+ }
119
+ const parsed = Number(value);
120
+ if (!Number.isFinite(parsed)) {
121
+ throw new ApiError_1.ApiBadRequestError(`Parameter '${binding.wireName}' must be a number.`);
122
+ }
123
+ return parsed;
124
+ }
125
+ case 'boolean':
126
+ if (value === 'true')
127
+ return true;
128
+ if (value === 'false')
129
+ return false;
130
+ throw new ApiError_1.ApiBadRequestError(`Parameter '${binding.wireName}' must be 'true' or 'false'.`);
131
+ }
132
+ }
133
+ }
134
+ exports.HttpContractMapper = HttpContractMapper;
135
+ //# sourceMappingURL=HttpContract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HttpContract.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/HttpContract.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApiBadRequestError } from '../errors/ApiError';\n\n/** HTTP verbs currently supported by generated Webpieces request/response contracts. */\nexport type ContractHttpMethod = 'GET' | 'POST';\n\n/** Whether a generated client returns the response body or the whole transport-neutral response. */\nexport type EndpointResponseType = 'body' | 'full';\n\n/** The wire source for one explicitly mapped scalar/list method parameter. */\nexport type HttpParameterSource = 'path' | 'query';\n\n/** Runtime conversion supported for path/query values after TypeScript design metadata is read. */\nexport type HttpParameterValueType = 'string' | 'number' | 'boolean' | 'array' | 'unknown';\n\n/** Concrete runtime values produced for path/query parameters. */\ntype ConvertedHttpParameter = string | number | boolean | readonly string[];\n\n/** Metadata written by `@PathParam` / `@QueryParam` before route construction knows the value type. */\nexport class HttpParameterDeclaration {\n constructor(\n public readonly index: number,\n public readonly source: HttpParameterSource,\n public readonly wireName: string,\n ) {}\n}\n\n/** Fully resolved parameter metadata carried by every server and client route. */\nexport class HttpParameterBinding extends HttpParameterDeclaration {\n constructor(\n index: number,\n source: HttpParameterSource,\n wireName: string,\n public readonly valueType: HttpParameterValueType,\n ) {\n super(index, source, wireName);\n }\n}\n\n/** Result of mapping one API method call to its HTTP path/query/body. */\nexport class BoundHttpRequest {\n constructor(\n public readonly path: string,\n // webpieces-disable no-any-unknown -- the contract owns the request body type\n public readonly body: unknown,\n ) {}\n}\n\n/**\n * The single path/query/body mapper shared by generated clients and the incoming server adapter.\n * Java Webpieces' `HttpsJsonClientInvokeHandler` is the reference model: explicit wire names,\n * encoded placeholders, omitted null query values, and repeated list keys. This port deliberately\n * fixes Java's list-query overwrite bug and never depends on compiled JavaScript parameter names.\n */\nexport class HttpContractMapper {\n /** API arguments -> encoded relative URL + body. */\n // webpieces-disable no-function-outside-class -- pure cross-runtime mapper used by DI-free browser clients and server adapters\n static toWire(\n pathTemplate: string,\n bindings: readonly HttpParameterBinding[],\n bodyParameterIndex: number | undefined,\n // webpieces-disable no-any-unknown -- arbitrary API method parameters cross this generic boundary\n args: readonly unknown[],\n ): BoundHttpRequest {\n let path = pathTemplate;\n const query: string[] = [];\n\n for (const binding of bindings) {\n const value = args[binding.index];\n if (binding.source === 'path') {\n if (value === undefined || value === null) {\n throw new ApiBadRequestError(\n `Missing path parameter '${binding.wireName}' at argument ${binding.index}.`,\n );\n }\n const token = `{${binding.wireName}}`;\n path = path.split(token).join(encodeURIComponent(String(value)));\n continue;\n }\n\n if (value === undefined || value === null) continue;\n const values = Array.isArray(value) ? value : [value];\n for (const item of values) {\n if (item === undefined || item === null) continue;\n query.push(\n `${encodeURIComponent(binding.wireName)}=${encodeURIComponent(String(item))}`,\n );\n }\n }\n\n if (/\\{[^{}]+\\}/.test(path)) {\n throw new ApiBadRequestError(\n `Not every path placeholder was supplied for '${pathTemplate}'.`,\n );\n }\n const suffix =\n query.length === 0 ? '' : `${path.includes('?') ? '&' : '?'}${query.join('&')}`;\n return new BoundHttpRequest(\n `${path}${suffix}`,\n bodyParameterIndex === undefined ? undefined : args[bodyParameterIndex],\n );\n }\n\n /** Decoded path/query/body -> API arguments in declaration order. */\n // webpieces-disable no-function-outside-class -- pure inverse mapper paired with toWire\n static fromWire(\n bindings: readonly HttpParameterBinding[],\n bodyParameterIndex: number | undefined,\n // webpieces-disable no-any-unknown -- the contract owns the request body type\n body: unknown,\n pathValues: ReadonlyMap<string, string | readonly string[]>,\n queryValues: ReadonlyMap<string, string | readonly string[]>,\n ): unknown[] {\n const highestBinding = bindings.reduce(\n (highest: number, binding: HttpParameterBinding) => Math.max(highest, binding.index),\n -1,\n );\n const argCount = Math.max(highestBinding, bodyParameterIndex ?? -1) + 1;\n // webpieces-disable no-any-unknown -- API argument types are erased at the shared wire boundary\n const args = new Array<unknown>(argCount);\n if (bodyParameterIndex !== undefined) args[bodyParameterIndex] = body;\n\n for (const binding of bindings) {\n const values = binding.source === 'path' ? pathValues : queryValues;\n const raw = values.get(binding.wireName);\n if (raw === undefined) {\n if (binding.source === 'path') {\n throw new ApiBadRequestError(`Missing path parameter '${binding.wireName}'.`);\n }\n args[binding.index] = undefined;\n continue;\n }\n args[binding.index] = this.convert(binding, raw);\n }\n return args;\n }\n\n // webpieces-disable no-function-outside-class -- private pure helper for the static wire mapper\n private static convert(\n binding: HttpParameterBinding,\n raw: string | readonly string[],\n ): ConvertedHttpParameter {\n const values = Array.isArray(raw) ? raw : [raw];\n if (binding.valueType === 'array') return [...values];\n if (values.length !== 1) {\n throw new ApiBadRequestError(\n `Parameter '${binding.wireName}' was repeated but is not declared as an array.`,\n );\n }\n const value = values[0];\n switch (binding.valueType) {\n case 'string':\n case 'unknown':\n return value;\n case 'number': {\n if (value.trim() === '') {\n throw new ApiBadRequestError(\n `Parameter '${binding.wireName}' must be a number.`,\n );\n }\n const parsed = Number(value);\n if (!Number.isFinite(parsed)) {\n throw new ApiBadRequestError(\n `Parameter '${binding.wireName}' must be a number.`,\n );\n }\n return parsed;\n }\n case 'boolean':\n if (value === 'true') return true;\n if (value === 'false') return false;\n throw new ApiBadRequestError(\n `Parameter '${binding.wireName}' must be 'true' or 'false'.`,\n );\n }\n }\n}\n"]}
@@ -0,0 +1,29 @@
1
+ import { ExternalSystemKind } from './external-caller';
2
+ import { ContractHttpMethod, EndpointResponseType } from './HttpContract';
3
+ /** Options for one `@Endpoint`, stored parallel to the method-to-path metadata. */
4
+ export interface EndpointOptions {
5
+ /** HTTP verb for this route. Defaults to POST. */
6
+ httpMethod?: ContractHttpMethod;
7
+ /** Return only a JSON body (default), or the full transport-neutral status/header/body value. */
8
+ responseType?: EndpointResponseType;
9
+ /**
10
+ * Parse the request body as application/x-www-form-urlencoded instead of JSON. The request
11
+ * DTO must be flat; urlencoded has no nesting. Default false = JSON.
12
+ */
13
+ formPost?: boolean;
14
+ /**
15
+ * Retain the verbatim request bytes and absolute URL for a `@WpAuthWebhook` callback to verify.
16
+ * This is retention, not new buffering: the Express adapter already accumulates the body.
17
+ *
18
+ * `@WpAuthWebhook` requires this option at wiring time. It combines with `formPost` for vendors
19
+ * that sign flat form bodies, e.g. `{ formPost: true, rawBody: true }`.
20
+ */
21
+ rawBody?: boolean;
22
+ }
23
+ /** An external endpoint must also identify the outside system that calls it. */
24
+ export interface ExternalEndpointOptions extends EndpointOptions {
25
+ /** The outside system (`'twilio'`) — graph identity, not display text. */
26
+ calledBy: string;
27
+ /** What that caller is; picks its graph shape. Defaults to `'saas'`. */
28
+ callerKind?: ExternalSystemKind;
29
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=HttpEndpointOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HttpEndpointOptions.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/HttpEndpointOptions.ts"],"names":[],"mappings":"","sourcesContent":["import { ExternalSystemKind } from './external-caller';\nimport { ContractHttpMethod, EndpointResponseType } from './HttpContract';\n\n/** Options for one `@Endpoint`, stored parallel to the method-to-path metadata. */\nexport interface EndpointOptions {\n /** HTTP verb for this route. Defaults to POST. */\n httpMethod?: ContractHttpMethod;\n /** Return only a JSON body (default), or the full transport-neutral status/header/body value. */\n responseType?: EndpointResponseType;\n /**\n * Parse the request body as application/x-www-form-urlencoded instead of JSON. The request\n * DTO must be flat; urlencoded has no nesting. Default false = JSON.\n */\n formPost?: boolean;\n /**\n * Retain the verbatim request bytes and absolute URL for a `@WpAuthWebhook` callback to verify.\n * This is retention, not new buffering: the Express adapter already accumulates the body.\n *\n * `@WpAuthWebhook` requires this option at wiring time. It combines with `formPost` for vendors\n * that sign flat form bodies, e.g. `{ formPost: true, rawBody: true }`.\n */\n rawBody?: boolean;\n}\n\n/** An external endpoint must also identify the outside system that calls it. */\nexport interface ExternalEndpointOptions extends EndpointOptions {\n /** The outside system (`'twilio'`) — graph identity, not display text. */\n calledBy: string;\n /** What that caller is; picks its graph shape. Defaults to `'saas'`. */\n callerKind?: ExternalSystemKind;\n}\n"]}
@@ -46,9 +46,9 @@ export declare class HttpResponseStatus {
46
46
  * {@link ApiErrorPayload} there, and the built-in client mapping reads it back as one — but an app that
47
47
  * owns the whole response owns the body shape too, so the framework does not constrain it.
48
48
  */
49
- export declare class HttpResponseDto {
49
+ export declare class HttpResponseDto<TBody = unknown> {
50
50
  readonly status: HttpResponseStatus;
51
51
  readonly headers: readonly HttpHeader[];
52
- readonly body: unknown;
53
- constructor(status: HttpResponseStatus, headers: readonly HttpHeader[], body: unknown);
52
+ readonly body: TBody;
53
+ constructor(status: HttpResponseStatus, headers: readonly HttpHeader[], body: TBody);
54
54
  }
@@ -57,6 +57,7 @@ exports.HttpResponseStatus = HttpResponseStatus;
57
57
  * {@link ApiErrorPayload} there, and the built-in client mapping reads it back as one — but an app that
58
58
  * owns the whole response owns the body shape too, so the framework does not constrain it.
59
59
  */
60
+ // webpieces-disable no-any-unknown -- the app owns the full-response body shape
60
61
  class HttpResponseDto {
61
62
  status;
62
63
  headers;
@@ -1 +1 @@
1
- {"version":3,"file":"HttpResponseDto.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/HttpResponseDto.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAa,UAAU;IAEC;IACA;IAFpB,YACoB,IAAY,EACZ,KAAa;QADb,SAAI,GAAJ,IAAI,CAAQ;QACZ,UAAK,GAAL,KAAK,CAAQ;IAC9B,CAAC;CACP;AALD,gCAKC;AAED,4FAA4F;AAC5F,MAAa,kBAAkB;IAEP;IACA;IAFpB,YACoB,IAAY,EACZ,MAAc;QADd,SAAI,GAAJ,IAAI,CAAQ;QACZ,WAAM,GAAN,MAAM,CAAQ;IAC/B,CAAC;CACP;AALD,gDAKC;AAED;;;;;;GAMG;AACH,MAAa,eAAe;IAEJ;IACA;IAEA;IAJpB,YACoB,MAA0B,EAC1B,OAA8B;IAC9C,mLAAmL;IACnK,IAAa;QAHb,WAAM,GAAN,MAAM,CAAoB;QAC1B,YAAO,GAAP,OAAO,CAAuB;QAE9B,SAAI,GAAJ,IAAI,CAAS;IAC9B,CAAC;CACP;AAPD,0CAOC","sourcesContent":["/**\n * HttpResponseDto - the ENTIRE HTTP response, as pure data, in the ONE form webpieces speaks.\n *\n * Modelled on java webpieces' `http/http1_1-parser/.../api/dto/` (`HttpPayload` -> `HttpMessage` ->\n * `HttpResponse` -> `HttpResponseStatusLine` -> `HttpResponseStatus`), flattened to the KISS subset\n * this framework actually needs.\n *\n * # Why a DTO at all, instead of handing an app express's `res` or fetch's `Response`\n *\n * Node/express and browser fetch model a response completely differently, and an\n * {@link ErrorTranslators} implementation is registered ONCE and serves BOTH — the server writing a\n * response and every client in the process reading one. So neither transport's object can be the\n * currency. webpieces normalises both into this DTO at its own boundary, and the app only ever sees\n * this. One form, both transports, both directions.\n *\n * # The two properties copied from java deliberately\n *\n * - **Headers are a LIST of `{name, value}`, not a Map.** HTTP permits repeats (`Set-Cookie` is the\n * everyday one) and a Map silently drops all but the last. The REQUEST side of webpieces-ts\n * already respects this (`readExpressHeaders` returns `Map<string, string[]>`), so the response\n * side must not be the half that loses data.\n * - **Status is `{ code, reason }`, not a bare number.** The reason phrase is part of the response\n * and an app may want its own ('Order Not Found' beside a 460).\n *\n * `HttpVersion` from the java DTO is deliberately OMITTED: express and fetch each own the version,\n * and no app decision depends on it.\n *\n * Pure data, so these are CLASSES with explicit constructors (webpieces guideline: data => classes),\n * and they live in core-util so the identical types reach a node server and a browser bundle.\n */\nexport class HttpHeader {\n constructor(\n public readonly name: string,\n public readonly value: string,\n ) {}\n}\n\n/** The status line's status: the numeric code AND the reason phrase that goes beside it. */\nexport class HttpResponseStatus {\n constructor(\n public readonly code: number,\n public readonly reason: string,\n ) {}\n}\n\n/**\n * The whole response: status (code + reason), the header LIST, and the body.\n *\n * `body` is `unknown` because it is whatever the app chose to publish. webpieces' OWN default puts a\n * {@link ApiErrorPayload} there, and the built-in client mapping reads it back as one — but an app that\n * owns the whole response owns the body shape too, so the framework does not constrain it.\n */\nexport class HttpResponseDto {\n constructor(\n public readonly status: HttpResponseStatus,\n public readonly headers: readonly HttpHeader[],\n // webpieces-disable no-any-unknown -- the app owns the body shape when it owns the response; webpieces' own default puts a ApiErrorPayload here, an app puts whatever it publishes\n public readonly body: unknown,\n ) {}\n}\n"]}
1
+ {"version":3,"file":"HttpResponseDto.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/HttpResponseDto.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAa,UAAU;IAEC;IACA;IAFpB,YACoB,IAAY,EACZ,KAAa;QADb,SAAI,GAAJ,IAAI,CAAQ;QACZ,UAAK,GAAL,KAAK,CAAQ;IAC9B,CAAC;CACP;AALD,gCAKC;AAED,4FAA4F;AAC5F,MAAa,kBAAkB;IAEP;IACA;IAFpB,YACoB,IAAY,EACZ,MAAc;QADd,SAAI,GAAJ,IAAI,CAAQ;QACZ,WAAM,GAAN,MAAM,CAAQ;IAC/B,CAAC;CACP;AALD,gDAKC;AAED;;;;;;GAMG;AACH,gFAAgF;AAChF,MAAa,eAAe;IAEJ;IACA;IAEA;IAJpB,YACoB,MAA0B,EAC1B,OAA8B;IAC9C,mLAAmL;IACnK,IAAW;QAHX,WAAM,GAAN,MAAM,CAAoB;QAC1B,YAAO,GAAP,OAAO,CAAuB;QAE9B,SAAI,GAAJ,IAAI,CAAO;IAC5B,CAAC;CACP;AAPD,0CAOC","sourcesContent":["/**\n * HttpResponseDto - the ENTIRE HTTP response, as pure data, in the ONE form webpieces speaks.\n *\n * Modelled on java webpieces' `http/http1_1-parser/.../api/dto/` (`HttpPayload` -> `HttpMessage` ->\n * `HttpResponse` -> `HttpResponseStatusLine` -> `HttpResponseStatus`), flattened to the KISS subset\n * this framework actually needs.\n *\n * # Why a DTO at all, instead of handing an app express's `res` or fetch's `Response`\n *\n * Node/express and browser fetch model a response completely differently, and an\n * {@link ErrorTranslators} implementation is registered ONCE and serves BOTH — the server writing a\n * response and every client in the process reading one. So neither transport's object can be the\n * currency. webpieces normalises both into this DTO at its own boundary, and the app only ever sees\n * this. One form, both transports, both directions.\n *\n * # The two properties copied from java deliberately\n *\n * - **Headers are a LIST of `{name, value}`, not a Map.** HTTP permits repeats (`Set-Cookie` is the\n * everyday one) and a Map silently drops all but the last. The REQUEST side of webpieces-ts\n * already respects this (`readExpressHeaders` returns `Map<string, string[]>`), so the response\n * side must not be the half that loses data.\n * - **Status is `{ code, reason }`, not a bare number.** The reason phrase is part of the response\n * and an app may want its own ('Order Not Found' beside a 460).\n *\n * `HttpVersion` from the java DTO is deliberately OMITTED: express and fetch each own the version,\n * and no app decision depends on it.\n *\n * Pure data, so these are CLASSES with explicit constructors (webpieces guideline: data => classes),\n * and they live in core-util so the identical types reach a node server and a browser bundle.\n */\nexport class HttpHeader {\n constructor(\n public readonly name: string,\n public readonly value: string,\n ) {}\n}\n\n/** The status line's status: the numeric code AND the reason phrase that goes beside it. */\nexport class HttpResponseStatus {\n constructor(\n public readonly code: number,\n public readonly reason: string,\n ) {}\n}\n\n/**\n * The whole response: status (code + reason), the header LIST, and the body.\n *\n * `body` is `unknown` because it is whatever the app chose to publish. webpieces' OWN default puts a\n * {@link ApiErrorPayload} there, and the built-in client mapping reads it back as one — but an app that\n * owns the whole response owns the body shape too, so the framework does not constrain it.\n */\n// webpieces-disable no-any-unknown -- the app owns the full-response body shape\nexport class HttpResponseDto<TBody = unknown> {\n constructor(\n public readonly status: HttpResponseStatus,\n public readonly headers: readonly HttpHeader[],\n // webpieces-disable no-any-unknown -- the app owns the body shape when it owns the response; webpieces' own default puts a ApiErrorPayload here, an app puts whatever it publishes\n public readonly body: TBody,\n ) {}\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { MaskSpec } from './LogFieldMask';
2
2
  import { AuthMeta } from './auth-mode';
3
+ import { EndpointResponseType, HttpParameterBinding } from './HttpContract';
3
4
  /**
4
5
  * Route metadata stored per-method at runtime.
5
6
  * Used internally by http-routing and http-client as the runtime representation
@@ -10,6 +11,12 @@ import { AuthMeta } from './auth-mode';
10
11
  * `external-caller.ts` were split off `decorators.ts` before it. Nothing about its role changed.
11
12
  */
12
13
  export declare class RouteMetadata {
14
+ /** Explicit path/query mappings, in API declaration order. */
15
+ readonly parameterBindings: readonly HttpParameterBinding[];
16
+ /** The sole unannotated POST parameter, or undefined for a bodyless route. */
17
+ readonly bodyParameterIndex?: number | undefined;
18
+ /** Whether clients receive only the body or the complete status/header/body response. */
19
+ readonly responseType: EndpointResponseType;
13
20
  httpMethod: string;
14
21
  path: string;
15
22
  methodName: string;
@@ -36,5 +43,11 @@ export declare class RouteMetadata {
36
43
  * the body from the ROUTE, without knowing the apiClass/methodName.
37
44
  */
38
45
  readonly rawBody: boolean;
39
- constructor(httpMethod: string, path: string, methodName: string, controllerClassName?: string, authMeta?: AuthMeta, apiName?: string, formPost?: boolean, mask?: MaskSpec, rawBody?: boolean);
46
+ constructor(httpMethod: string, path: string, methodName: string, controllerClassName?: string, authMeta?: AuthMeta, apiName?: string, formPost?: boolean, mask?: MaskSpec, rawBody?: boolean,
47
+ /** Explicit path/query mappings, in API declaration order. */
48
+ parameterBindings?: readonly HttpParameterBinding[],
49
+ /** The sole unannotated POST parameter, or undefined for a bodyless route. */
50
+ bodyParameterIndex?: number | undefined,
51
+ /** Whether clients receive only the body or the complete status/header/body response. */
52
+ responseType?: EndpointResponseType);
40
53
  }
@@ -11,6 +11,9 @@ exports.RouteMetadata = void 0;
11
11
  * `external-caller.ts` were split off `decorators.ts` before it. Nothing about its role changed.
12
12
  */
13
13
  class RouteMetadata {
14
+ parameterBindings;
15
+ bodyParameterIndex;
16
+ responseType;
14
17
  httpMethod;
15
18
  path;
16
19
  methodName;
@@ -37,7 +40,16 @@ class RouteMetadata {
37
40
  * the body from the ROUTE, without knowing the apiClass/methodName.
38
41
  */
39
42
  rawBody;
40
- constructor(httpMethod, path, methodName, controllerClassName, authMeta, apiName, formPost = false, mask, rawBody = false) {
43
+ constructor(httpMethod, path, methodName, controllerClassName, authMeta, apiName, formPost = false, mask, rawBody = false,
44
+ /** Explicit path/query mappings, in API declaration order. */
45
+ parameterBindings = [],
46
+ /** The sole unannotated POST parameter, or undefined for a bodyless route. */
47
+ bodyParameterIndex,
48
+ /** Whether clients receive only the body or the complete status/header/body response. */
49
+ responseType = 'body') {
50
+ this.parameterBindings = parameterBindings;
51
+ this.bodyParameterIndex = bodyParameterIndex;
52
+ this.responseType = responseType;
41
53
  this.httpMethod = httpMethod;
42
54
  this.path = path;
43
55
  this.methodName = methodName;
@@ -47,6 +59,9 @@ class RouteMetadata {
47
59
  this.formPost = formPost;
48
60
  this.mask = mask;
49
61
  this.rawBody = rawBody;
62
+ this.parameterBindings = parameterBindings;
63
+ this.bodyParameterIndex = bodyParameterIndex;
64
+ this.responseType = responseType;
50
65
  }
51
66
  }
52
67
  exports.RouteMetadata = RouteMetadata;
@@ -1 +1 @@
1
- {"version":3,"file":"RouteMetadata.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/RouteMetadata.ts"],"names":[],"mappings":";;;AAGA;;;;;;;;GAQG;AACH,MAAa,aAAa;IACtB,UAAU,CAAS;IACnB,IAAI,CAAS;IACb,UAAU,CAAS;IACnB,mBAAmB,CAAU;IAC7B,QAAQ,CAAY;IACpB,wFAAwF;IACxF,OAAO,CAAU;IACjB;;;;OAIG;IACM,QAAQ,CAAU;IAC3B;;;;OAIG;IACM,IAAI,CAAY;IACzB;;;;;OAKG;IACM,OAAO,CAAU;IAE1B,YACI,UAAkB,EAClB,IAAY,EACZ,UAAkB,EAClB,mBAA4B,EAC5B,QAAmB,EACnB,OAAgB,EAChB,WAAoB,KAAK,EACzB,IAAe,EACf,UAAmB,KAAK;QAExB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,mBAAmB,GAAG,mBAAmB,CAAC;QAC/C,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAC3B,CAAC;CACJ;AAjDD,sCAiDC","sourcesContent":["import { MaskSpec } from './LogFieldMask';\nimport { AuthMeta } from './auth-mode';\n\n/**\n * Route metadata stored per-method at runtime.\n * Used internally by http-routing and http-client as the runtime representation\n * of a route. Constructed from @ApiPath + @Endpoint metadata by ProxyClient\n * and ApiRoutingFactory.\n *\n * Lives in its own file (one class per file) purely for file size, exactly as `api-kind.ts` and\n * `external-caller.ts` were split off `decorators.ts` before it. Nothing about its role changed.\n */\nexport class RouteMetadata {\n httpMethod: string;\n path: string;\n methodName: string;\n controllerClassName?: string;\n authMeta?: AuthMeta;\n /** The API contract class name (e.g. 'SaveApi') — distinct from the controller name. */\n apiName?: string;\n /**\n * True when @Endpoint(..., { formPost: true }): the body is application/x-www-form-urlencoded\n * (flat key→value), not JSON. Rides the route metadata so the per-route body parse can branch\n * without knowing the apiClass/methodName. Default false = JSON.\n */\n readonly formPost: boolean;\n /**\n * The @MaskLog field-mask spec for this route, or undefined when the method declared none. Read\n * ONCE here at route-build time and handed to {@link LogApiCallImpl} via ApiMethodInfo, so the per-call\n * log path pays for masking only on routes that opted in (the rest stay on plain JSON.stringify).\n */\n readonly mask?: MaskSpec;\n /**\n * True when @Endpoint(..., { rawBody: true }): the transport must retain the verbatim bytes +\n * absolute url for the `@WpAuthWebhook` hook to verify a vendor signature over. Rides the route\n * metadata for the same reason {@link formPost} does — the transport adapter decides how to read\n * the body from the ROUTE, without knowing the apiClass/methodName.\n */\n readonly rawBody: boolean;\n\n constructor(\n httpMethod: string,\n path: string,\n methodName: string,\n controllerClassName?: string,\n authMeta?: AuthMeta,\n apiName?: string,\n formPost: boolean = false,\n mask?: MaskSpec,\n rawBody: boolean = false,\n ) {\n this.httpMethod = httpMethod;\n this.path = path;\n this.methodName = methodName;\n this.controllerClassName = controllerClassName;\n this.authMeta = authMeta;\n this.apiName = apiName;\n this.formPost = formPost;\n this.mask = mask;\n this.rawBody = rawBody;\n }\n}\n"]}
1
+ {"version":3,"file":"RouteMetadata.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/RouteMetadata.ts"],"names":[],"mappings":";;;AAIA;;;;;;;;GAQG;AACH,MAAa,aAAa;IAuCT;IAEA;IAEA;IA1Cb,UAAU,CAAS;IACnB,IAAI,CAAS;IACb,UAAU,CAAS;IACnB,mBAAmB,CAAU;IAC7B,QAAQ,CAAY;IACpB,wFAAwF;IACxF,OAAO,CAAU;IACjB;;;;OAIG;IACM,QAAQ,CAAU;IAC3B;;;;OAIG;IACM,IAAI,CAAY;IACzB;;;;;OAKG;IACM,OAAO,CAAU;IAE1B,YACI,UAAkB,EAClB,IAAY,EACZ,UAAkB,EAClB,mBAA4B,EAC5B,QAAmB,EACnB,OAAgB,EAChB,WAAoB,KAAK,EACzB,IAAe,EACf,UAAmB,KAAK;IACxB,8DAA8D;IACrD,oBAAqD,EAAE;IAChE,8EAA8E;IACrE,kBAA2B;IACpC,yFAAyF;IAChF,eAAqC,MAAM;QAJ3C,sBAAiB,GAAjB,iBAAiB,CAAsC;QAEvD,uBAAkB,GAAlB,kBAAkB,CAAS;QAE3B,iBAAY,GAAZ,YAAY,CAA+B;QAEpD,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,mBAAmB,GAAG,mBAAmB,CAAC;QAC/C,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,iBAAiB,GAAG,iBAAiB,CAAC;QAC3C,IAAI,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;QAC7C,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;IACrC,CAAC;CACJ;AA1DD,sCA0DC","sourcesContent":["import { MaskSpec } from './LogFieldMask';\nimport { AuthMeta } from './auth-mode';\nimport { EndpointResponseType, HttpParameterBinding } from './HttpContract';\n\n/**\n * Route metadata stored per-method at runtime.\n * Used internally by http-routing and http-client as the runtime representation\n * of a route. Constructed from @ApiPath + @Endpoint metadata by ProxyClient\n * and ApiRoutingFactory.\n *\n * Lives in its own file (one class per file) purely for file size, exactly as `api-kind.ts` and\n * `external-caller.ts` were split off `decorators.ts` before it. Nothing about its role changed.\n */\nexport class RouteMetadata {\n httpMethod: string;\n path: string;\n methodName: string;\n controllerClassName?: string;\n authMeta?: AuthMeta;\n /** The API contract class name (e.g. 'SaveApi') — distinct from the controller name. */\n apiName?: string;\n /**\n * True when @Endpoint(..., { formPost: true }): the body is application/x-www-form-urlencoded\n * (flat key→value), not JSON. Rides the route metadata so the per-route body parse can branch\n * without knowing the apiClass/methodName. Default false = JSON.\n */\n readonly formPost: boolean;\n /**\n * The @MaskLog field-mask spec for this route, or undefined when the method declared none. Read\n * ONCE here at route-build time and handed to {@link LogApiCallImpl} via ApiMethodInfo, so the per-call\n * log path pays for masking only on routes that opted in (the rest stay on plain JSON.stringify).\n */\n readonly mask?: MaskSpec;\n /**\n * True when @Endpoint(..., { rawBody: true }): the transport must retain the verbatim bytes +\n * absolute url for the `@WpAuthWebhook` hook to verify a vendor signature over. Rides the route\n * metadata for the same reason {@link formPost} does — the transport adapter decides how to read\n * the body from the ROUTE, without knowing the apiClass/methodName.\n */\n readonly rawBody: boolean;\n\n constructor(\n httpMethod: string,\n path: string,\n methodName: string,\n controllerClassName?: string,\n authMeta?: AuthMeta,\n apiName?: string,\n formPost: boolean = false,\n mask?: MaskSpec,\n rawBody: boolean = false,\n /** Explicit path/query mappings, in API declaration order. */\n readonly parameterBindings: readonly HttpParameterBinding[] = [],\n /** The sole unannotated POST parameter, or undefined for a bodyless route. */\n readonly bodyParameterIndex?: number,\n /** Whether clients receive only the body or the complete status/header/body response. */\n readonly responseType: EndpointResponseType = 'body',\n ) {\n this.httpMethod = httpMethod;\n this.path = path;\n this.methodName = methodName;\n this.controllerClassName = controllerClassName;\n this.authMeta = authMeta;\n this.apiName = apiName;\n this.formPost = formPost;\n this.mask = mask;\n this.rawBody = rawBody;\n this.parameterBindings = parameterBindings;\n this.bodyParameterIndex = bodyParameterIndex;\n this.responseType = responseType;\n }\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import { RouteMetadata } from './RouteMetadata';
2
+ /**
3
+ * Build the one runtime route model consumed by incoming routing, in-process clients, and both
4
+ * generated HTTP clients. Keeping this join/validation here prevents four subtly different
5
+ * interpretations of the same decorators.
6
+ */
7
+ export declare class RouteMetadataFactory {
8
+ static create(apiClass: Function, methodName: string, controllerClassName?: string): RouteMetadata;
9
+ private static joinPath;
10
+ private static httpMethod;
11
+ private static responseType;
12
+ private static parameterTypes;
13
+ private static validateParameters;
14
+ private static valueType;
15
+ }
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RouteMetadataFactory = void 0;
4
+ const decorators_1 = require("./decorators");
5
+ const http_parameter_decorators_1 = require("./http-parameter-decorators");
6
+ const HttpContract_1 = require("./HttpContract");
7
+ const RouteMetadata_1 = require("./RouteMetadata");
8
+ /**
9
+ * Build the one runtime route model consumed by incoming routing, in-process clients, and both
10
+ * generated HTTP clients. Keeping this join/validation here prevents four subtly different
11
+ * interpretations of the same decorators.
12
+ */
13
+ class RouteMetadataFactory {
14
+ // webpieces-disable no-function-outside-class -- deterministic decorator metadata factory shared by DI-free browser and server runtimes
15
+ static create(apiClass, methodName, controllerClassName) {
16
+ const endpoints = (0, decorators_1.getEndpoints)(apiClass) ?? {};
17
+ const endpointPath = endpoints[methodName];
18
+ if (endpointPath === undefined) {
19
+ throw new Error(`No @Endpoint metadata for ${apiClass.name || 'Unknown'}.${methodName}.`);
20
+ }
21
+ const fullPath = this.joinPath((0, decorators_1.getApiPath)(apiClass) ?? '', endpointPath);
22
+ const options = (0, decorators_1.getEndpointOptions)(apiClass, methodName);
23
+ const httpMethod = this.httpMethod(options.httpMethod, apiClass, methodName);
24
+ if (httpMethod === 'GET' && options.formPost === true) {
25
+ throw new Error(`${apiClass.name}.${methodName} is GET and cannot declare formPost:true because GET has no request body.`);
26
+ }
27
+ if (httpMethod === 'GET' && options.rawBody === true) {
28
+ throw new Error(`${apiClass.name}.${methodName} is GET and cannot declare rawBody:true because GET has no request body.`);
29
+ }
30
+ const parameterTypes = this.parameterTypes(apiClass, methodName);
31
+ const declarations = (0, http_parameter_decorators_1.getHttpParameterDeclarations)(apiClass, methodName);
32
+ const bodyParameterIndex = this.validateParameters(apiClass, methodName, fullPath, httpMethod, parameterTypes, declarations);
33
+ const bindings = declarations
34
+ .map((declaration) => new HttpContract_1.HttpParameterBinding(declaration.index, declaration.source, declaration.wireName, this.valueType(parameterTypes[declaration.index])))
35
+ .sort((a, b) => a.index - b.index);
36
+ return new RouteMetadata_1.RouteMetadata(httpMethod, fullPath, methodName, 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));
37
+ }
38
+ // webpieces-disable no-function-outside-class -- private pure helper for the static metadata factory
39
+ static joinPath(basePath, endpointPath) {
40
+ const joined = `${basePath}/${endpointPath}`.replace(/\/{2,}/g, '/');
41
+ return joined.startsWith('/') ? joined : `/${joined}`;
42
+ }
43
+ // webpieces-disable no-function-outside-class -- private pure helper for the static metadata factory
44
+ static httpMethod(value, apiClass, methodName) {
45
+ const method = value ?? 'POST';
46
+ if (method === 'GET' || method === 'POST')
47
+ return method;
48
+ throw new Error(`${apiClass.name}.${methodName} declares unsupported httpMethod '${String(method)}'; use GET or POST.`);
49
+ }
50
+ // webpieces-disable no-function-outside-class -- private pure helper for the static metadata factory
51
+ static responseType(value, apiClass, methodName) {
52
+ const responseType = value ?? 'body';
53
+ if (responseType === 'body' || responseType === 'full')
54
+ return responseType;
55
+ throw new Error(`${apiClass.name}.${methodName} declares unsupported responseType '${String(responseType)}'; use body or full.`);
56
+ }
57
+ // webpieces-disable no-function-outside-class -- Reflect metadata lookup has no injectable dependency
58
+ static parameterTypes(apiClass, methodName) {
59
+ return Reflect.getMetadata('design:paramtypes', apiClass.prototype, methodName) ?? [];
60
+ }
61
+ // webpieces-disable no-function-outside-class -- private deterministic contract validation helper
62
+ static validateParameters(apiClass, methodName, fullPath, httpMethod, parameterTypes, declarations) {
63
+ const label = `${apiClass.name || 'Unknown'}.${methodName}`;
64
+ const placeholders = [...fullPath.matchAll(/\{([^{}]+)\}/g)].map((match) => match[1]);
65
+ const pathNames = declarations
66
+ .filter((declaration) => declaration.source === 'path')
67
+ .map((declaration) => declaration.wireName);
68
+ for (const placeholder of new Set(placeholders)) {
69
+ if (!pathNames.includes(placeholder)) {
70
+ throw new Error(`${label} path '${fullPath}' contains {${placeholder}} but no @PathParam('${placeholder}') parameter.`);
71
+ }
72
+ }
73
+ for (const pathName of pathNames) {
74
+ if (!placeholders.includes(pathName)) {
75
+ throw new Error(`${label} declares @PathParam('${pathName}') but '${fullPath}' has no {${pathName}} placeholder.`);
76
+ }
77
+ }
78
+ const seenWire = new Set();
79
+ const mappedIndexes = new Set();
80
+ for (const declaration of declarations) {
81
+ const wireKey = `${declaration.source}:${declaration.wireName}`;
82
+ if (seenWire.has(wireKey)) {
83
+ throw new Error(`${label} maps '${wireKey}' more than once.`);
84
+ }
85
+ seenWire.add(wireKey);
86
+ mappedIndexes.add(declaration.index);
87
+ }
88
+ const declaredCount = Math.max(parameterTypes.length, declarations.reduce((highest, declaration) => Math.max(highest, declaration.index + 1), 0));
89
+ const unbound = [];
90
+ for (let index = 0; index < declaredCount; index++) {
91
+ if (!mappedIndexes.has(index))
92
+ unbound.push(index);
93
+ }
94
+ if (httpMethod === 'GET' && unbound.length > 0) {
95
+ throw new Error(`${label} is GET, so every parameter must use @PathParam(...) or @QueryParam(...); argument ${unbound[0]} is unmapped.`);
96
+ }
97
+ if (unbound.length > 1) {
98
+ throw new Error(`${label} has ${unbound.length} unmapped parameters. POST permits exactly one unannotated request body; map every other parameter explicitly.`);
99
+ }
100
+ return unbound[0];
101
+ }
102
+ // webpieces-disable no-function-outside-class -- private conversion of reflected constructors to wire types
103
+ static valueType(type) {
104
+ if (type === String)
105
+ return 'string';
106
+ if (type === Number)
107
+ return 'number';
108
+ if (type === Boolean)
109
+ return 'boolean';
110
+ if (type === Array)
111
+ return 'array';
112
+ return 'unknown';
113
+ }
114
+ }
115
+ exports.RouteMetadataFactory = RouteMetadataFactory;
116
+ //# sourceMappingURL=RouteMetadataFactory.js.map
@@ -0,0 +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 getEndpointOptions,\n getEndpoints,\n getMaskSpec,\n} from './decorators';\nimport { getHttpParameterDeclarations } from './http-parameter-decorators';\nimport {\n ContractHttpMethod,\n EndpointResponseType,\n HttpParameterBinding,\n HttpParameterDeclaration,\n HttpParameterValueType,\n} from './HttpContract';\nimport { RouteMetadata } from './RouteMetadata';\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 endpoints = getEndpoints(apiClass) ?? {};\n const endpointPath = endpoints[methodName];\n if (endpointPath === undefined) {\n throw new Error(\n `No @Endpoint metadata for ${apiClass.name || 'Unknown'}.${methodName}.`,\n );\n }\n const fullPath = this.joinPath(getApiPath(apiClass) ?? '', endpointPath);\n const options = getEndpointOptions(apiClass, methodName);\n const httpMethod = this.httpMethod(options.httpMethod, apiClass, methodName);\n if (httpMethod === 'GET' && options.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' && options.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 const parameterTypes = this.parameterTypes(apiClass, methodName);\n const declarations = getHttpParameterDeclarations(apiClass, methodName);\n const bodyParameterIndex = 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 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 );\n }\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 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 httpMethod(\n value: ContractHttpMethod | undefined,\n apiClass: Function,\n methodName: string,\n ): ContractHttpMethod {\n const method = value ?? 'POST';\n if (method === 'GET' || method === 'POST') return method;\n throw new Error(\n `${apiClass.name}.${methodName} declares unsupported httpMethod '${String(method)}'; use GET or POST.`,\n );\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: ContractHttpMethod,\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,7 +1,9 @@
1
1
  import 'reflect-metadata';
2
2
  import { MaskSpec, MaskMode } from './LogFieldMask';
3
- import { ExternalSystemKind } from './external-caller';
4
3
  import { ApiKeyCredentials, AuthMeta, AuthMode, JwtRequirement } from './auth-mode';
4
+ import { EndpointOptions, ExternalEndpointOptions } from './HttpEndpointOptions';
5
+ export type { EndpointOptions, ExternalEndpointOptions } from './HttpEndpointOptions';
6
+ export { PathParam, QueryParam, getHttpParameterDeclarations } from './http-parameter-decorators';
5
7
  /**
6
8
  * Metadata keys for storing API routing information.
7
9
  * These keys are used by both server-side (routing) and client-side (client generation).
@@ -26,6 +28,8 @@ export declare const METADATA_KEYS: {
26
28
  MASK_LOG: string;
27
29
  /** Per-method opt-in metadata for publishing an RPC endpoint as an MCP tool. */
28
30
  MCP_TOOLS: string;
31
+ /** Per-method explicit path/query parameter declarations, keyed by parameter index. */
32
+ HTTP_PARAMETERS: string;
29
33
  };
30
34
  /**
31
35
  * WHAT TRIGGERS an endpoint at runtime — the single fact that decides how the runtime architecture
@@ -49,41 +53,6 @@ export type EndpointKind = 'rpc' | 'cloudtasks' | 'cron' | 'external';
49
53
  * Options for a single @Endpoint. Kept in a metadata map PARALLEL to ENDPOINTS so the existing
50
54
  * `Record<methodName, path>` shape every consumer iterates stays unchanged.
51
55
  */
52
- export interface EndpointOptions {
53
- /**
54
- * Parse the request body as application/x-www-form-urlencoded (flat key→value) instead of JSON.
55
- * For EXTERNAL webhooks (e.g. Twilio) that post form-encoded. The request DTO must be FLAT —
56
- * urlencoded has no nesting (unlike JSON). Default false = JSON.
57
- */
58
- formPost?: boolean;
59
- /**
60
- * RETAIN the verbatim request bytes + the absolute url the sender addressed, so an
61
- * {@link WpAuthWebhook} hook can verify a vendor signature over them (see `RawRequest`).
62
- *
63
- * Opt-in PER ENDPOINT, sitting beside `formPost` and for the same reason: the cost lands on the
64
- * handful of webhook routes rather than on every request in the process. It is retention, not
65
- * new buffering — the express adapter already accumulates the whole body, it simply threw it
66
- * away once it had parsed a DTO.
67
- *
68
- * REQUIRED by `@WpAuthWebhook`, checked at wiring time (see
69
- * {@link assertEveryWebhookEndpointRetainsRawBody}) rather than left to fail as a 401 in
70
- * production: a hook with nothing to verify is a misconfiguration, not a bad request.
71
- *
72
- * Combines with `formPost` — `{ formPost: true, rawBody: true }` is the Twilio case, where the
73
- * hook needs the bytes and the url while the controller still wants the flat parsed DTO.
74
- */
75
- rawBody?: boolean;
76
- }
77
- /**
78
- * Options for an `external` @Endpoint: everything {@link EndpointOptions} carries, PLUS a REQUIRED
79
- * declaration of WHO is calling. See {@link Endpoint} for why, `external-caller.ts` for identity.
80
- */
81
- export interface ExternalEndpointOptions extends EndpointOptions {
82
- /** The outside system that posts here (`'twilio'`) — the graph node IDENTITY, not display text. */
83
- calledBy: string;
84
- /** What that caller IS; picks the node's shape. Defaults to `'saas'` (see DEFAULT_CALLER_KIND). */
85
- callerKind?: ExternalSystemKind;
86
- }
87
56
  /**
88
57
  * @ApiPath(basePath) - Class decorator that marks a class as an API definition
89
58
  * and sets the base path for all endpoints.
@@ -100,10 +69,8 @@ export interface ExternalEndpointOptions extends EndpointOptions {
100
69
  */
101
70
  export declare function ApiPath(basePath: string): ClassDecorator;
102
71
  /**
103
- * @Endpoint(path, kind, options?) - Method decorator that registers a POST endpoint at the given
104
- * path and declares WHAT TRIGGERS it.
105
- *
106
- * All endpoints are POST-only (matching gRPC/thrift style).
72
+ * @Endpoint(path, kind, options?) - Method decorator that registers an HTTP endpoint at the given
73
+ * path and declares WHAT TRIGGERS it. POST is the default; GET is explicit in `httpMethod`.
107
74
  *
108
75
  * Usage:
109
76
  * ```typescript
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MISSING_AUTH_DECORATOR_FIX = exports.METADATA_KEYS = void 0;
3
+ exports.MISSING_AUTH_DECORATOR_FIX = exports.METADATA_KEYS = exports.getHttpParameterDeclarations = exports.QueryParam = exports.PathParam = void 0;
4
4
  exports.ApiPath = ApiPath;
5
5
  exports.Endpoint = Endpoint;
6
6
  exports.MaskLog = MaskLog;
@@ -32,6 +32,11 @@ const LogFieldMask_1 = require("./LogFieldMask");
32
32
  const external_caller_1 = require("./external-caller");
33
33
  // The TYPE layer these decorators attach — split out for file size only (see auth-mode.ts).
34
34
  const auth_mode_1 = require("./auth-mode");
35
+ const http_parameter_decorators_1 = require("./http-parameter-decorators");
36
+ var http_parameter_decorators_2 = require("./http-parameter-decorators");
37
+ Object.defineProperty(exports, "PathParam", { enumerable: true, get: function () { return http_parameter_decorators_2.PathParam; } });
38
+ Object.defineProperty(exports, "QueryParam", { enumerable: true, get: function () { return http_parameter_decorators_2.QueryParam; } });
39
+ Object.defineProperty(exports, "getHttpParameterDeclarations", { enumerable: true, get: function () { return http_parameter_decorators_2.getHttpParameterDeclarations; } });
35
40
  /**
36
41
  * Metadata keys for storing API routing information.
37
42
  * These keys are used by both server-side (routing) and client-side (client generation).
@@ -56,7 +61,13 @@ exports.METADATA_KEYS = {
56
61
  MASK_LOG: 'webpieces:mask-log',
57
62
  /** Per-method opt-in metadata for publishing an RPC endpoint as an MCP tool. */
58
63
  MCP_TOOLS: 'webpieces:mcp-tools',
64
+ /** Per-method explicit path/query parameter declarations, keyed by parameter index. */
65
+ HTTP_PARAMETERS: http_parameter_decorators_1.HTTP_PARAMETERS_METADATA_KEY,
59
66
  };
67
+ /**
68
+ * Options for a single @Endpoint. Kept in a metadata map PARALLEL to ENDPOINTS so the existing
69
+ * `Record<methodName, path>` shape every consumer iterates stays unchanged.
70
+ */
60
71
  /**
61
72
  * @ApiPath(basePath) - Class decorator that marks a class as an API definition
62
73
  * and sets the base path for all endpoints.
@@ -1 +1 @@
1
- {"version":3,"file":"decorators.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/decorators.ts"],"names":[],"mappings":"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","sourcesContent":["import 'reflect-metadata';\nimport { MaskSpec, MaskMode } from './LogFieldMask';\nimport {\n DEFAULT_CALLER_KIND,\n ENDPOINT_CALLER_KEY,\n ExternalCaller,\n ExternalSystemKind,\n getEndpointCaller,\n} from './external-caller';\n// The TYPE layer these decorators attach — split out for file size only (see auth-mode.ts).\nimport { ApiKeyCredentials, AuthMeta, AuthMode, JwtRequirement } from './auth-mode';\n\n/**\n * Metadata keys for storing API routing information.\n * These keys are used by both server-side (routing) and client-side (client generation).\n */\nexport const METADATA_KEYS = {\n API_PATH: 'webpieces:api-path',\n ENDPOINTS: 'webpieces:endpoints',\n AUTH_META: 'webpieces:auth-meta',\n /** Method names carrying one of the canonical authorization decorators. */\n AUTH_METHODS: 'webpieces:auth-methods',\n /** 'rpc' (default, sync request/response) vs 'pubsub' (fire-and-forget cloud task). */\n API_KIND: 'webpieces:api-kind',\n /** Per-method Cloud Tasks queue-name override (set via @Queue). */\n QUEUE_OVERRIDE: 'webpieces:queue-override',\n /** Per-method @Endpoint options (e.g. formPost), parallel to ENDPOINTS. */\n ENDPOINT_OPTIONS: 'webpieces:endpoint-options',\n /** Per-method @Endpoint trigger kind (rpc | cloudtasks | cron | external), parallel to ENDPOINTS. */\n ENDPOINT_KIND: 'webpieces:endpoint-kind',\n /** Per-method declared external CALLER (only for kind 'external'), parallel to ENDPOINTS. */\n ENDPOINT_CALLER: ENDPOINT_CALLER_KEY,\n /** Per-method @MaskLog spec (which DTO fields the LogApiCall path masks). */\n MASK_LOG: 'webpieces:mask-log',\n /** Per-method opt-in metadata for publishing an RPC endpoint as an MCP tool. */\n MCP_TOOLS: 'webpieces:mcp-tools',\n};\n\n/**\n * WHAT TRIGGERS an endpoint at runtime — the single fact that decides how the runtime architecture\n * graph draws it, and which Terraform resource must exist for it to ever fire:\n *\n * - `rpc` — a caller in this repo (or a browser) calls it synchronously. A direct arrow.\n * - `cloudtasks` — a producer ENQUEUES it; Cloud Tasks delivers it later. Drawn producer → queue →\n * consumer, one queue node per METHOD (see {@link Queue}). Producer and consumer\n * being the SAME service is legal and common — the queue decouples them.\n * - `cron` — a scheduler fires it on a clock. Nothing in-repo calls it; drawn hanging off a\n * clock symbol. Backed by a Cloud Scheduler job.\n * - `external` — a system OUTSIDE this repo drives it (a GCP Pub/Sub push subscription, a Twilio\n * or Gmail webhook). Drawn as an inbound dashed arrow from that system.\n *\n * Declared PER METHOD, because one api class routinely mixes them: an admin contract can have\n * caller-driven endpoints AND a nightly cron sweep. A class-level marker cannot express that, which\n * is exactly why the graph could not tell these apart before.\n */\nexport type EndpointKind = 'rpc' | 'cloudtasks' | 'cron' | 'external';\n\n/**\n * Options for a single @Endpoint. Kept in a metadata map PARALLEL to ENDPOINTS so the existing\n * `Record<methodName, path>` shape every consumer iterates stays unchanged.\n */\nexport interface EndpointOptions {\n /**\n * Parse the request body as application/x-www-form-urlencoded (flat key→value) instead of JSON.\n * For EXTERNAL webhooks (e.g. Twilio) that post form-encoded. The request DTO must be FLAT —\n * urlencoded has no nesting (unlike JSON). Default false = JSON.\n */\n formPost?: boolean;\n\n /**\n * RETAIN the verbatim request bytes + the absolute url the sender addressed, so an\n * {@link WpAuthWebhook} hook can verify a vendor signature over them (see `RawRequest`).\n *\n * Opt-in PER ENDPOINT, sitting beside `formPost` and for the same reason: the cost lands on the\n * handful of webhook routes rather than on every request in the process. It is retention, not\n * new buffering — the express adapter already accumulates the whole body, it simply threw it\n * away once it had parsed a DTO.\n *\n * REQUIRED by `@WpAuthWebhook`, checked at wiring time (see\n * {@link assertEveryWebhookEndpointRetainsRawBody}) rather than left to fail as a 401 in\n * production: a hook with nothing to verify is a misconfiguration, not a bad request.\n *\n * Combines with `formPost` — `{ formPost: true, rawBody: true }` is the Twilio case, where the\n * hook needs the bytes and the url while the controller still wants the flat parsed DTO.\n */\n rawBody?: boolean;\n}\n\n/**\n * Options for an `external` @Endpoint: everything {@link EndpointOptions} carries, PLUS a REQUIRED\n * declaration of WHO is calling. See {@link Endpoint} for why, `external-caller.ts` for identity.\n */\nexport interface ExternalEndpointOptions extends EndpointOptions {\n /** The outside system that posts here (`'twilio'`) — the graph node IDENTITY, not display text. */\n calledBy: string;\n /** What that caller IS; picks the node's shape. Defaults to `'saas'` (see DEFAULT_CALLER_KIND). */\n callerKind?: ExternalSystemKind;\n}\n\n/**\n * @ApiPath(basePath) - Class decorator that marks a class as an API definition\n * and sets the base path for all endpoints.\n *\n * Usage:\n * ```typescript\n * @ApiPath('/api/save')\n * abstract class SaveApi {\n * @WpAuthJwt({ roles: ['admin'] })\n * @Endpoint('/item', 'rpc')\n * save(request: SaveRequest): Promise<SaveResponse> { ... }\n * }\n * ```\n */\nexport function ApiPath(basePath: string): ClassDecorator {\n return (target: Function) => {\n if (Reflect.hasMetadata('webpieces:ipc-api-id', target)) {\n throw new Error(\n `Class ${target.name || 'Unknown'} cannot combine @ApiPath with @WpInternal.`,\n );\n }\n Reflect.defineMetadata(METADATA_KEYS.API_PATH, basePath, target);\n\n // Initialize endpoints map if not exists\n if (!Reflect.hasMetadata(METADATA_KEYS.ENDPOINTS, target)) {\n Reflect.defineMetadata(METADATA_KEYS.ENDPOINTS, {}, target);\n }\n };\n}\n\n/**\n * @Endpoint(path, kind, options?) - Method decorator that registers a POST endpoint at the given\n * path and declares WHAT TRIGGERS it.\n *\n * All endpoints are POST-only (matching gRPC/thrift style).\n *\n * Usage:\n * ```typescript\n * @Endpoint('/item', 'rpc')\n * save(request: SaveRequest): Promise<SaveResponse> { ... }\n *\n * // enqueued by a producer, delivered later by Cloud Tasks:\n * @Endpoint('/send', 'cloudtasks')\n * send(request: SendRequest): Promise<void> { ... }\n *\n * // fired by Cloud Scheduler on a clock, called by nobody in this repo:\n * @Endpoint('/nightly', 'cron')\n * nightly(request: NightlyRequest): Promise<void> { ... }\n *\n * // EXTERNAL webhook posting application/x-www-form-urlencoded (e.g. Twilio):\n * @Endpoint('/hook', 'external', { formPost: true, calledBy: 'twilio' })\n * inbound(request: InboundRequest): Promise<InboundResponse> { ... }\n * ```\n *\n * `kind` is REQUIRED and deliberately positional: it makes every pre-existing single-argument\n * `@Endpoint('/x')` a COMPILE error rather than something a lint rule has to chase, so no endpoint\n * can slip into the runtime architecture graph with its trigger left to guesswork. See\n * {@link EndpointKind} for what each value draws and which Terraform resource backs it.\n *\n * `calledBy` is REQUIRED for `external` FOR EXACTLY THE SAME REASON, enforced by the overloads below:\n * the one box on the runtime graph whose whole job is to say who calls us from outside could only\n * restate OUR OWN contract name, because nothing in the source ever said who the caller was. This is\n * BREAKING for published consumers, intentionally — an existing `@Endpoint(p, 'external', {...})`\n * stops compiling until it names its caller. Migration is one property; see the migration note in\n * `external-caller.ts`. Non-`external` endpoints are completely unaffected.\n *\n * The path write to ENDPOINTS is UNCHANGED (every consumer iterates `[methodName, path]`); kind,\n * options and caller ride PARALLEL ENDPOINT_KIND / ENDPOINT_OPTIONS / ENDPOINT_CALLER maps.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function Endpoint(\n path: string,\n kind: 'external',\n options: ExternalEndpointOptions,\n): MethodDecorator;\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function Endpoint(\n path: string,\n kind: Exclude<EndpointKind, 'external'>,\n options?: EndpointOptions,\n): MethodDecorator;\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function Endpoint(\n path: string,\n kind: EndpointKind,\n options: EndpointOptions = {},\n): 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\n const endpoints: Record<string, string> =\n Reflect.getMetadata(METADATA_KEYS.ENDPOINTS, metadataTarget) || {};\n\n endpoints[propertyKey as string] = path;\n\n Reflect.defineMetadata(METADATA_KEYS.ENDPOINTS, endpoints, metadataTarget);\n\n const kinds: Record<string, EndpointKind> =\n Reflect.getMetadata(METADATA_KEYS.ENDPOINT_KIND, metadataTarget) || {};\n kinds[propertyKey as string] = kind;\n Reflect.defineMetadata(METADATA_KEYS.ENDPOINT_KIND, kinds, metadataTarget);\n\n const opts: Record<string, EndpointOptions> =\n Reflect.getMetadata(METADATA_KEYS.ENDPOINT_OPTIONS, metadataTarget) || {};\n opts[propertyKey as string] = options;\n Reflect.defineMetadata(METADATA_KEYS.ENDPOINT_OPTIONS, opts, metadataTarget);\n\n // ONLY for 'external', mirroring how a queue name is recorded only for the kinds that HAVE\n // a queue: a caller on an rpc endpoint would be a fact about nothing.\n const declared = options as ExternalEndpointOptions;\n if (\n kind !== 'external' ||\n typeof declared.calledBy !== 'string' ||\n declared.calledBy === ''\n )\n return;\n const callers: Record<string, ExternalCaller> =\n Reflect.getMetadata(METADATA_KEYS.ENDPOINT_CALLER, metadataTarget) || {};\n callers[propertyKey as string] = new ExternalCaller(\n declared.callerKind ?? DEFAULT_CALLER_KIND,\n declared.calledBy,\n );\n Reflect.defineMetadata(METADATA_KEYS.ENDPOINT_CALLER, callers, metadataTarget);\n };\n}\n\n/**\n * @MaskLog(fields) - declare which fields of THIS method's request/response DTOs the\n * {@link LogApiCallImpl} logging path must mask, so a secret riding on a DTO (an OAuth refresh token, an\n * id-token JWT) is never written to the logs in cleartext. The REAL value still travels on the wire\n * untouched — masking lives in the logging path only.\n *\n * ```typescript\n * @Endpoint('/account', 'rpc')\n * @MaskLog({ refreshToken: 'full', accessToken: 'last4', credential: 'full' })\n * getEmailAccount(request: GetEmailAccountRequest): Promise<GetEmailAccountResponse> { ... }\n * ```\n *\n * Matching is by field NAME at any depth (nested objects + array elements), so\n * `response.account.refreshToken` is masked. Declared on the SHARED api contract, so BOTH the client\n * `[API-client-*]` and server `[API-server-*]` lines mask it. The spec is read ONCE at route-build\n * time and rides {@link RouteMetadata.mask}, so an unmasked method pays nothing at call time.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function MaskLog(fields: Record<string, MaskMode>): MethodDecorator {\n const spec = new MaskSpec(fields);\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 specs: Record<string, MaskSpec> =\n Reflect.getMetadata(METADATA_KEYS.MASK_LOG, metadataTarget) || {};\n specs[propertyKey as string] = spec;\n Reflect.defineMetadata(METADATA_KEYS.MASK_LOG, specs, metadataTarget);\n };\n}\n\n/**\n * The @MaskLog spec for one method, or undefined if the method declared none (the common case — the\n * caller then logs the DTO verbatim on the plain JSON.stringify fast path).\n */\n// webpieces-disable no-function-outside-class -- reflect-metadata reader, sibling of getEndpointOptions\nexport function getMaskSpec(apiClass: Function, methodName: string): MaskSpec | undefined {\n const specs: Record<string, MaskSpec> =\n Reflect.getMetadata(METADATA_KEYS.MASK_LOG, apiClass) || {};\n return specs[methodName];\n}\n\n/**\n * Shared implementation for every auth decorator. Authorization is deliberately\n * method-only: a class-level default is too easy to miss when reviewing one endpoint.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nfunction defineAuthMode(mode: AuthMode): MethodDecorator {\n const authMeta = new AuthMeta(mode);\n\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any, propertyKey?: string | symbol, _descriptor?: PropertyDescriptor) => {\n if (propertyKey === undefined) {\n throw new Error(\n 'Authorization decorators are method-only; annotate every @Endpoint explicitly.',\n );\n }\n const metadataTarget = typeof target === 'function' ? target : target.constructor;\n if (Reflect.hasMetadata('webpieces:ipc-api-id', metadataTarget)) {\n throw new Error(\n `Internal API ${metadataTarget.name || 'Unknown'} cannot use HTTP authorization decorators.`,\n );\n }\n validateNoConflictingDecorators(metadataTarget, propertyKey as string);\n Reflect.defineMetadata(METADATA_KEYS.AUTH_META, authMeta, metadataTarget, propertyKey);\n const methods = new Set<string>(\n Reflect.getMetadata(METADATA_KEYS.AUTH_METHODS, metadataTarget) || [],\n );\n methods.add(propertyKey as string);\n Reflect.defineMetadata(METADATA_KEYS.AUTH_METHODS, [...methods], metadataTarget);\n };\n}\n\n/**\n * @WpAuthPublic(reason) - endpoint intentionally requires no authentication.\n * The non-empty reason makes anonymous exposure an explicit review decision.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function WpAuthPublic(reason: string): MethodDecorator {\n if (typeof reason !== 'string' || reason.trim() === '') {\n throw new Error(\n '@WpAuthPublic requires a non-empty reason explaining why anonymous access is required.',\n );\n }\n return defineAuthMode({ kind: 'public' });\n}\n\n/**\n * @WpAuthJwt(requirement) - THE user-facing JWT decorator, covering the whole user-JWT axis: the\n * compiler-enforced role decision ({@link JwtRoles}) plus app-defined fields ({@link JwtRequirement}).\n *\n * ```typescript\n * @WpAuthJwt({ roles: ['admin', 'editor'] }) // any-of\n * @WpAuthJwt({ allRolesAllowed: true, inOrg: true }) // wide + an app rule enforced by authorizeJwt\n * ```\n *\n * It absorbed the former `@Auth(requirement)` — same argument, same AuthMode, so two spellings of one\n * decision. One decorator per credential kind now: `@WpAuthPublic` / `@WpAuthJwt` / `@WpAuthOidc` /\n * `@WpAuthSharedSecret` / `@WpAuthLocalOnly`.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function WpAuthJwt(requirement: JwtRequirement): MethodDecorator {\n return defineAuthMode({ kind: 'jwt', requirement });\n}\n\n/**\n * The roles an endpoint accepts, or [] when it accepts every authenticated user. The ONE reader of\n * the {@link JwtRoles} union, so no caller has to re-derive \"does absent mean wide?\" — a question\n * whose two plausible answers is how the widest grant kept hiding behind an absent field.\n */\n// webpieces-disable no-function-outside-class -- reflect-metadata reader, sibling of getAuthMode\nexport function rolesRequired(requirement: JwtRequirement): readonly string[] {\n return requirement.allRolesAllowed === true ? [] : requirement.roles;\n}\n\n/**\n * @WpAuthOidc(...callers) - Google OIDC service-to-service auth (Cloud Tasks delivery / cross-service\n * RPC). `callers` is an OPTIONAL app-level allow-list of caller service accounts.\n *\n * NO args = TRUST THE EDGE: accept any genuine Google-signed OIDC caller, because a PRIVATE Cloud\n * Run service's edge already gates WHO via `run.invoker` IAM (managed in terraform — one source of\n * truth, no hand-synced list in code). If the service is actually PUBLIC, the verifier logs a loud\n * warning (it can't be the gate then). Pass explicit SAs (`@WpAuthOidc('svc-a')`) only when you want\n * an additional app-level allow-list as defense-in-depth.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function WpAuthOidc(...callers: string[]): MethodDecorator {\n return defineAuthMode({ kind: 'oidc', callers });\n}\n\n/**\n * @WpAuthSharedSecret(key) - constant-time compare of an inbound header against the secret bound for\n * `key`. `key` is a LOOKUP KEY (not an env var): the server looks up its accepted {@link SharedSecrets}\n * by this key, and each client looks up the value it sends by the SAME key (see {@link Secrets}).\n * For internal callers that cannot mint OIDC tokens.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function WpAuthSharedSecret(key: string): MethodDecorator {\n return defineAuthMode({ kind: 'shared-secret', secretKey: key });\n}\n\n/**\n * @WpAuthWebhook(name) - an OUTSIDE vendor signed this request in its OWN scheme; the app's bound\n * `WebhookAuthCallback` proves it. THE mode for every signed inbound webhook — Sentry, GitHub, Stripe, Slack,\n * Twilio — none of which fits the other kinds: no vendor mints Google OIDC tokens, and none sends its\n * secret (they all send a DERIVATION over the request), so `@WpAuthPublic` was the only reachable posture\n * and `calledBy: 'sentry'` stayed a claim rather than a fact.\n *\n * ```typescript\n * @WpAuthWebhook('sentry')\n * @Endpoint('/hook/sentry/issue', 'external', { calledBy: 'sentry', rawBody: true })\n * abstract notify(request: SentryIssueHook): Promise<HookAck>;\n * ```\n *\n * `name` is a bare STRING resolved through DI in the server's container, exactly as\n * `@WpAuthOidc('gmail-push')` already is — never a function reference. An api contract is level 0: a\n * direct reference to a verifier would invert the dependency graph and drag a vendor SDK into the\n * browser bundle that imports the same contract.\n *\n * THE FRAMEWORK IMPLEMENTS NO VENDOR CRYPTO, deliberately. Every vendor ships an official validator\n * (`twilio.validateRequest`, `stripe.webhooks.constructEvent`, `@octokit/webhooks-methods`) and every\n * vendor revises its scheme (Twilio added `bodySHA256` for JSON bodies; Stripe versions its header).\n * Reimplementing five of those is signing up to track five security changelogs forever and to be\n * wrong at the moment being wrong matters. The framework hands the hook enough of the raw request to\n * call the vendor's own library — hence the REQUIRED `{ rawBody: true }` (see\n * {@link EndpointOptions.rawBody}), which is checked at wiring time.\n *\n * FAILS CLOSED: with no `WebhookAuthCallback` bound, every `@WpAuthWebhook` endpoint 401s, matching `JwtHook`.\n * Silently allowing an unverified webhook is the one default that must not exist.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function WpAuthWebhook(name: string): MethodDecorator {\n return defineAuthMode({ kind: 'webhook', name });\n}\n\n/**\n * @WpAuthApiKey(regime, credentials) - a CUSTOMER holds the credential. The app's bound `ApiKeyHook`\n * authenticates the inbound request against its own datastore and returns the `ContextTuple` entries\n * the framework seeds into `RequestContext`. THE mode for a partner-facing contract consumed by other\n * companies' codebases (POS vendors, back-office platforms, ETL pipelines).\n *\n * ```typescript\n * @WpAuthApiKey('onetablet-partner', [\n * { in: 'header', name: 'x-api-key', description: 'The key issued to your integration.' },\n * { in: 'header', name: 'x-organization-id', description: 'Which of your organizations to act on.' },\n * ])\n * @ApiPath('/management/v1')\n * abstract class ManagementApi { ... }\n * ```\n *\n * `regime` is a bare STRING selecting WHICH key regime this route belongs to, exactly as\n * `@WpAuthSharedSecret(key)` and `@WpAuthWebhook(vendor)` already are — one hook serves several regimes,\n * and an api contract is level 0, so it never references a verifier directly.\n *\n * `credentials` DECLARES where the credential rides ({@link ApiKeyCredential}), so a spec generator\n * reading route auth metadata can emit `components.securitySchemes` instead of a human hand-writing\n * them into a manifest. It is a NON-EMPTY, ORDERED list rather than one credential because a real\n * regime authenticates a PAIR — the key names a customer, a second header names which of that\n * customer's organizations the request acts on, and a mismatch is a 401. Its OpenAPI form is two\n * schemes plus ONE security-requirement object holding BOTH keys (an AND); a LIST of two objects\n * would mean \"either alone suffices\", which is a load-bearing difference a single-credential shape\n * cannot even express. ORDER IS SIGNIFICANT and preserved: it is the order the credentials are\n * presented in the published document.\n *\n * The list can never be EMPTY — a contract that declares a key regime and then names no credential\n * would generate a document with no security block, which is the exact silent failure this argument\n * exists to remove. That is a compile error, not a runtime throw (see {@link JwtRoles}'s non-empty\n * tuple, the same device for the same reason).\n *\n * WHY IT IS NOT `@WpAuthSharedSecret`. Shared-secret declares that AN INTERNAL SERVICE is on the other\n * end, so the framework BELIEVES the trusted context headers that caller forwarded (see\n * `DestinationTrust.forAuthMode` and `AuthFilter.verifiesCaller`). A customer is not an internal\n * service: declaring a partner endpoint `@WpAuthSharedSecret` would let that partner assert someone\n * else's org id on the wire and have it admitted — a privilege escalation. `apikey` therefore sits\n * with `jwt` on the caller-NOT-verified side, where an inbound trusted header is admitted only when\n * the hook independently derived the SAME value.\n *\n * WHY THE HOOK SEES THE REQUEST, NOT ONE TOKEN. A real key regime checks the key TOGETHER WITH a\n * second header (the organization it is acting for), and `JwtHook.parseJwt` — handed one pre-extracted\n * token from one header — physically cannot. `ApiKeyHook.verifyApiKey(regime, request)` gets the whole\n * inbound request instead, so the app owns which headers carry the credential and validates them as a\n * PAIR. `credentials` does NOT change that: the framework reads no header from it and performs no\n * extraction. It is DECLARATION for readers of the contract, and enforcement stays entirely the hook's.\n *\n * FAILS CLOSED: with no `ApiKeyHook` bound, every `@WpAuthApiKey` endpoint 401s, matching `JwtHook` and\n * `WebhookAuthCallback`.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function WpAuthApiKey(regime: string, credentials: ApiKeyCredentials): MethodDecorator {\n return defineAuthMode({ kind: 'apikey', regime, credentials });\n}\n\n/**\n * @WpAuthLocalOnly() - this endpoint exists ONLY on a developer's machine. Off-local it is not\n * registered as a route at all, and if it is somehow reached it 404s. Method-only, like every\n * authorization decorator, so every endpoint's posture is visible at the method.\n *\n * ```typescript\n * @WpAuthLocalOnly()\n * @Endpoint('/logs', 'rpc')\n * sendBatch(request: SendLogBatchRequest): Promise<SendLogBatchResponse> { ... }\n * ```\n *\n * WHY IT IS AN AUTH MODE AND NOT A ROUTE-MODULE `if`. Apps hand-rolled this in TWO places kept in\n * sync by a comment: a route module that registered the route only locally, PLUS a\n * `if (env !== 'local') throw new ApiForbiddenError(...)` at the top of the handler. Neither half\n * was visible on the CONTRACT, so nothing reading the api — a human, a generated client, or an\n * agent — could tell this endpoint from a `@WpAuthPublic` one. Both halves are the framework's job now,\n * driven by this ONE declaration on the contract, which is where every other \"who may call this\"\n * fact already lives.\n *\n * It is DELIBERATELY a peer of @WpAuthPublic / @WpAuthJwt / @WpAuthOidc / @WpAuthSharedSecret / @WpAuthApiKey rather than an\n * option on one of them: one decorator per credential kind, and \"local-only\" is a different kind of\n * gate — it authenticates nobody, it excludes an entire environment.\n *\n * HOW \"local\" IS DECIDED: {@link RuntimeLocality}, declared once at startup (a REQUIRED input to\n * `RuntimeSetupOptions`). Undeclared means DEPLOYED, so a forgotten wiring call refuses the endpoint\n * rather than exposing it.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function WpAuthLocalOnly(): MethodDecorator {\n return defineAuthMode({ kind: 'local-only' });\n}\n\n// ============================================================\n// Helper functions\n// ============================================================\n\n/**\n * Get the base path from @ApiPath decorator.\n */\nexport function getApiPath(apiClass: Function): string | undefined {\n return Reflect.getMetadata(METADATA_KEYS.API_PATH, apiClass);\n}\n\n/**\n * Get all endpoints from @Endpoint decorators.\n * Returns a record of methodName -> endpoint path.\n */\nexport function getEndpoints(apiClass: Function): Record<string, string> | undefined {\n return Reflect.getMetadata(METADATA_KEYS.ENDPOINTS, apiClass);\n}\n\n/**\n * Every method's declared trigger kind, as `methodName -> kind`. Parallel to {@link getEndpoints}.\n * Empty for a class carrying no @Endpoint at all.\n */\n// webpieces-disable no-function-outside-class -- reflect-metadata reader, sibling of getEndpoints\nexport function getEndpointKinds(apiClass: Function): Record<string, EndpointKind> {\n return Reflect.getMetadata(METADATA_KEYS.ENDPOINT_KIND, apiClass) || {};\n}\n\n/**\n * What triggers ONE method, or undefined when the method carries no @Endpoint.\n *\n * Defaults to nothing rather than to 'rpc': `kind` is a required argument, so a missing entry means\n * \"this is not an endpoint\", never \"an endpoint that forgot to say\". Silently defaulting here would\n * put an undeclared cron or webhook back into the graph as a normal rpc call — the exact blindness\n * the required argument exists to remove.\n */\n// webpieces-disable no-function-outside-class -- reflect-metadata reader, sibling of getEndpoints\nexport function getEndpointKind(apiClass: Function, methodName: string): EndpointKind | undefined {\n return getEndpointKinds(apiClass)[methodName];\n}\n\n/**\n * Get the @Endpoint options for one method (empty object if the method had no options).\n */\n// webpieces-disable no-function-outside-class -- reflect-metadata reader, sibling of getEndpoints\nexport function getEndpointOptions(apiClass: Function, methodName: string): EndpointOptions {\n const opts: Record<string, EndpointOptions> =\n Reflect.getMetadata(METADATA_KEYS.ENDPOINT_OPTIONS, apiClass) || {};\n return opts[methodName] ?? {};\n}\n\n/**\n * Fail-fast at wiring time when an `external` endpoint declared no caller. The {@link Endpoint}\n * overloads already make that a COMPILE error; this is the backstop for the ways TS is bypassed —\n * a JS caller, an `as any` options object, a hand-rolled Reflect.defineMetadata.\n * @throws Error naming the first external endpoint with no `calledBy`.\n */\n// webpieces-disable no-function-outside-class -- wiring-time assert, sibling of assertEveryEndpointHasAuthMode\nexport function assertEveryExternalEndpointDeclaresCaller(apiClass: Function): void {\n const kinds = getEndpointKinds(apiClass);\n for (const methodName of Object.keys(kinds)) {\n if (\n kinds[methodName] !== 'external' ||\n getEndpointCaller(apiClass, methodName) !== undefined\n )\n continue;\n throw new Error(\n `External endpoint '${methodName}' in ${apiClass.name || 'Unknown'} declares no caller. Say WHO ` +\n `posts to it: @Endpoint(path, 'external', { calledBy: '<vendor>' }) — the runtime architecture ` +\n `graph cannot name an inbound caller it was never told about.`,\n );\n }\n}\n\n/**\n * True when the method's @Endpoint declared `{ formPost: true }` — its body is\n * application/x-www-form-urlencoded (flat), not JSON.\n */\n// webpieces-disable no-function-outside-class -- reflect-metadata reader, sibling of getEndpoints\nexport function isFormPost(apiClass: Function, methodName: string): boolean {\n return getEndpointOptions(apiClass, methodName).formPost === true;\n}\n\n/**\n * True when the method's @Endpoint declared `{ rawBody: true }` — the transport must retain the\n * verbatim bytes + absolute url for an {@link WpAuthWebhook} hook to verify.\n */\n// webpieces-disable no-function-outside-class -- reflect-metadata reader, sibling of isFormPost\nexport function isRawBody(apiClass: Function, methodName: string): boolean {\n return getEndpointOptions(apiClass, methodName).rawBody === true;\n}\n\n/**\n * Fail-fast at wiring time when an `@WpAuthWebhook` endpoint did not ask the transport to keep the\n * bytes it is supposed to verify. A hook with nothing to verify is a MISCONFIGURATION, and it must\n * surface at startup, naming the fix — not as a 401 in production on exactly the traffic the endpoint\n * exists for.\n *\n * This pairing is a runtime assert rather than a type because the two halves live on DIFFERENT\n * decorators (`@WpAuthWebhook` and `@Endpoint`), and no union over one decorator's argument can say\n * anything about the other's.\n *\n * @throws Error naming the first `@WpAuthWebhook` endpoint missing `{ rawBody: true }`.\n */\n// webpieces-disable no-function-outside-class -- wiring-time assert, sibling of assertEveryEndpointHasAuthMode\nexport function assertEveryWebhookEndpointRetainsRawBody(apiClass: Function): void {\n const endpoints = getEndpoints(apiClass) || {};\n for (const methodName of Object.keys(endpoints)) {\n if (\n getAuthMode(apiClass, methodName)?.kind !== 'webhook' ||\n isRawBody(apiClass, methodName)\n )\n continue;\n throw new Error(\n `Endpoint '${methodName}' in ${apiClass.name || 'Unknown'} is @WpAuthWebhook but its @Endpoint ` +\n `does not declare { rawBody: true }. A webhook hook verifies a signature over the bytes and ` +\n `the url the SENDER transmitted, and without that option the transport parses the body and ` +\n `throws them away — leaving the hook nothing to check.`,\n );\n }\n}\n\n/**\n * Check if a class has @ApiPath decorator.\n */\nexport function isApiPath(apiClass: Function): boolean {\n return Reflect.hasMetadata(METADATA_KEYS.API_PATH, apiClass);\n}\n\n/**\n * Get method-level auth metadata. Class-level authorization is forbidden.\n */\n// webpieces-disable no-function-outside-class -- reflect-metadata reader paired with the decorator API\nexport function getAuthMeta(apiClass: Function, methodName: string): AuthMeta | undefined {\n return Reflect.getMetadata(METADATA_KEYS.AUTH_META, apiClass, methodName);\n}\n\n/**\n * Get the auth mode for a method, or undefined.\n * Convenience wrapper over getAuthMeta for callers that only want the mode.\n */\n// webpieces-disable no-function-outside-class -- typed convenience reader over getAuthMeta\nexport function getAuthMode(apiClass: Function, methodName: string): AuthMode | undefined {\n return getAuthMeta(apiClass, methodName)?.mode;\n}\n\n/**\n * The ONE prescription for \"this endpoint declares no auth\", shared by the two places that raise it\n * (here and http-routing's ApiRoutingFactory) because they had drifted into teaching different menus.\n * A message teaching an incomplete API is the same defect as an API with two spellings: whichever menu\n * the caller hits becomes the API they believe exists. It leads with the ROLE-GATED member on purpose —\n * the first thing offered should not be the widest grant.\n */\nexport const MISSING_AUTH_DECORATOR_FIX =\n \"Add one of @WpAuthJwt({roles: ['admin']}) / @WpAuthJwt({allRolesAllowed: true}) / \" +\n '@WpAuthOidc(...callers) / @WpAuthSharedSecret(key) / ' +\n \"@WpAuthWebhook('vendor') / @WpAuthApiKey('regime', [{in: 'header', name: 'x-api-key'}]) / \" +\n \"@WpAuthLocalOnly() / @WpAuthPublic('why anonymous access is required') to \" +\n 'the method.';\n\n/**\n * Fail-fast at wiring time if any endpoint lacks an auth mode. Both the server\n * (ApiRoutingFactory) and the task/rpc clients call this so a missing auth\n * decorator is a startup error, never a silent open endpoint.\n * @throws Error naming the first endpoint with no auth decorator, via {@link MISSING_AUTH_DECORATOR_FIX}.\n */\nexport function assertEveryEndpointHasAuthMode(apiClass: Function): void {\n const apiName = apiClass.name || 'Unknown';\n const endpoints = getEndpoints(apiClass) || {};\n for (const methodName of Object.keys(endpoints)) {\n if (!getAuthMeta(apiClass, methodName)) {\n throw new Error(\n `Endpoint '${methodName}' in ${apiName} has no auth decorator. ` +\n MISSING_AUTH_DECORATOR_FIX,\n );\n }\n }\n}\n\n/**\n * Validate that a class/method doesn't have conflicting auth decorators.\n * @throws Error if multiple auth decorators are found on the same target.\n */\n// webpieces-disable no-function-outside-class -- shared decorator metadata validation\nexport function validateNoConflictingDecorators(\n apiClass: Function,\n methodName: string | undefined,\n): void {\n const existing = methodName\n ? Reflect.getMetadata(METADATA_KEYS.AUTH_META, apiClass, methodName)\n : Reflect.getMetadata(METADATA_KEYS.AUTH_META, apiClass);\n\n if (existing) {\n const targetName = apiClass.name || 'Unknown';\n const location = methodName\n ? `method '${methodName}' of ${targetName}`\n : `class ${targetName}`;\n throw new Error(\n `Conflicting auth decorator on ${location}. ` +\n `Only one of @WpAuthJwt({...}) / @WpAuthOidc(...) / @WpAuthSharedSecret(...) / ` +\n `@WpAuthWebhook(...) / @WpAuthApiKey(...) / @WpAuthLocalOnly() / ` +\n `@WpAuthPublic('reason') is allowed per target.`,\n );\n }\n}\n"]}
1
+ {"version":3,"file":"decorators.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/decorators.ts"],"names":[],"mappings":"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","sourcesContent":["import 'reflect-metadata';\nimport { MaskSpec, MaskMode } from './LogFieldMask';\nimport {\n DEFAULT_CALLER_KIND,\n ENDPOINT_CALLER_KEY,\n ExternalCaller,\n getEndpointCaller,\n} from './external-caller';\n// The TYPE layer these decorators attach — split out for file size only (see auth-mode.ts).\nimport { ApiKeyCredentials, AuthMeta, AuthMode, JwtRequirement } from './auth-mode';\nimport { EndpointOptions, ExternalEndpointOptions } from './HttpEndpointOptions';\nimport { HTTP_PARAMETERS_METADATA_KEY } from './http-parameter-decorators';\n\nexport type { EndpointOptions, ExternalEndpointOptions } from './HttpEndpointOptions';\nexport { PathParam, QueryParam, getHttpParameterDeclarations } from './http-parameter-decorators';\n\n/**\n * Metadata keys for storing API routing information.\n * These keys are used by both server-side (routing) and client-side (client generation).\n */\nexport const METADATA_KEYS = {\n API_PATH: 'webpieces:api-path',\n ENDPOINTS: 'webpieces:endpoints',\n AUTH_META: 'webpieces:auth-meta',\n /** Method names carrying one of the canonical authorization decorators. */\n AUTH_METHODS: 'webpieces:auth-methods',\n /** 'rpc' (default, sync request/response) vs 'pubsub' (fire-and-forget cloud task). */\n API_KIND: 'webpieces:api-kind',\n /** Per-method Cloud Tasks queue-name override (set via @Queue). */\n QUEUE_OVERRIDE: 'webpieces:queue-override',\n /** Per-method @Endpoint options (e.g. formPost), parallel to ENDPOINTS. */\n ENDPOINT_OPTIONS: 'webpieces:endpoint-options',\n /** Per-method @Endpoint trigger kind (rpc | cloudtasks | cron | external), parallel to ENDPOINTS. */\n ENDPOINT_KIND: 'webpieces:endpoint-kind',\n /** Per-method declared external CALLER (only for kind 'external'), parallel to ENDPOINTS. */\n ENDPOINT_CALLER: ENDPOINT_CALLER_KEY,\n /** Per-method @MaskLog spec (which DTO fields the LogApiCall path masks). */\n MASK_LOG: 'webpieces:mask-log',\n /** Per-method opt-in metadata for publishing an RPC endpoint as an MCP tool. */\n MCP_TOOLS: 'webpieces:mcp-tools',\n /** Per-method explicit path/query parameter declarations, keyed by parameter index. */\n HTTP_PARAMETERS: HTTP_PARAMETERS_METADATA_KEY,\n};\n\n/**\n * WHAT TRIGGERS an endpoint at runtime — the single fact that decides how the runtime architecture\n * graph draws it, and which Terraform resource must exist for it to ever fire:\n *\n * - `rpc` — a caller in this repo (or a browser) calls it synchronously. A direct arrow.\n * - `cloudtasks` — a producer ENQUEUES it; Cloud Tasks delivers it later. Drawn producer → queue →\n * consumer, one queue node per METHOD (see {@link Queue}). Producer and consumer\n * being the SAME service is legal and common — the queue decouples them.\n * - `cron` — a scheduler fires it on a clock. Nothing in-repo calls it; drawn hanging off a\n * clock symbol. Backed by a Cloud Scheduler job.\n * - `external` — a system OUTSIDE this repo drives it (a GCP Pub/Sub push subscription, a Twilio\n * or Gmail webhook). Drawn as an inbound dashed arrow from that system.\n *\n * Declared PER METHOD, because one api class routinely mixes them: an admin contract can have\n * caller-driven endpoints AND a nightly cron sweep. A class-level marker cannot express that, which\n * is exactly why the graph could not tell these apart before.\n */\nexport type EndpointKind = 'rpc' | 'cloudtasks' | 'cron' | 'external';\n\n/**\n * Options for a single @Endpoint. Kept in a metadata map PARALLEL to ENDPOINTS so the existing\n * `Record<methodName, path>` shape every consumer iterates stays unchanged.\n */\n/**\n * @ApiPath(basePath) - Class decorator that marks a class as an API definition\n * and sets the base path for all endpoints.\n *\n * Usage:\n * ```typescript\n * @ApiPath('/api/save')\n * abstract class SaveApi {\n * @WpAuthJwt({ roles: ['admin'] })\n * @Endpoint('/item', 'rpc')\n * save(request: SaveRequest): Promise<SaveResponse> { ... }\n * }\n * ```\n */\nexport function ApiPath(basePath: string): ClassDecorator {\n return (target: Function) => {\n if (Reflect.hasMetadata('webpieces:ipc-api-id', target)) {\n throw new Error(\n `Class ${target.name || 'Unknown'} cannot combine @ApiPath with @WpInternal.`,\n );\n }\n Reflect.defineMetadata(METADATA_KEYS.API_PATH, basePath, target);\n\n // Initialize endpoints map if not exists\n if (!Reflect.hasMetadata(METADATA_KEYS.ENDPOINTS, target)) {\n Reflect.defineMetadata(METADATA_KEYS.ENDPOINTS, {}, target);\n }\n };\n}\n\n/**\n * @Endpoint(path, kind, options?) - Method decorator that registers an HTTP endpoint at the given\n * path and declares WHAT TRIGGERS it. POST is the default; GET is explicit in `httpMethod`.\n *\n * Usage:\n * ```typescript\n * @Endpoint('/item', 'rpc')\n * save(request: SaveRequest): Promise<SaveResponse> { ... }\n *\n * // enqueued by a producer, delivered later by Cloud Tasks:\n * @Endpoint('/send', 'cloudtasks')\n * send(request: SendRequest): Promise<void> { ... }\n *\n * // fired by Cloud Scheduler on a clock, called by nobody in this repo:\n * @Endpoint('/nightly', 'cron')\n * nightly(request: NightlyRequest): Promise<void> { ... }\n *\n * // EXTERNAL webhook posting application/x-www-form-urlencoded (e.g. Twilio):\n * @Endpoint('/hook', 'external', { formPost: true, calledBy: 'twilio' })\n * inbound(request: InboundRequest): Promise<InboundResponse> { ... }\n * ```\n *\n * `kind` is REQUIRED and deliberately positional: it makes every pre-existing single-argument\n * `@Endpoint('/x')` a COMPILE error rather than something a lint rule has to chase, so no endpoint\n * can slip into the runtime architecture graph with its trigger left to guesswork. See\n * {@link EndpointKind} for what each value draws and which Terraform resource backs it.\n *\n * `calledBy` is REQUIRED for `external` FOR EXACTLY THE SAME REASON, enforced by the overloads below:\n * the one box on the runtime graph whose whole job is to say who calls us from outside could only\n * restate OUR OWN contract name, because nothing in the source ever said who the caller was. This is\n * BREAKING for published consumers, intentionally — an existing `@Endpoint(p, 'external', {...})`\n * stops compiling until it names its caller. Migration is one property; see the migration note in\n * `external-caller.ts`. Non-`external` endpoints are completely unaffected.\n *\n * The path write to ENDPOINTS is UNCHANGED (every consumer iterates `[methodName, path]`); kind,\n * options and caller ride PARALLEL ENDPOINT_KIND / ENDPOINT_OPTIONS / ENDPOINT_CALLER maps.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function Endpoint(\n path: string,\n kind: 'external',\n options: ExternalEndpointOptions,\n): MethodDecorator;\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function Endpoint(\n path: string,\n kind: Exclude<EndpointKind, 'external'>,\n options?: EndpointOptions,\n): MethodDecorator;\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function Endpoint(\n path: string,\n kind: EndpointKind,\n options: EndpointOptions = {},\n): 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\n const endpoints: Record<string, string> =\n Reflect.getMetadata(METADATA_KEYS.ENDPOINTS, metadataTarget) || {};\n\n endpoints[propertyKey as string] = path;\n\n Reflect.defineMetadata(METADATA_KEYS.ENDPOINTS, endpoints, metadataTarget);\n\n const kinds: Record<string, EndpointKind> =\n Reflect.getMetadata(METADATA_KEYS.ENDPOINT_KIND, metadataTarget) || {};\n kinds[propertyKey as string] = kind;\n Reflect.defineMetadata(METADATA_KEYS.ENDPOINT_KIND, kinds, metadataTarget);\n\n const opts: Record<string, EndpointOptions> =\n Reflect.getMetadata(METADATA_KEYS.ENDPOINT_OPTIONS, metadataTarget) || {};\n opts[propertyKey as string] = options;\n Reflect.defineMetadata(METADATA_KEYS.ENDPOINT_OPTIONS, opts, metadataTarget);\n\n // ONLY for 'external', mirroring how a queue name is recorded only for the kinds that HAVE\n // a queue: a caller on an rpc endpoint would be a fact about nothing.\n const declared = options as ExternalEndpointOptions;\n if (\n kind !== 'external' ||\n typeof declared.calledBy !== 'string' ||\n declared.calledBy === ''\n )\n return;\n const callers: Record<string, ExternalCaller> =\n Reflect.getMetadata(METADATA_KEYS.ENDPOINT_CALLER, metadataTarget) || {};\n callers[propertyKey as string] = new ExternalCaller(\n declared.callerKind ?? DEFAULT_CALLER_KIND,\n declared.calledBy,\n );\n Reflect.defineMetadata(METADATA_KEYS.ENDPOINT_CALLER, callers, metadataTarget);\n };\n}\n\n/**\n * @MaskLog(fields) - declare which fields of THIS method's request/response DTOs the\n * {@link LogApiCallImpl} logging path must mask, so a secret riding on a DTO (an OAuth refresh token, an\n * id-token JWT) is never written to the logs in cleartext. The REAL value still travels on the wire\n * untouched — masking lives in the logging path only.\n *\n * ```typescript\n * @Endpoint('/account', 'rpc')\n * @MaskLog({ refreshToken: 'full', accessToken: 'last4', credential: 'full' })\n * getEmailAccount(request: GetEmailAccountRequest): Promise<GetEmailAccountResponse> { ... }\n * ```\n *\n * Matching is by field NAME at any depth (nested objects + array elements), so\n * `response.account.refreshToken` is masked. Declared on the SHARED api contract, so BOTH the client\n * `[API-client-*]` and server `[API-server-*]` lines mask it. The spec is read ONCE at route-build\n * time and rides {@link RouteMetadata.mask}, so an unmasked method pays nothing at call time.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function MaskLog(fields: Record<string, MaskMode>): MethodDecorator {\n const spec = new MaskSpec(fields);\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 specs: Record<string, MaskSpec> =\n Reflect.getMetadata(METADATA_KEYS.MASK_LOG, metadataTarget) || {};\n specs[propertyKey as string] = spec;\n Reflect.defineMetadata(METADATA_KEYS.MASK_LOG, specs, metadataTarget);\n };\n}\n\n/**\n * The @MaskLog spec for one method, or undefined if the method declared none (the common case — the\n * caller then logs the DTO verbatim on the plain JSON.stringify fast path).\n */\n// webpieces-disable no-function-outside-class -- reflect-metadata reader, sibling of getEndpointOptions\nexport function getMaskSpec(apiClass: Function, methodName: string): MaskSpec | undefined {\n const specs: Record<string, MaskSpec> =\n Reflect.getMetadata(METADATA_KEYS.MASK_LOG, apiClass) || {};\n return specs[methodName];\n}\n\n/**\n * Shared implementation for every auth decorator. Authorization is deliberately\n * method-only: a class-level default is too easy to miss when reviewing one endpoint.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nfunction defineAuthMode(mode: AuthMode): MethodDecorator {\n const authMeta = new AuthMeta(mode);\n\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any, propertyKey?: string | symbol, _descriptor?: PropertyDescriptor) => {\n if (propertyKey === undefined) {\n throw new Error(\n 'Authorization decorators are method-only; annotate every @Endpoint explicitly.',\n );\n }\n const metadataTarget = typeof target === 'function' ? target : target.constructor;\n if (Reflect.hasMetadata('webpieces:ipc-api-id', metadataTarget)) {\n throw new Error(\n `Internal API ${metadataTarget.name || 'Unknown'} cannot use HTTP authorization decorators.`,\n );\n }\n validateNoConflictingDecorators(metadataTarget, propertyKey as string);\n Reflect.defineMetadata(METADATA_KEYS.AUTH_META, authMeta, metadataTarget, propertyKey);\n const methods = new Set<string>(\n Reflect.getMetadata(METADATA_KEYS.AUTH_METHODS, metadataTarget) || [],\n );\n methods.add(propertyKey as string);\n Reflect.defineMetadata(METADATA_KEYS.AUTH_METHODS, [...methods], metadataTarget);\n };\n}\n\n/**\n * @WpAuthPublic(reason) - endpoint intentionally requires no authentication.\n * The non-empty reason makes anonymous exposure an explicit review decision.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function WpAuthPublic(reason: string): MethodDecorator {\n if (typeof reason !== 'string' || reason.trim() === '') {\n throw new Error(\n '@WpAuthPublic requires a non-empty reason explaining why anonymous access is required.',\n );\n }\n return defineAuthMode({ kind: 'public' });\n}\n\n/**\n * @WpAuthJwt(requirement) - THE user-facing JWT decorator, covering the whole user-JWT axis: the\n * compiler-enforced role decision ({@link JwtRoles}) plus app-defined fields ({@link JwtRequirement}).\n *\n * ```typescript\n * @WpAuthJwt({ roles: ['admin', 'editor'] }) // any-of\n * @WpAuthJwt({ allRolesAllowed: true, inOrg: true }) // wide + an app rule enforced by authorizeJwt\n * ```\n *\n * It absorbed the former `@Auth(requirement)` — same argument, same AuthMode, so two spellings of one\n * decision. One decorator per credential kind now: `@WpAuthPublic` / `@WpAuthJwt` / `@WpAuthOidc` /\n * `@WpAuthSharedSecret` / `@WpAuthLocalOnly`.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function WpAuthJwt(requirement: JwtRequirement): MethodDecorator {\n return defineAuthMode({ kind: 'jwt', requirement });\n}\n\n/**\n * The roles an endpoint accepts, or [] when it accepts every authenticated user. The ONE reader of\n * the {@link JwtRoles} union, so no caller has to re-derive \"does absent mean wide?\" — a question\n * whose two plausible answers is how the widest grant kept hiding behind an absent field.\n */\n// webpieces-disable no-function-outside-class -- reflect-metadata reader, sibling of getAuthMode\nexport function rolesRequired(requirement: JwtRequirement): readonly string[] {\n return requirement.allRolesAllowed === true ? [] : requirement.roles;\n}\n\n/**\n * @WpAuthOidc(...callers) - Google OIDC service-to-service auth (Cloud Tasks delivery / cross-service\n * RPC). `callers` is an OPTIONAL app-level allow-list of caller service accounts.\n *\n * NO args = TRUST THE EDGE: accept any genuine Google-signed OIDC caller, because a PRIVATE Cloud\n * Run service's edge already gates WHO via `run.invoker` IAM (managed in terraform — one source of\n * truth, no hand-synced list in code). If the service is actually PUBLIC, the verifier logs a loud\n * warning (it can't be the gate then). Pass explicit SAs (`@WpAuthOidc('svc-a')`) only when you want\n * an additional app-level allow-list as defense-in-depth.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function WpAuthOidc(...callers: string[]): MethodDecorator {\n return defineAuthMode({ kind: 'oidc', callers });\n}\n\n/**\n * @WpAuthSharedSecret(key) - constant-time compare of an inbound header against the secret bound for\n * `key`. `key` is a LOOKUP KEY (not an env var): the server looks up its accepted {@link SharedSecrets}\n * by this key, and each client looks up the value it sends by the SAME key (see {@link Secrets}).\n * For internal callers that cannot mint OIDC tokens.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function WpAuthSharedSecret(key: string): MethodDecorator {\n return defineAuthMode({ kind: 'shared-secret', secretKey: key });\n}\n\n/**\n * @WpAuthWebhook(name) - an OUTSIDE vendor signed this request in its OWN scheme; the app's bound\n * `WebhookAuthCallback` proves it. THE mode for every signed inbound webhook — Sentry, GitHub, Stripe, Slack,\n * Twilio — none of which fits the other kinds: no vendor mints Google OIDC tokens, and none sends its\n * secret (they all send a DERIVATION over the request), so `@WpAuthPublic` was the only reachable posture\n * and `calledBy: 'sentry'` stayed a claim rather than a fact.\n *\n * ```typescript\n * @WpAuthWebhook('sentry')\n * @Endpoint('/hook/sentry/issue', 'external', { calledBy: 'sentry', rawBody: true })\n * abstract notify(request: SentryIssueHook): Promise<HookAck>;\n * ```\n *\n * `name` is a bare STRING resolved through DI in the server's container, exactly as\n * `@WpAuthOidc('gmail-push')` already is — never a function reference. An api contract is level 0: a\n * direct reference to a verifier would invert the dependency graph and drag a vendor SDK into the\n * browser bundle that imports the same contract.\n *\n * THE FRAMEWORK IMPLEMENTS NO VENDOR CRYPTO, deliberately. Every vendor ships an official validator\n * (`twilio.validateRequest`, `stripe.webhooks.constructEvent`, `@octokit/webhooks-methods`) and every\n * vendor revises its scheme (Twilio added `bodySHA256` for JSON bodies; Stripe versions its header).\n * Reimplementing five of those is signing up to track five security changelogs forever and to be\n * wrong at the moment being wrong matters. The framework hands the hook enough of the raw request to\n * call the vendor's own library — hence the REQUIRED `{ rawBody: true }` (see\n * {@link EndpointOptions.rawBody}), which is checked at wiring time.\n *\n * FAILS CLOSED: with no `WebhookAuthCallback` bound, every `@WpAuthWebhook` endpoint 401s, matching `JwtHook`.\n * Silently allowing an unverified webhook is the one default that must not exist.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function WpAuthWebhook(name: string): MethodDecorator {\n return defineAuthMode({ kind: 'webhook', name });\n}\n\n/**\n * @WpAuthApiKey(regime, credentials) - a CUSTOMER holds the credential. The app's bound `ApiKeyHook`\n * authenticates the inbound request against its own datastore and returns the `ContextTuple` entries\n * the framework seeds into `RequestContext`. THE mode for a partner-facing contract consumed by other\n * companies' codebases (POS vendors, back-office platforms, ETL pipelines).\n *\n * ```typescript\n * @WpAuthApiKey('onetablet-partner', [\n * { in: 'header', name: 'x-api-key', description: 'The key issued to your integration.' },\n * { in: 'header', name: 'x-organization-id', description: 'Which of your organizations to act on.' },\n * ])\n * @ApiPath('/management/v1')\n * abstract class ManagementApi { ... }\n * ```\n *\n * `regime` is a bare STRING selecting WHICH key regime this route belongs to, exactly as\n * `@WpAuthSharedSecret(key)` and `@WpAuthWebhook(vendor)` already are — one hook serves several regimes,\n * and an api contract is level 0, so it never references a verifier directly.\n *\n * `credentials` DECLARES where the credential rides ({@link ApiKeyCredential}), so a spec generator\n * reading route auth metadata can emit `components.securitySchemes` instead of a human hand-writing\n * them into a manifest. It is a NON-EMPTY, ORDERED list rather than one credential because a real\n * regime authenticates a PAIR — the key names a customer, a second header names which of that\n * customer's organizations the request acts on, and a mismatch is a 401. Its OpenAPI form is two\n * schemes plus ONE security-requirement object holding BOTH keys (an AND); a LIST of two objects\n * would mean \"either alone suffices\", which is a load-bearing difference a single-credential shape\n * cannot even express. ORDER IS SIGNIFICANT and preserved: it is the order the credentials are\n * presented in the published document.\n *\n * The list can never be EMPTY — a contract that declares a key regime and then names no credential\n * would generate a document with no security block, which is the exact silent failure this argument\n * exists to remove. That is a compile error, not a runtime throw (see {@link JwtRoles}'s non-empty\n * tuple, the same device for the same reason).\n *\n * WHY IT IS NOT `@WpAuthSharedSecret`. Shared-secret declares that AN INTERNAL SERVICE is on the other\n * end, so the framework BELIEVES the trusted context headers that caller forwarded (see\n * `DestinationTrust.forAuthMode` and `AuthFilter.verifiesCaller`). A customer is not an internal\n * service: declaring a partner endpoint `@WpAuthSharedSecret` would let that partner assert someone\n * else's org id on the wire and have it admitted — a privilege escalation. `apikey` therefore sits\n * with `jwt` on the caller-NOT-verified side, where an inbound trusted header is admitted only when\n * the hook independently derived the SAME value.\n *\n * WHY THE HOOK SEES THE REQUEST, NOT ONE TOKEN. A real key regime checks the key TOGETHER WITH a\n * second header (the organization it is acting for), and `JwtHook.parseJwt` — handed one pre-extracted\n * token from one header — physically cannot. `ApiKeyHook.verifyApiKey(regime, request)` gets the whole\n * inbound request instead, so the app owns which headers carry the credential and validates them as a\n * PAIR. `credentials` does NOT change that: the framework reads no header from it and performs no\n * extraction. It is DECLARATION for readers of the contract, and enforcement stays entirely the hook's.\n *\n * FAILS CLOSED: with no `ApiKeyHook` bound, every `@WpAuthApiKey` endpoint 401s, matching `JwtHook` and\n * `WebhookAuthCallback`.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function WpAuthApiKey(regime: string, credentials: ApiKeyCredentials): MethodDecorator {\n return defineAuthMode({ kind: 'apikey', regime, credentials });\n}\n\n/**\n * @WpAuthLocalOnly() - this endpoint exists ONLY on a developer's machine. Off-local it is not\n * registered as a route at all, and if it is somehow reached it 404s. Method-only, like every\n * authorization decorator, so every endpoint's posture is visible at the method.\n *\n * ```typescript\n * @WpAuthLocalOnly()\n * @Endpoint('/logs', 'rpc')\n * sendBatch(request: SendLogBatchRequest): Promise<SendLogBatchResponse> { ... }\n * ```\n *\n * WHY IT IS AN AUTH MODE AND NOT A ROUTE-MODULE `if`. Apps hand-rolled this in TWO places kept in\n * sync by a comment: a route module that registered the route only locally, PLUS a\n * `if (env !== 'local') throw new ApiForbiddenError(...)` at the top of the handler. Neither half\n * was visible on the CONTRACT, so nothing reading the api — a human, a generated client, or an\n * agent — could tell this endpoint from a `@WpAuthPublic` one. Both halves are the framework's job now,\n * driven by this ONE declaration on the contract, which is where every other \"who may call this\"\n * fact already lives.\n *\n * It is DELIBERATELY a peer of @WpAuthPublic / @WpAuthJwt / @WpAuthOidc / @WpAuthSharedSecret / @WpAuthApiKey rather than an\n * option on one of them: one decorator per credential kind, and \"local-only\" is a different kind of\n * gate — it authenticates nobody, it excludes an entire environment.\n *\n * HOW \"local\" IS DECIDED: {@link RuntimeLocality}, declared once at startup (a REQUIRED input to\n * `RuntimeSetupOptions`). Undeclared means DEPLOYED, so a forgotten wiring call refuses the endpoint\n * rather than exposing it.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function WpAuthLocalOnly(): MethodDecorator {\n return defineAuthMode({ kind: 'local-only' });\n}\n\n// ============================================================\n// Helper functions\n// ============================================================\n\n/**\n * Get the base path from @ApiPath decorator.\n */\nexport function getApiPath(apiClass: Function): string | undefined {\n return Reflect.getMetadata(METADATA_KEYS.API_PATH, apiClass);\n}\n\n/**\n * Get all endpoints from @Endpoint decorators.\n * Returns a record of methodName -> endpoint path.\n */\nexport function getEndpoints(apiClass: Function): Record<string, string> | undefined {\n return Reflect.getMetadata(METADATA_KEYS.ENDPOINTS, apiClass);\n}\n\n/**\n * Every method's declared trigger kind, as `methodName -> kind`. Parallel to {@link getEndpoints}.\n * Empty for a class carrying no @Endpoint at all.\n */\n// webpieces-disable no-function-outside-class -- reflect-metadata reader, sibling of getEndpoints\nexport function getEndpointKinds(apiClass: Function): Record<string, EndpointKind> {\n return Reflect.getMetadata(METADATA_KEYS.ENDPOINT_KIND, apiClass) || {};\n}\n\n/**\n * What triggers ONE method, or undefined when the method carries no @Endpoint.\n *\n * Defaults to nothing rather than to 'rpc': `kind` is a required argument, so a missing entry means\n * \"this is not an endpoint\", never \"an endpoint that forgot to say\". Silently defaulting here would\n * put an undeclared cron or webhook back into the graph as a normal rpc call — the exact blindness\n * the required argument exists to remove.\n */\n// webpieces-disable no-function-outside-class -- reflect-metadata reader, sibling of getEndpoints\nexport function getEndpointKind(apiClass: Function, methodName: string): EndpointKind | undefined {\n return getEndpointKinds(apiClass)[methodName];\n}\n\n/**\n * Get the @Endpoint options for one method (empty object if the method had no options).\n */\n// webpieces-disable no-function-outside-class -- reflect-metadata reader, sibling of getEndpoints\nexport function getEndpointOptions(apiClass: Function, methodName: string): EndpointOptions {\n const opts: Record<string, EndpointOptions> =\n Reflect.getMetadata(METADATA_KEYS.ENDPOINT_OPTIONS, apiClass) || {};\n return opts[methodName] ?? {};\n}\n\n/**\n * Fail-fast at wiring time when an `external` endpoint declared no caller. The {@link Endpoint}\n * overloads already make that a COMPILE error; this is the backstop for the ways TS is bypassed —\n * a JS caller, an `as any` options object, a hand-rolled Reflect.defineMetadata.\n * @throws Error naming the first external endpoint with no `calledBy`.\n */\n// webpieces-disable no-function-outside-class -- wiring-time assert, sibling of assertEveryEndpointHasAuthMode\nexport function assertEveryExternalEndpointDeclaresCaller(apiClass: Function): void {\n const kinds = getEndpointKinds(apiClass);\n for (const methodName of Object.keys(kinds)) {\n if (\n kinds[methodName] !== 'external' ||\n getEndpointCaller(apiClass, methodName) !== undefined\n )\n continue;\n throw new Error(\n `External endpoint '${methodName}' in ${apiClass.name || 'Unknown'} declares no caller. Say WHO ` +\n `posts to it: @Endpoint(path, 'external', { calledBy: '<vendor>' }) — the runtime architecture ` +\n `graph cannot name an inbound caller it was never told about.`,\n );\n }\n}\n\n/**\n * True when the method's @Endpoint declared `{ formPost: true }` — its body is\n * application/x-www-form-urlencoded (flat), not JSON.\n */\n// webpieces-disable no-function-outside-class -- reflect-metadata reader, sibling of getEndpoints\nexport function isFormPost(apiClass: Function, methodName: string): boolean {\n return getEndpointOptions(apiClass, methodName).formPost === true;\n}\n\n/**\n * True when the method's @Endpoint declared `{ rawBody: true }` — the transport must retain the\n * verbatim bytes + absolute url for an {@link WpAuthWebhook} hook to verify.\n */\n// webpieces-disable no-function-outside-class -- reflect-metadata reader, sibling of isFormPost\nexport function isRawBody(apiClass: Function, methodName: string): boolean {\n return getEndpointOptions(apiClass, methodName).rawBody === true;\n}\n\n/**\n * Fail-fast at wiring time when an `@WpAuthWebhook` endpoint did not ask the transport to keep the\n * bytes it is supposed to verify. A hook with nothing to verify is a MISCONFIGURATION, and it must\n * surface at startup, naming the fix — not as a 401 in production on exactly the traffic the endpoint\n * exists for.\n *\n * This pairing is a runtime assert rather than a type because the two halves live on DIFFERENT\n * decorators (`@WpAuthWebhook` and `@Endpoint`), and no union over one decorator's argument can say\n * anything about the other's.\n *\n * @throws Error naming the first `@WpAuthWebhook` endpoint missing `{ rawBody: true }`.\n */\n// webpieces-disable no-function-outside-class -- wiring-time assert, sibling of assertEveryEndpointHasAuthMode\nexport function assertEveryWebhookEndpointRetainsRawBody(apiClass: Function): void {\n const endpoints = getEndpoints(apiClass) || {};\n for (const methodName of Object.keys(endpoints)) {\n if (\n getAuthMode(apiClass, methodName)?.kind !== 'webhook' ||\n isRawBody(apiClass, methodName)\n )\n continue;\n throw new Error(\n `Endpoint '${methodName}' in ${apiClass.name || 'Unknown'} is @WpAuthWebhook but its @Endpoint ` +\n `does not declare { rawBody: true }. A webhook hook verifies a signature over the bytes and ` +\n `the url the SENDER transmitted, and without that option the transport parses the body and ` +\n `throws them away — leaving the hook nothing to check.`,\n );\n }\n}\n\n/**\n * Check if a class has @ApiPath decorator.\n */\nexport function isApiPath(apiClass: Function): boolean {\n return Reflect.hasMetadata(METADATA_KEYS.API_PATH, apiClass);\n}\n\n/**\n * Get method-level auth metadata. Class-level authorization is forbidden.\n */\n// webpieces-disable no-function-outside-class -- reflect-metadata reader paired with the decorator API\nexport function getAuthMeta(apiClass: Function, methodName: string): AuthMeta | undefined {\n return Reflect.getMetadata(METADATA_KEYS.AUTH_META, apiClass, methodName);\n}\n\n/**\n * Get the auth mode for a method, or undefined.\n * Convenience wrapper over getAuthMeta for callers that only want the mode.\n */\n// webpieces-disable no-function-outside-class -- typed convenience reader over getAuthMeta\nexport function getAuthMode(apiClass: Function, methodName: string): AuthMode | undefined {\n return getAuthMeta(apiClass, methodName)?.mode;\n}\n\n/**\n * The ONE prescription for \"this endpoint declares no auth\", shared by the two places that raise it\n * (here and http-routing's ApiRoutingFactory) because they had drifted into teaching different menus.\n * A message teaching an incomplete API is the same defect as an API with two spellings: whichever menu\n * the caller hits becomes the API they believe exists. It leads with the ROLE-GATED member on purpose —\n * the first thing offered should not be the widest grant.\n */\nexport const MISSING_AUTH_DECORATOR_FIX =\n \"Add one of @WpAuthJwt({roles: ['admin']}) / @WpAuthJwt({allRolesAllowed: true}) / \" +\n '@WpAuthOidc(...callers) / @WpAuthSharedSecret(key) / ' +\n \"@WpAuthWebhook('vendor') / @WpAuthApiKey('regime', [{in: 'header', name: 'x-api-key'}]) / \" +\n \"@WpAuthLocalOnly() / @WpAuthPublic('why anonymous access is required') to \" +\n 'the method.';\n\n/**\n * Fail-fast at wiring time if any endpoint lacks an auth mode. Both the server\n * (ApiRoutingFactory) and the task/rpc clients call this so a missing auth\n * decorator is a startup error, never a silent open endpoint.\n * @throws Error naming the first endpoint with no auth decorator, via {@link MISSING_AUTH_DECORATOR_FIX}.\n */\nexport function assertEveryEndpointHasAuthMode(apiClass: Function): void {\n const apiName = apiClass.name || 'Unknown';\n const endpoints = getEndpoints(apiClass) || {};\n for (const methodName of Object.keys(endpoints)) {\n if (!getAuthMeta(apiClass, methodName)) {\n throw new Error(\n `Endpoint '${methodName}' in ${apiName} has no auth decorator. ` +\n MISSING_AUTH_DECORATOR_FIX,\n );\n }\n }\n}\n\n/**\n * Validate that a class/method doesn't have conflicting auth decorators.\n * @throws Error if multiple auth decorators are found on the same target.\n */\n// webpieces-disable no-function-outside-class -- shared decorator metadata validation\nexport function validateNoConflictingDecorators(\n apiClass: Function,\n methodName: string | undefined,\n): void {\n const existing = methodName\n ? Reflect.getMetadata(METADATA_KEYS.AUTH_META, apiClass, methodName)\n : Reflect.getMetadata(METADATA_KEYS.AUTH_META, apiClass);\n\n if (existing) {\n const targetName = apiClass.name || 'Unknown';\n const location = methodName\n ? `method '${methodName}' of ${targetName}`\n : `class ${targetName}`;\n throw new Error(\n `Conflicting auth decorator on ${location}. ` +\n `Only one of @WpAuthJwt({...}) / @WpAuthOidc(...) / @WpAuthSharedSecret(...) / ` +\n `@WpAuthWebhook(...) / @WpAuthApiKey(...) / @WpAuthLocalOnly() / ` +\n `@WpAuthPublic('reason') is allowed per target.`,\n );\n }\n}\n"]}
@@ -0,0 +1,9 @@
1
+ import 'reflect-metadata';
2
+ import { HttpParameterDeclaration } from './HttpContract';
3
+ export declare const HTTP_PARAMETERS_METADATA_KEY = "webpieces:http-parameters";
4
+ /** Map one method argument to a `{placeholder}` in `@ApiPath + @Endpoint`. */
5
+ export declare function PathParam(wireName: string): ParameterDecorator;
6
+ /** Map one method argument to an explicitly named query key. */
7
+ export declare function QueryParam(wireName: string): ParameterDecorator;
8
+ /** Explicit path/query mappings for one endpoint, independent of JavaScript parameter names. */
9
+ export declare function getHttpParameterDeclarations(apiClass: Function, methodName: string): HttpParameterDeclaration[];
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HTTP_PARAMETERS_METADATA_KEY = void 0;
4
+ exports.PathParam = PathParam;
5
+ exports.QueryParam = QueryParam;
6
+ exports.getHttpParameterDeclarations = getHttpParameterDeclarations;
7
+ require("reflect-metadata");
8
+ const HttpContract_1 = require("./HttpContract");
9
+ exports.HTTP_PARAMETERS_METADATA_KEY = 'webpieces:http-parameters';
10
+ /** Shared implementation for explicit method-parameter wire mappings. */
11
+ // webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope
12
+ function defineHttpParameter(source, wireName) {
13
+ if (typeof wireName !== 'string' || wireName.trim() === '') {
14
+ throw new Error(`@${source === 'path' ? 'PathParam' : 'QueryParam'} requires a non-empty wire name.`);
15
+ }
16
+ return (
17
+ // webpieces-disable no-any-unknown -- reflect-metadata parameter decorator API requires any
18
+ target, propertyKey, parameterIndex) => {
19
+ if (propertyKey === undefined) {
20
+ throw new Error('HTTP parameter decorators are method-only.');
21
+ }
22
+ const metadataTarget = typeof target === 'function' ? target : target.constructor;
23
+ const all = Reflect.getMetadata(exports.HTTP_PARAMETERS_METADATA_KEY, metadataTarget) || {};
24
+ const declarations = [...(all[propertyKey] ?? [])];
25
+ if (declarations.some((declaration) => declaration.index === parameterIndex)) {
26
+ throw new Error(`Argument ${parameterIndex} of ${metadataTarget.name}.${String(propertyKey)} already has an HTTP parameter decorator.`);
27
+ }
28
+ declarations.push(new HttpContract_1.HttpParameterDeclaration(parameterIndex, source, wireName));
29
+ all[propertyKey] = declarations;
30
+ Reflect.defineMetadata(exports.HTTP_PARAMETERS_METADATA_KEY, all, metadataTarget);
31
+ };
32
+ }
33
+ /** Map one method argument to a `{placeholder}` in `@ApiPath + @Endpoint`. */
34
+ // webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope
35
+ function PathParam(wireName) {
36
+ return defineHttpParameter('path', wireName);
37
+ }
38
+ /** Map one method argument to an explicitly named query key. */
39
+ // webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope
40
+ function QueryParam(wireName) {
41
+ return defineHttpParameter('query', wireName);
42
+ }
43
+ /** Explicit path/query mappings for one endpoint, independent of JavaScript parameter names. */
44
+ // webpieces-disable no-function-outside-class -- reflect-metadata reader
45
+ function getHttpParameterDeclarations(apiClass, methodName) {
46
+ const all = Reflect.getMetadata(exports.HTTP_PARAMETERS_METADATA_KEY, apiClass) || {};
47
+ return [...(all[methodName] ?? [])];
48
+ }
49
+ //# sourceMappingURL=http-parameter-decorators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-parameter-decorators.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/http-parameter-decorators.ts"],"names":[],"mappings":";;;AA2CA,8BAEC;AAID,gCAEC;AAID,oEAOC;AA9DD,4BAA0B;AAC1B,iDAA+E;AAElE,QAAA,4BAA4B,GAAG,2BAA2B,CAAC;AAExE,yEAAyE;AACzE,2GAA2G;AAC3G,SAAS,mBAAmB,CAAC,MAA2B,EAAE,QAAgB;IACtE,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CACX,IAAI,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,kCAAkC,CACvF,CAAC;IACN,CAAC;IACD,OAAO;IACH,4FAA4F;IAC5F,MAAW,EACX,WAAwC,EACxC,cAAsB,EAClB,EAAE;QACN,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAClE,CAAC;QACD,MAAM,cAAc,GAAG,OAAO,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC;QAClF,MAAM,GAAG,GACL,OAAO,CAAC,WAAW,CAAC,oCAA4B,EAAE,cAAc,CAAC,IAAI,EAAE,CAAC;QAC5E,MAAM,YAAY,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,WAAqB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAC7D,IACI,YAAY,CAAC,IAAI,CACb,CAAC,WAAqC,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,KAAK,cAAc,CAClF,EACH,CAAC;YACC,MAAM,IAAI,KAAK,CACX,YAAY,cAAc,OAAO,cAAc,CAAC,IAAI,IAAI,MAAM,CAAC,WAAW,CAAC,2CAA2C,CACzH,CAAC;QACN,CAAC;QACD,YAAY,CAAC,IAAI,CAAC,IAAI,uCAAwB,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;QAClF,GAAG,CAAC,WAAqB,CAAC,GAAG,YAAY,CAAC;QAC1C,OAAO,CAAC,cAAc,CAAC,oCAA4B,EAAE,GAAG,EAAE,cAAc,CAAC,CAAC;IAC9E,CAAC,CAAC;AACN,CAAC;AAED,8EAA8E;AAC9E,2GAA2G;AAC3G,SAAgB,SAAS,CAAC,QAAgB;IACtC,OAAO,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AACjD,CAAC;AAED,gEAAgE;AAChE,2GAA2G;AAC3G,SAAgB,UAAU,CAAC,QAAgB;IACvC,OAAO,mBAAmB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAClD,CAAC;AAED,gGAAgG;AAChG,yEAAyE;AACzE,SAAgB,4BAA4B,CACxC,QAAkB,EAClB,UAAkB;IAElB,MAAM,GAAG,GACL,OAAO,CAAC,WAAW,CAAC,oCAA4B,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;IACtE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AACxC,CAAC","sourcesContent":["import 'reflect-metadata';\nimport { HttpParameterDeclaration, HttpParameterSource } from './HttpContract';\n\nexport const HTTP_PARAMETERS_METADATA_KEY = 'webpieces:http-parameters';\n\n/** Shared implementation for explicit method-parameter wire mappings. */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nfunction defineHttpParameter(source: HttpParameterSource, wireName: string): ParameterDecorator {\n if (typeof wireName !== 'string' || wireName.trim() === '') {\n throw new Error(\n `@${source === 'path' ? 'PathParam' : 'QueryParam'} requires a non-empty wire name.`,\n );\n }\n return (\n // webpieces-disable no-any-unknown -- reflect-metadata parameter decorator API requires any\n target: any,\n propertyKey: string | symbol | undefined,\n parameterIndex: number,\n ): void => {\n if (propertyKey === undefined) {\n throw new Error('HTTP parameter decorators are method-only.');\n }\n const metadataTarget = typeof target === 'function' ? target : target.constructor;\n const all: Record<string, HttpParameterDeclaration[]> =\n Reflect.getMetadata(HTTP_PARAMETERS_METADATA_KEY, metadataTarget) || {};\n const declarations = [...(all[propertyKey as string] ?? [])];\n if (\n declarations.some(\n (declaration: HttpParameterDeclaration) => declaration.index === parameterIndex,\n )\n ) {\n throw new Error(\n `Argument ${parameterIndex} of ${metadataTarget.name}.${String(propertyKey)} already has an HTTP parameter decorator.`,\n );\n }\n declarations.push(new HttpParameterDeclaration(parameterIndex, source, wireName));\n all[propertyKey as string] = declarations;\n Reflect.defineMetadata(HTTP_PARAMETERS_METADATA_KEY, all, metadataTarget);\n };\n}\n\n/** Map one method argument to a `{placeholder}` in `@ApiPath + @Endpoint`. */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function PathParam(wireName: string): ParameterDecorator {\n return defineHttpParameter('path', wireName);\n}\n\n/** Map one method argument to an explicitly named query key. */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function QueryParam(wireName: string): ParameterDecorator {\n return defineHttpParameter('query', wireName);\n}\n\n/** Explicit path/query mappings for one endpoint, independent of JavaScript parameter names. */\n// webpieces-disable no-function-outside-class -- reflect-metadata reader\nexport function getHttpParameterDeclarations(\n apiClass: Function,\n methodName: string,\n): HttpParameterDeclaration[] {\n const all: Record<string, HttpParameterDeclaration[]> =\n Reflect.getMetadata(HTTP_PARAMETERS_METADATA_KEY, apiClass) || {};\n return [...(all[methodName] ?? [])];\n}\n"]}
package/src/index.d.ts CHANGED
@@ -18,11 +18,15 @@ export { ConsoleLoggerFactory } from './logging/ConsoleLoggerFactory';
18
18
  export { LogManager } from './logging/LogManager';
19
19
  export { LogChunker, LogChunkerImpl, LogChunkInfo, MAX_GCP_LOG_BYTES, GCP_LOG_BUDGET_BYTES, } from './logging/LogChunker';
20
20
  export { ApiPath, Endpoint, WpAuthPublic, WpAuthJwt, rolesRequired, MISSING_AUTH_DECORATOR_FIX, WpAuthOidc, WpAuthSharedSecret, WpAuthWebhook, WpAuthApiKey, WpAuthLocalOnly, MaskLog, getApiPath, getEndpoints, getEndpointOptions, getEndpointKind, getEndpointKinds, getMaskSpec, isFormPost, isRawBody, isApiPath, getAuthMeta, getAuthMode, assertEveryEndpointHasAuthMode, assertEveryExternalEndpointDeclaresCaller, assertEveryWebhookEndpointRetainsRawBody, validateNoConflictingDecorators, METADATA_KEYS, } from './http/decorators';
21
+ export { PathParam, QueryParam, getHttpParameterDeclarations, } from './http/http-parameter-decorators';
21
22
  export { WpInternal, WpIpcEndpoint, isInternalApi, getIpcApiId, getIpcEndpoints, getIpcEndpointKind, getIpcMaskSpec, assertInternalApi, assertNotInternalApi, IPC_METADATA_KEYS, } from './ipc/IpcDecorators';
22
23
  export type { IpcApiType, IpcEndpointKind, IpcEndpointOptions } from './ipc/IpcDecorators';
23
24
  export { RouteMetadata } from './http/RouteMetadata';
25
+ export { RouteMetadataFactory } from './http/RouteMetadataFactory';
26
+ export { BoundHttpRequest, HttpContractMapper, HttpParameterBinding, HttpParameterDeclaration, } from './http/HttpContract';
27
+ export type { ContractHttpMethod, EndpointResponseType, HttpParameterSource, HttpParameterValueType, } from './http/HttpContract';
24
28
  export type { EndpointKind, EndpointOptions, ExternalEndpointOptions } from './http/decorators';
25
- export { WpMcpTool, WpMcpToolHints, WpMcpToolMetadata, getWpMcpTools, } from './mcp/McpMetadata';
29
+ export { WpMcpTool, WpMcpToolHints, WpMcpToolMetadata, getWpMcpTools } from './mcp/McpMetadata';
26
30
  export type { WpMcpMethodDecorator, WpMcpToolOptions } from './mcp/McpMetadata';
27
31
  export { WpDto, WpDtoField, WpDtoFieldOptions, WpDtoFieldMetadata, WpResponseDto, ApiJsonSchema, DtoValidationFailure, DtoSchemaBuilder, } from './mcp/DtoSchema';
28
32
  export type { DtoClass, DtoArrayItem, DtoValue } from './mcp/DtoSchema';
package/src/index.js CHANGED
@@ -8,9 +8,10 @@
8
8
  * @packageDocumentation
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.assertInternalApi = exports.getIpcMaskSpec = exports.getIpcEndpointKind = exports.getIpcEndpoints = exports.getIpcApiId = exports.isInternalApi = exports.WpIpcEndpoint = exports.WpInternal = 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.getEndpointOptions = 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.ApiCallTimeoutError = exports.CallContext = exports.CallDeadline = exports.CallRegistry = exports.ClientRegistry = exports.HeaderRegistry = exports.DateTimeUtil = exports.TimeUtil = 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.DtoSchemaBuilder = exports.DtoValidationFailure = exports.ApiJsonSchema = exports.WpResponseDto = exports.WpDtoFieldMetadata = exports.WpDtoFieldOptions = exports.WpDtoField = exports.WpDto = exports.getWpMcpTools = exports.WpMcpToolMetadata = exports.WpMcpToolHints = exports.WpMcpTool = exports.RouteMetadata = exports.IPC_METADATA_KEYS = exports.assertNotInternalApi = void 0;
13
- exports.ApiErrorHttpStatus = exports.ApiErrorPayload = exports.ApiErrorCodec = exports.ApiEndpointNotFoundError = exports.ApiConnectionError = exports.ApiDependencyBackoffError = exports.ApiUnauthorizedError = exports.ApiBadRequestError = exports.ApiEndUserError = exports.ApiRequestTimeoutError = exports.ApiForbiddenError = exports.ApiRateLimitedError = exports.ApiImplementationError = exports.ApiDependencyTimeoutError = exports.ApiUnavailableError = 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.HttpResponseDto = exports.HttpResponseStatus = exports.HttpHeader = exports.RuntimeLocality = exports.ServiceInfo = void 0;
11
+ exports.getIpcEndpoints = exports.getIpcApiId = exports.isInternalApi = exports.WpIpcEndpoint = exports.WpInternal = 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.getEndpointOptions = 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.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.DtoSchemaBuilder = exports.DtoValidationFailure = exports.ApiJsonSchema = exports.WpResponseDto = exports.WpDtoFieldMetadata = exports.WpDtoFieldOptions = exports.WpDtoField = exports.WpDto = exports.getWpMcpTools = exports.WpMcpToolMetadata = exports.WpMcpToolHints = exports.WpMcpTool = exports.HttpParameterDeclaration = exports.HttpParameterBinding = exports.HttpContractMapper = exports.BoundHttpRequest = exports.RouteMetadataFactory = exports.RouteMetadata = exports.IPC_METADATA_KEYS = exports.assertNotInternalApi = exports.assertInternalApi = exports.getIpcMaskSpec = exports.getIpcEndpointKind = void 0;
13
+ exports.ApiUnauthorizedError = exports.ApiBadRequestError = exports.ApiEndUserError = exports.ApiRequestTimeoutError = exports.ApiForbiddenError = exports.ApiRateLimitedError = exports.ApiImplementationError = exports.ApiDependencyTimeoutError = exports.ApiUnavailableError = 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.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;
14
+ exports.ApiErrorHttpStatus = exports.ApiErrorPayload = exports.ApiErrorCodec = exports.ApiEndpointNotFoundError = exports.ApiConnectionError = exports.ApiDependencyBackoffError = void 0;
14
15
  var errorUtils_1 = require("./lib/errorUtils");
15
16
  Object.defineProperty(exports, "toError", { enumerable: true, get: function () { return errorUtils_1.toError; } });
16
17
  var ContextKey_1 = require("./ContextKey");
@@ -72,6 +73,10 @@ Object.defineProperty(exports, "assertEveryExternalEndpointDeclaresCaller", { en
72
73
  Object.defineProperty(exports, "assertEveryWebhookEndpointRetainsRawBody", { enumerable: true, get: function () { return decorators_1.assertEveryWebhookEndpointRetainsRawBody; } });
73
74
  Object.defineProperty(exports, "validateNoConflictingDecorators", { enumerable: true, get: function () { return decorators_1.validateNoConflictingDecorators; } });
74
75
  Object.defineProperty(exports, "METADATA_KEYS", { enumerable: true, get: function () { return decorators_1.METADATA_KEYS; } });
76
+ var http_parameter_decorators_1 = require("./http/http-parameter-decorators");
77
+ Object.defineProperty(exports, "PathParam", { enumerable: true, get: function () { return http_parameter_decorators_1.PathParam; } });
78
+ Object.defineProperty(exports, "QueryParam", { enumerable: true, get: function () { return http_parameter_decorators_1.QueryParam; } });
79
+ Object.defineProperty(exports, "getHttpParameterDeclarations", { enumerable: true, get: function () { return http_parameter_decorators_1.getHttpParameterDeclarations; } });
75
80
  var IpcDecorators_1 = require("./ipc/IpcDecorators");
76
81
  Object.defineProperty(exports, "WpInternal", { enumerable: true, get: function () { return IpcDecorators_1.WpInternal; } });
77
82
  Object.defineProperty(exports, "WpIpcEndpoint", { enumerable: true, get: function () { return IpcDecorators_1.WpIpcEndpoint; } });
@@ -86,6 +91,13 @@ Object.defineProperty(exports, "IPC_METADATA_KEYS", { enumerable: true, get: fun
86
91
  // The runtime representation of ONE route (split out of decorators.ts for file size only).
87
92
  var RouteMetadata_1 = require("./http/RouteMetadata");
88
93
  Object.defineProperty(exports, "RouteMetadata", { enumerable: true, get: function () { return RouteMetadata_1.RouteMetadata; } });
94
+ var RouteMetadataFactory_1 = require("./http/RouteMetadataFactory");
95
+ Object.defineProperty(exports, "RouteMetadataFactory", { enumerable: true, get: function () { return RouteMetadataFactory_1.RouteMetadataFactory; } });
96
+ var HttpContract_1 = require("./http/HttpContract");
97
+ Object.defineProperty(exports, "BoundHttpRequest", { enumerable: true, get: function () { return HttpContract_1.BoundHttpRequest; } });
98
+ Object.defineProperty(exports, "HttpContractMapper", { enumerable: true, get: function () { return HttpContract_1.HttpContractMapper; } });
99
+ Object.defineProperty(exports, "HttpParameterBinding", { enumerable: true, get: function () { return HttpContract_1.HttpParameterBinding; } });
100
+ Object.defineProperty(exports, "HttpParameterDeclaration", { enumerable: true, get: function () { return HttpContract_1.HttpParameterDeclaration; } });
89
101
  var McpMetadata_1 = require("./mcp/McpMetadata");
90
102
  Object.defineProperty(exports, "WpMcpTool", { enumerable: true, get: function () { return McpMetadata_1.WpMcpTool; } });
91
103
  Object.defineProperty(exports, "WpMcpToolHints", { enumerable: true, get: function () { return McpMetadata_1.WpMcpToolHints; } });
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 getEndpointOptions,\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 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 type { EndpointKind, EndpointOptions, ExternalEndpointOptions } from './http/decorators';\nexport {\n WpMcpTool,\n WpMcpToolHints,\n WpMcpToolMetadata,\n getWpMcpTools,\n} from './mcp/McpMetadata';\nexport type { WpMcpMethodDecorator, WpMcpToolOptions } from './mcp/McpMetadata';\nexport {\n WpDto,\n WpDtoField,\n WpDtoFieldOptions,\n WpDtoFieldMetadata,\n WpResponseDto,\n ApiJsonSchema,\n DtoValidationFailure,\n DtoSchemaBuilder,\n} from './mcp/DtoSchema';\nexport type { DtoClass, DtoArrayItem, DtoValue } from './mcp/DtoSchema';\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 @Endpoint(..., '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 ErrorTranslators 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 { ErrorTranslators } from './http/ErrorTranslators';\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';\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 { ApiType, 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 ApiUnavailableError,\n ApiDependencyTimeoutError,\n ApiImplementationError,\n ApiRateLimitedError,\n ApiForbiddenError,\n ApiRequestTimeoutError,\n ApiEndUserError,\n ApiBadRequestError,\n ApiUnauthorizedError,\n ApiDependencyBackoffError,\n ApiConnectionError,\n ApiEndpointNotFoundError,\n ApiErrorCodec,\n ApiErrorPayload,\n} from './errors';\nexport type { ApiErrorKind } from './errors';\nexport { ApiErrorHttpStatus } 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 getEndpointOptions,\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 {\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 type {\n ContractHttpMethod,\n EndpointResponseType,\n HttpParameterSource,\n HttpParameterValueType,\n} from './http/HttpContract';\nexport type { EndpointKind, EndpointOptions, ExternalEndpointOptions } from './http/decorators';\nexport { WpMcpTool, WpMcpToolHints, WpMcpToolMetadata, getWpMcpTools } from './mcp/McpMetadata';\nexport type { WpMcpMethodDecorator, WpMcpToolOptions } from './mcp/McpMetadata';\nexport {\n WpDto,\n WpDtoField,\n WpDtoFieldOptions,\n WpDtoFieldMetadata,\n WpResponseDto,\n ApiJsonSchema,\n DtoValidationFailure,\n DtoSchemaBuilder,\n} from './mcp/DtoSchema';\nexport type { DtoClass, DtoArrayItem, DtoValue } from './mcp/DtoSchema';\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 @Endpoint(..., '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 ErrorTranslators 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 { ErrorTranslators } from './http/ErrorTranslators';\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';\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 { ApiType, 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 ApiUnavailableError,\n ApiDependencyTimeoutError,\n ApiImplementationError,\n ApiRateLimitedError,\n ApiForbiddenError,\n ApiRequestTimeoutError,\n ApiEndUserError,\n ApiBadRequestError,\n ApiUnauthorizedError,\n ApiDependencyBackoffError,\n ApiConnectionError,\n ApiEndpointNotFoundError,\n ApiErrorCodec,\n ApiErrorPayload,\n} from './errors';\nexport type { ApiErrorKind } from './errors';\nexport { ApiErrorHttpStatus } from './http/ApiErrorHttpStatus';\n"]}