@webpieces/core-util 0.4.487 → 0.4.489

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.487",
3
+ "version": "0.4.489",
4
4
  "description": "Utility functions for WebPieces - works in browser and Node.js",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -14,9 +14,29 @@ export declare const METADATA_KEYS: {
14
14
  QUEUE_OVERRIDE: string;
15
15
  /** Per-method @Endpoint options (e.g. formPost), parallel to ENDPOINTS. */
16
16
  ENDPOINT_OPTIONS: string;
17
+ /** Per-method @Endpoint trigger kind (rpc | cloudtasks | cron | external), parallel to ENDPOINTS. */
18
+ ENDPOINT_KIND: string;
17
19
  /** Per-method @MaskLog spec (which DTO fields the LogApiCall path masks). */
18
20
  MASK_LOG: string;
19
21
  };
22
+ /**
23
+ * WHAT TRIGGERS an endpoint at runtime — the single fact that decides how the runtime architecture
24
+ * graph draws it, and which Terraform resource must exist for it to ever fire:
25
+ *
26
+ * - `rpc` — a caller in this repo (or a browser) calls it synchronously. A direct arrow.
27
+ * - `cloudtasks` — a producer ENQUEUES it; Cloud Tasks delivers it later. Drawn producer → queue →
28
+ * consumer, one queue node per METHOD (see {@link Queue}). Producer and consumer
29
+ * being the SAME service is legal and common — the queue decouples them.
30
+ * - `cron` — a scheduler fires it on a clock. Nothing in-repo calls it; drawn hanging off a
31
+ * clock symbol. Backed by a Cloud Scheduler job.
32
+ * - `external` — a system OUTSIDE this repo drives it (a GCP Pub/Sub push subscription, a Twilio
33
+ * or Gmail webhook). Drawn as an inbound dashed arrow from that system.
34
+ *
35
+ * Declared PER METHOD, because one api class routinely mixes them: an admin contract can have
36
+ * caller-driven endpoints AND a nightly cron sweep. A class-level marker cannot express that, which
37
+ * is exactly why the graph could not tell these apart before.
38
+ */
39
+ export type EndpointKind = 'rpc' | 'cloudtasks' | 'cron' | 'external';
20
40
  /**
21
41
  * Options for a single @Endpoint. Kept in a metadata map PARALLEL to ENDPOINTS so the existing
22
42
  * `Record<methodName, path>` shape every consumer iterates stays unchanged.
@@ -114,31 +134,45 @@ export declare class AuthMeta {
114
134
  * @Authentication({authenticated: true})
115
135
  * @ApiPath('/api/save')
116
136
  * abstract class SaveApi {
117
- * @Endpoint('/item')
137
+ * @Endpoint('/item', 'rpc')
118
138
  * save(request: SaveRequest): Promise<SaveResponse> { ... }
119
139
  * }
120
140
  * ```
121
141
  */
122
142
  export declare function ApiPath(basePath: string): ClassDecorator;
123
143
  /**
124
- * @Endpoint(path, options?) - Method decorator that registers a POST endpoint at the given path.
144
+ * @Endpoint(path, kind, options?) - Method decorator that registers a POST endpoint at the given
145
+ * path and declares WHAT TRIGGERS it.
125
146
  *
126
147
  * All endpoints are POST-only (matching gRPC/thrift style).
127
148
  *
128
149
  * Usage:
129
150
  * ```typescript
130
- * @Endpoint('/item')
151
+ * @Endpoint('/item', 'rpc')
131
152
  * save(request: SaveRequest): Promise<SaveResponse> { ... }
132
153
  *
154
+ * // enqueued by a producer, delivered later by Cloud Tasks:
155
+ * @Endpoint('/send', 'cloudtasks')
156
+ * send(request: SendRequest): Promise<void> { ... }
157
+ *
158
+ * // fired by Cloud Scheduler on a clock, called by nobody in this repo:
159
+ * @Endpoint('/nightly', 'cron')
160
+ * nightly(request: NightlyRequest): Promise<void> { ... }
161
+ *
133
162
  * // EXTERNAL webhook posting application/x-www-form-urlencoded (e.g. Twilio):
134
- * @Endpoint('/hook', { formPost: true })
163
+ * @Endpoint('/hook', 'external', { formPost: true })
135
164
  * inbound(request: InboundRequest): Promise<InboundResponse> { ... }
136
165
  * ```
137
166
  *
138
- * The path write to ENDPOINTS is UNCHANGED (every consumer iterates `[methodName, path]`); options
139
- * ride a PARALLEL ENDPOINT_OPTIONS map so nothing downstream changes shape.
167
+ * `kind` is REQUIRED and deliberately positional: it makes every pre-existing single-argument
168
+ * `@Endpoint('/x')` a COMPILE error rather than something a lint rule has to chase, so no endpoint
169
+ * can slip into the runtime architecture graph with its trigger left to guesswork. See
170
+ * {@link EndpointKind} for what each value draws and which Terraform resource backs it.
171
+ *
172
+ * The path write to ENDPOINTS is UNCHANGED (every consumer iterates `[methodName, path]`); the kind
173
+ * and options ride PARALLEL ENDPOINT_KIND / ENDPOINT_OPTIONS maps so nothing downstream changes shape.
140
174
  */
141
- export declare function Endpoint(path: string, options?: EndpointOptions): MethodDecorator;
175
+ export declare function Endpoint(path: string, kind: EndpointKind, options?: EndpointOptions): MethodDecorator;
142
176
  /**
143
177
  * @MaskLog(fields) - declare which fields of THIS method's request/response DTOs the
144
178
  * {@link LogApiCall} logging path must mask, so a secret riding on a DTO (an OAuth refresh token, an
@@ -146,7 +180,7 @@ export declare function Endpoint(path: string, options?: EndpointOptions): Metho
146
180
  * untouched — masking lives in the logging path only.
147
181
  *
148
182
  * ```typescript
149
- * @Endpoint('/account')
183
+ * @Endpoint('/account', 'rpc')
150
184
  * @MaskLog({ refreshToken: 'full', accessToken: 'last4', credential: 'full' })
151
185
  * getEmailAccount(request: GetEmailAccountRequest): Promise<GetEmailAccountResponse> { ... }
152
186
  * ```
@@ -226,6 +260,20 @@ export declare function getApiPath(apiClass: Function): string | undefined;
226
260
  * Returns a record of methodName -> endpoint path.
227
261
  */
228
262
  export declare function getEndpoints(apiClass: Function): Record<string, string> | undefined;
263
+ /**
264
+ * Every method's declared trigger kind, as `methodName -> kind`. Parallel to {@link getEndpoints}.
265
+ * Empty for a class carrying no @Endpoint at all.
266
+ */
267
+ export declare function getEndpointKinds(apiClass: Function): Record<string, EndpointKind>;
268
+ /**
269
+ * What triggers ONE method, or undefined when the method carries no @Endpoint.
270
+ *
271
+ * Defaults to nothing rather than to 'rpc': `kind` is a required argument, so a missing entry means
272
+ * "this is not an endpoint", never "an endpoint that forgot to say". Silently defaulting here would
273
+ * put an undeclared cron or webhook back into the graph as a normal rpc call — the exact blindness
274
+ * the required argument exists to remove.
275
+ */
276
+ export declare function getEndpointKind(apiClass: Function, methodName: string): EndpointKind | undefined;
229
277
  /**
230
278
  * Get the @Endpoint options for one method (empty object if the method had no options).
231
279
  */
@@ -289,9 +337,19 @@ export declare function getApiKind(apiClass: Function): ApiKind;
289
337
  */
290
338
  export declare function assertApiKind(apiClass: Function, expected: ApiKind): void;
291
339
  /**
292
- * Validate @PubSub conventions at wiring time: the class must be @ApiPath + @PubSub
293
- * and declare at least one endpoint. (Return-type is Promise<void>, a compile-time
294
- * contract — TS erases types at runtime so it cannot be re-checked here.)
340
+ * Which {@link EndpointKind}s each {@link ApiKind} may declare. A @PubSub contract is delivered
341
+ * asynchronously by definition, so `rpc` is meaningless on it; an @Rpc contract has no queue, so
342
+ * `cloudtasks`/`cron` on it would name a queue/schedule nothing could ever deliver to. `external`
343
+ * is legal on both — a webhook posts synchronously, a push subscription does not.
344
+ *
345
+ * Shared so the wiring-time assert below and the build-time architecture scan enforce ONE rule.
346
+ */
347
+ export declare const ENDPOINT_KINDS_BY_API_KIND: Record<ApiKind, readonly EndpointKind[]>;
348
+ /**
349
+ * Validate @PubSub conventions at wiring time: the class must be @ApiPath + @PubSub, declare at
350
+ * least one endpoint, and every endpoint must declare a kind this api kind can actually deliver.
351
+ * (Return-type is Promise<void>, a compile-time contract — TS erases types at runtime so it cannot
352
+ * be re-checked here.)
295
353
  * @throws Error if conventions are violated.
296
354
  */
297
355
  export declare function assertPubSubConventions(apiClass: Function): void;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AuthenticationConfig = exports.AuthMeta = exports.RouteMetadata = exports.METADATA_KEYS = void 0;
3
+ exports.ENDPOINT_KINDS_BY_API_KIND = exports.AuthenticationConfig = exports.AuthMeta = exports.RouteMetadata = exports.METADATA_KEYS = void 0;
4
4
  exports.ApiPath = ApiPath;
5
5
  exports.Endpoint = Endpoint;
6
6
  exports.MaskLog = MaskLog;
@@ -13,6 +13,8 @@ exports.AuthOidc = AuthOidc;
13
13
  exports.AuthSharedSecret = AuthSharedSecret;
14
14
  exports.getApiPath = getApiPath;
15
15
  exports.getEndpoints = getEndpoints;
16
+ exports.getEndpointKinds = getEndpointKinds;
17
+ exports.getEndpointKind = getEndpointKind;
16
18
  exports.getEndpointOptions = getEndpointOptions;
17
19
  exports.isFormPost = isFormPost;
18
20
  exports.isApiPath = isApiPath;
@@ -43,6 +45,8 @@ exports.METADATA_KEYS = {
43
45
  QUEUE_OVERRIDE: 'webpieces:queue-override',
44
46
  /** Per-method @Endpoint options (e.g. formPost), parallel to ENDPOINTS. */
45
47
  ENDPOINT_OPTIONS: 'webpieces:endpoint-options',
48
+ /** Per-method @Endpoint trigger kind (rpc | cloudtasks | cron | external), parallel to ENDPOINTS. */
49
+ ENDPOINT_KIND: 'webpieces:endpoint-kind',
46
50
  /** Per-method @MaskLog spec (which DTO fields the LogApiCall path masks). */
47
51
  MASK_LOG: 'webpieces:mask-log',
48
52
  };
@@ -116,7 +120,7 @@ exports.AuthMeta = AuthMeta;
116
120
  * @Authentication({authenticated: true})
117
121
  * @ApiPath('/api/save')
118
122
  * abstract class SaveApi {
119
- * @Endpoint('/item')
123
+ * @Endpoint('/item', 'rpc')
120
124
  * save(request: SaveRequest): Promise<SaveResponse> { ... }
121
125
  * }
122
126
  * ```
@@ -132,31 +136,48 @@ function ApiPath(basePath) {
132
136
  };
133
137
  }
134
138
  /**
135
- * @Endpoint(path, options?) - Method decorator that registers a POST endpoint at the given path.
139
+ * @Endpoint(path, kind, options?) - Method decorator that registers a POST endpoint at the given
140
+ * path and declares WHAT TRIGGERS it.
136
141
  *
137
142
  * All endpoints are POST-only (matching gRPC/thrift style).
138
143
  *
139
144
  * Usage:
140
145
  * ```typescript
141
- * @Endpoint('/item')
146
+ * @Endpoint('/item', 'rpc')
142
147
  * save(request: SaveRequest): Promise<SaveResponse> { ... }
143
148
  *
149
+ * // enqueued by a producer, delivered later by Cloud Tasks:
150
+ * @Endpoint('/send', 'cloudtasks')
151
+ * send(request: SendRequest): Promise<void> { ... }
152
+ *
153
+ * // fired by Cloud Scheduler on a clock, called by nobody in this repo:
154
+ * @Endpoint('/nightly', 'cron')
155
+ * nightly(request: NightlyRequest): Promise<void> { ... }
156
+ *
144
157
  * // EXTERNAL webhook posting application/x-www-form-urlencoded (e.g. Twilio):
145
- * @Endpoint('/hook', { formPost: true })
158
+ * @Endpoint('/hook', 'external', { formPost: true })
146
159
  * inbound(request: InboundRequest): Promise<InboundResponse> { ... }
147
160
  * ```
148
161
  *
149
- * The path write to ENDPOINTS is UNCHANGED (every consumer iterates `[methodName, path]`); options
150
- * ride a PARALLEL ENDPOINT_OPTIONS map so nothing downstream changes shape.
162
+ * `kind` is REQUIRED and deliberately positional: it makes every pre-existing single-argument
163
+ * `@Endpoint('/x')` a COMPILE error rather than something a lint rule has to chase, so no endpoint
164
+ * can slip into the runtime architecture graph with its trigger left to guesswork. See
165
+ * {@link EndpointKind} for what each value draws and which Terraform resource backs it.
166
+ *
167
+ * The path write to ENDPOINTS is UNCHANGED (every consumer iterates `[methodName, path]`); the kind
168
+ * and options ride PARALLEL ENDPOINT_KIND / ENDPOINT_OPTIONS maps so nothing downstream changes shape.
151
169
  */
152
170
  // webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope
153
- function Endpoint(path, options = {}) {
171
+ function Endpoint(path, kind, options = {}) {
154
172
  // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any
155
173
  return (target, propertyKey, _descriptor) => {
156
174
  const metadataTarget = typeof target === 'function' ? target : target.constructor;
157
175
  const endpoints = Reflect.getMetadata(exports.METADATA_KEYS.ENDPOINTS, metadataTarget) || {};
158
176
  endpoints[propertyKey] = path;
159
177
  Reflect.defineMetadata(exports.METADATA_KEYS.ENDPOINTS, endpoints, metadataTarget);
178
+ const kinds = Reflect.getMetadata(exports.METADATA_KEYS.ENDPOINT_KIND, metadataTarget) || {};
179
+ kinds[propertyKey] = kind;
180
+ Reflect.defineMetadata(exports.METADATA_KEYS.ENDPOINT_KIND, kinds, metadataTarget);
160
181
  const opts = Reflect.getMetadata(exports.METADATA_KEYS.ENDPOINT_OPTIONS, metadataTarget) || {};
161
182
  opts[propertyKey] = options;
162
183
  Reflect.defineMetadata(exports.METADATA_KEYS.ENDPOINT_OPTIONS, opts, metadataTarget);
@@ -169,7 +190,7 @@ function Endpoint(path, options = {}) {
169
190
  * untouched — masking lives in the logging path only.
170
191
  *
171
192
  * ```typescript
172
- * @Endpoint('/account')
193
+ * @Endpoint('/account', 'rpc')
173
194
  * @MaskLog({ refreshToken: 'full', accessToken: 'last4', credential: 'full' })
174
195
  * getEmailAccount(request: GetEmailAccountRequest): Promise<GetEmailAccountResponse> { ... }
175
196
  * ```
@@ -316,6 +337,26 @@ function getApiPath(apiClass) {
316
337
  function getEndpoints(apiClass) {
317
338
  return Reflect.getMetadata(exports.METADATA_KEYS.ENDPOINTS, apiClass);
318
339
  }
340
+ /**
341
+ * Every method's declared trigger kind, as `methodName -> kind`. Parallel to {@link getEndpoints}.
342
+ * Empty for a class carrying no @Endpoint at all.
343
+ */
344
+ // webpieces-disable no-function-outside-class -- reflect-metadata reader, sibling of getEndpoints
345
+ function getEndpointKinds(apiClass) {
346
+ return Reflect.getMetadata(exports.METADATA_KEYS.ENDPOINT_KIND, apiClass) || {};
347
+ }
348
+ /**
349
+ * What triggers ONE method, or undefined when the method carries no @Endpoint.
350
+ *
351
+ * Defaults to nothing rather than to 'rpc': `kind` is a required argument, so a missing entry means
352
+ * "this is not an endpoint", never "an endpoint that forgot to say". Silently defaulting here would
353
+ * put an undeclared cron or webhook back into the graph as a normal rpc call — the exact blindness
354
+ * the required argument exists to remove.
355
+ */
356
+ // webpieces-disable no-function-outside-class -- reflect-metadata reader, sibling of getEndpoints
357
+ function getEndpointKind(apiClass, methodName) {
358
+ return getEndpointKinds(apiClass)[methodName];
359
+ }
319
360
  /**
320
361
  * Get the @Endpoint options for one method (empty object if the method had no options).
321
362
  */
@@ -431,9 +472,22 @@ function assertApiKind(apiClass, expected) {
431
472
  }
432
473
  }
433
474
  /**
434
- * Validate @PubSub conventions at wiring time: the class must be @ApiPath + @PubSub
435
- * and declare at least one endpoint. (Return-type is Promise<void>, a compile-time
436
- * contract — TS erases types at runtime so it cannot be re-checked here.)
475
+ * Which {@link EndpointKind}s each {@link ApiKind} may declare. A @PubSub contract is delivered
476
+ * asynchronously by definition, so `rpc` is meaningless on it; an @Rpc contract has no queue, so
477
+ * `cloudtasks`/`cron` on it would name a queue/schedule nothing could ever deliver to. `external`
478
+ * is legal on both — a webhook posts synchronously, a push subscription does not.
479
+ *
480
+ * Shared so the wiring-time assert below and the build-time architecture scan enforce ONE rule.
481
+ */
482
+ exports.ENDPOINT_KINDS_BY_API_KIND = {
483
+ rpc: ['rpc', 'external'],
484
+ pubsub: ['cloudtasks', 'cron', 'external'],
485
+ };
486
+ /**
487
+ * Validate @PubSub conventions at wiring time: the class must be @ApiPath + @PubSub, declare at
488
+ * least one endpoint, and every endpoint must declare a kind this api kind can actually deliver.
489
+ * (Return-type is Promise<void>, a compile-time contract — TS erases types at runtime so it cannot
490
+ * be re-checked here.)
437
491
  * @throws Error if conventions are violated.
438
492
  */
439
493
  function assertPubSubConventions(apiClass) {
@@ -446,6 +500,15 @@ function assertPubSubConventions(apiClass) {
446
500
  if (Object.keys(endpoints).length === 0) {
447
501
  throw new Error(`@PubSub API ${apiName} declares no @Endpoint methods`);
448
502
  }
503
+ const allowed = exports.ENDPOINT_KINDS_BY_API_KIND.pubsub;
504
+ const kinds = getEndpointKinds(apiClass);
505
+ for (const methodName of Object.keys(endpoints)) {
506
+ const kind = kinds[methodName];
507
+ if (kind !== undefined && allowed.includes(kind))
508
+ continue;
509
+ throw new Error(`@PubSub API ${apiName}.${methodName} declares @Endpoint(..., '${kind ?? 'missing'}') — a ` +
510
+ `@PubSub contract is delivered through a queue, so it must be one of: ${allowed.join(' | ')}.`);
511
+ }
449
512
  }
450
513
  /**
451
514
  * Resolve the Cloud Tasks queue name for a @PubSub method: the @Queue override if
@@ -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';\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 /** '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 @MaskLog spec (which DTO fields the LogApiCall path masks). */\n MASK_LOG: 'webpieces:mask-log',\n};\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/**\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 */\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 LogApiCall} 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 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 ) {\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 }\n}\n\n/**\n * The service-to-service / user auth mode of an endpoint. Discriminated union so\n * a filter can `switch (mode.kind)` and get the data it needs, exhaustively.\n *\n * - `public` → no auth check\n * - `jwt` → user-facing JWT (optionally role-gated), validated by the app AuthFilter\n * - `oidc` → Google OIDC service-to-service (Cloud Tasks delivery / cross-service RPC);\n * `callers` is the allow-list of caller service accounts ('self' = this service's SA)\n * - `shared-secret` → constant-time compare of a header against the secret bound for `secretKey`\n */\n/**\n * JwtRequirement - the endpoint's JWT authorization requirement, OPAQUE to the framework. The\n * default JwtHook.authorizeJwt enforces `roles` (any-of; empty = any authenticated user); apps\n * add their OWN fields (inOrg, tenant, feature, ...) via @Auth({...}) and override authorizeJwt to\n * enforce them. This is the pluggable seam: the framework authenticates, the app authorizes.\n */\nexport interface JwtRequirement {\n roles?: string[];\n // webpieces-disable no-any-unknown -- app-defined authorization fields (inOrg, tenant, ...)\n [field: string]: unknown;\n}\n\nexport type AuthMode =\n | { kind: 'public' }\n | { kind: 'jwt'; requirement: JwtRequirement }\n | { kind: 'oidc'; callers: string[] }\n | { kind: 'shared-secret'; secretKey: string };\n\n/**\n * Auth metadata attached to a class or method via one of the auth decorators\n * (@Public / @AuthJwt / @AuthOidc / @AuthSharedSecret) or the legacy @Authentication.\n *\n * Carries a discriminated {@link AuthMode}. The `authenticated`/`roles` getters are\n * kept for back-compat with readers that only understand the user-JWT model\n * (e.g. the example AuthFilter).\n */\nexport class AuthMeta {\n mode: AuthMode;\n\n constructor(mode: AuthMode) {\n this.mode = mode;\n }\n\n /** True for every non-public mode (jwt, oidc, shared-secret). */\n get authenticated(): boolean {\n return this.mode.kind !== 'public';\n }\n\n /** JWT roles, or empty for non-jwt modes (back-compat convenience over the requirement). */\n get roles(): string[] {\n return this.mode.kind === 'jwt' ? (this.mode.requirement.roles ?? []) : [];\n }\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 * @Authentication({authenticated: true})\n * @ApiPath('/api/save')\n * abstract class SaveApi {\n * @Endpoint('/item')\n * save(request: SaveRequest): Promise<SaveResponse> { ... }\n * }\n * ```\n */\nexport function ApiPath(basePath: string): ClassDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any) => {\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, options?) - Method decorator that registers a POST endpoint at the given path.\n *\n * All endpoints are POST-only (matching gRPC/thrift style).\n *\n * Usage:\n * ```typescript\n * @Endpoint('/item')\n * save(request: SaveRequest): Promise<SaveResponse> { ... }\n *\n * // EXTERNAL webhook posting application/x-www-form-urlencoded (e.g. Twilio):\n * @Endpoint('/hook', { formPost: true })\n * inbound(request: InboundRequest): Promise<InboundResponse> { ... }\n * ```\n *\n * The path write to ENDPOINTS is UNCHANGED (every consumer iterates `[methodName, path]`); options\n * ride a PARALLEL ENDPOINT_OPTIONS map so nothing downstream changes shape.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function Endpoint(path: string, options: EndpointOptions = {}): 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 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}\n\n/**\n * @MaskLog(fields) - declare which fields of THIS method's request/response DTOs the\n * {@link LogApiCall} 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')\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 * Authentication config passed to @Authentication() decorator.\n */\nexport class AuthenticationConfig {\n authenticated: boolean;\n roles?: string[];\n\n constructor(authenticated: boolean, roles?: string[]) {\n this.authenticated = authenticated;\n this.roles = roles;\n }\n}\n\n/**\n * @Authentication(config) - Class or method decorator for auth requirements.\n *\n * Single decorator replaces @Public/@Authenticated/@Roles:\n * - @Authentication({authenticated: false}) → public, no auth check\n * - @Authentication({authenticated: true}) → requires authentication\n * - @Authentication({authenticated: true, roles: ['admin']}) → requires auth + roles\n *\n * Class-level is required. Methods can override class-level.\n * Throws if authenticated=false but roles are specified (contradictory).\n */\nexport function Authentication(config: AuthenticationConfig): ClassDecorator & MethodDecorator {\n // Validate: can't be public with roles\n if (!config.authenticated && config.roles && config.roles.length > 0) {\n throw new Error(\n `Invalid @Authentication config: authenticated=false but roles=${JSON.stringify(config.roles)}. ` +\n `Cannot require roles on a public endpoint. Set authenticated=true or remove roles.`\n );\n }\n\n const mode: AuthMode = config.authenticated\n ? { kind: 'jwt', requirement: { roles: config.roles ?? [] } }\n : { kind: 'public' };\n return defineAuthMode(mode);\n}\n\n/**\n * Shared implementation for every auth decorator: stores an {@link AuthMeta} for\n * the given {@link AuthMode} at class- or method-level, rejecting a second auth\n * decorator on the same target.\n */\nfunction defineAuthMode(mode: AuthMode): ClassDecorator & 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 // Method decorator\n const metadataTarget = typeof target === 'function' ? target : target.constructor;\n validateNoConflictingDecorators(metadataTarget, propertyKey as string);\n Reflect.defineMetadata(METADATA_KEYS.AUTH_META, authMeta, metadataTarget, propertyKey);\n } else {\n // Class decorator\n validateNoConflictingDecorators(target, undefined);\n Reflect.defineMetadata(METADATA_KEYS.AUTH_META, authMeta, target);\n }\n };\n}\n\n/**\n * @Public() - endpoint requires no authentication. Class- or method-level.\n */\nexport function Public(): ClassDecorator & MethodDecorator {\n return defineAuthMode({ kind: 'public' });\n}\n\n/**\n * @AuthJwt(...roles) - user-facing JWT auth, optionally role-gated. The app-level\n * AuthFilter validates the token; roles=[] means \"any authenticated user\".\n */\nexport function AuthJwt(...roles: string[]): ClassDecorator & MethodDecorator {\n return defineAuthMode({ kind: 'jwt', requirement: { roles } });\n}\n\n/**\n * @Auth(requirement) - user-facing JWT auth with an APP-DEFINED authorization requirement beyond\n * roles, e.g. `@Auth({ inOrg: true })` or `@Auth({ roles: ['admin'], tenantScoped: true })`. The\n * framework authenticates the JWT (JwtHook.parseJwt), then hands `requirement` + the parsed\n * values to JwtHook.authorizeJwt — which the app overrides to enforce its own policy. This is\n * how clients plug in their own JWT security without touching the framework.\n */\nexport function Auth(requirement: JwtRequirement): ClassDecorator & MethodDecorator {\n return defineAuthMode({ kind: 'jwt', requirement });\n}\n\n/**\n * @AuthOidc(...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 (`@AuthOidc('svc-a')`) only when you want\n * an additional app-level allow-list as defense-in-depth.\n */\nexport function AuthOidc(...callers: string[]): ClassDecorator & MethodDecorator {\n return defineAuthMode({ kind: 'oidc', callers });\n}\n\n/**\n * @AuthSharedSecret(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 */\nexport function AuthSharedSecret(key: string): ClassDecorator & MethodDecorator {\n return defineAuthMode({ kind: 'shared-secret', secretKey: key });\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 * 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 * 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 * 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 auth metadata for a specific method, falling back to class-level auth.\n * Method-level auth takes precedence over class-level auth.\n */\nexport function getAuthMeta(apiClass: Function, methodName?: string): AuthMeta | undefined {\n // Check method-level first\n if (methodName) {\n const methodAuth = Reflect.getMetadata(METADATA_KEYS.AUTH_META, apiClass, methodName);\n if (methodAuth) {\n return methodAuth;\n }\n }\n\n // Fall back to class-level\n return Reflect.getMetadata(METADATA_KEYS.AUTH_META, apiClass);\n}\n\n/**\n * Get the auth mode for a method (falling back to class-level), or undefined.\n * Convenience wrapper over getAuthMeta for callers that only want the mode.\n */\nexport function getAuthMode(apiClass: Function, methodName?: string): AuthMode | undefined {\n return getAuthMeta(apiClass, methodName)?.mode;\n}\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 @Authentication/@Public/@Auth* decorator.\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 `Add @Public(), @AuthJwt(...), @AuthOidc(...) or @AuthSharedSecret(...) ` +\n `to the class or method.`,\n );\n }\n }\n}\n\n// ============================================================\n// API kind (RPC vs PubSub/Cloud Tasks) + queue naming\n// ============================================================\n\n/**\n * API kind. 'rpc' = synchronous request/response (http-client ↔ ApiRoutingFactory).\n * 'pubsub' = fire-and-forget cloud task; the enqueue client (cloudtasks-client)\n * schedules a Cloud Task that is later delivered to the SAME controller endpoint.\n */\nexport type ApiKind = 'rpc' | 'pubsub';\n\n/**\n * @Rpc() - marks an API class as synchronous request/response (the default kind).\n * Present mostly for symmetry/readability; an undecorated API is treated as 'rpc'.\n */\nexport function Rpc(): ClassDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any) => {\n Reflect.defineMetadata(METADATA_KEYS.API_KIND, 'rpc' as ApiKind, target);\n };\n}\n\n/**\n * @PubSub() - marks an API class as fire-and-forget over Cloud Tasks. Every method\n * MUST return Promise<void> (a compile-time contract on the abstract API). The\n * enqueue client and the controller share this one class, exactly like RPC.\n */\nexport function PubSub(): ClassDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any) => {\n Reflect.defineMetadata(METADATA_KEYS.API_KIND, 'pubsub' as ApiKind, target);\n };\n}\n\n/**\n * @Queue(name) - override the Cloud Tasks queue name for a @PubSub method. Default\n * (no decorator) is `${ApiClassName}-${methodName}`, matched 1:1 by Terraform.\n */\nexport function Queue(name: string): MethodDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any, propertyKey: string | symbol, _descriptor: PropertyDescriptor) => {\n const metadataTarget = typeof target === 'function' ? target : target.constructor;\n const overrides: Record<string, string> =\n Reflect.getMetadata(METADATA_KEYS.QUEUE_OVERRIDE, metadataTarget) || {};\n overrides[propertyKey as string] = name;\n Reflect.defineMetadata(METADATA_KEYS.QUEUE_OVERRIDE, overrides, metadataTarget);\n };\n}\n\n/**\n * Get the API kind. Defaults to 'rpc' when neither @Rpc nor @PubSub is present.\n */\nexport function getApiKind(apiClass: Function): ApiKind {\n return (Reflect.getMetadata(METADATA_KEYS.API_KIND, apiClass) as ApiKind) ?? 'rpc';\n}\n\n/**\n * Assert the API class is of the expected kind (used by the clients: the RPC\n * client rejects a @PubSub api and vice-versa).\n * @throws Error if the kind doesn't match.\n */\nexport function assertApiKind(apiClass: Function, expected: ApiKind): void {\n const actual = getApiKind(apiClass);\n if (actual !== expected) {\n const apiName = apiClass.name || 'Unknown';\n throw new Error(\n `API ${apiName} is @${actual === 'pubsub' ? 'PubSub' : 'Rpc'} but a ` +\n `${expected === 'pubsub' ? '@PubSub (cloud task)' : '@Rpc'} API was required here.`,\n );\n }\n}\n\n/**\n * Validate @PubSub conventions at wiring time: the class must be @ApiPath + @PubSub\n * and declare at least one endpoint. (Return-type is Promise<void>, a compile-time\n * contract — TS erases types at runtime so it cannot be re-checked here.)\n * @throws Error if conventions are violated.\n */\nexport function assertPubSubConventions(apiClass: Function): void {\n assertApiKind(apiClass, 'pubsub');\n const apiName = apiClass.name || 'Unknown';\n if (!isApiPath(apiClass)) {\n throw new Error(`@PubSub API ${apiName} must also be decorated with @ApiPath()`);\n }\n const endpoints = getEndpoints(apiClass) || {};\n if (Object.keys(endpoints).length === 0) {\n throw new Error(`@PubSub API ${apiName} declares no @Endpoint methods`);\n }\n}\n\n/**\n * Resolve the Cloud Tasks queue name for a @PubSub method: the @Queue override if\n * present, else `${ApiClassName}-${methodName}`.\n */\nexport function getQueueName(apiClass: Function, methodName: string): string {\n const overrides: Record<string, string> =\n Reflect.getMetadata(METADATA_KEYS.QUEUE_OVERRIDE, apiClass) || {};\n return overrides[methodName] ?? `${apiClass.name || 'Unknown'}-${methodName}`;\n}\n\n/**\n * Validate that a class/method doesn't have conflicting auth decorators.\n * @throws Error if multiple @Authentication decorators are found on the same target.\n */\nexport function validateNoConflictingDecorators(apiClass: Function, methodName: string | undefined): 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 ? `method '${methodName}' of ${targetName}` : `class ${targetName}`;\n throw new Error(\n `Conflicting @Authentication on ${location}. ` +\n `Only one @Authentication() decorator 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';\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 /** '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 @MaskLog spec (which DTO fields the LogApiCall path masks). */\n MASK_LOG: 'webpieces:mask-log',\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/**\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 */\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 LogApiCall} 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 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 ) {\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 }\n}\n\n/**\n * The service-to-service / user auth mode of an endpoint. Discriminated union so\n * a filter can `switch (mode.kind)` and get the data it needs, exhaustively.\n *\n * - `public` → no auth check\n * - `jwt` → user-facing JWT (optionally role-gated), validated by the app AuthFilter\n * - `oidc` → Google OIDC service-to-service (Cloud Tasks delivery / cross-service RPC);\n * `callers` is the allow-list of caller service accounts ('self' = this service's SA)\n * - `shared-secret` → constant-time compare of a header against the secret bound for `secretKey`\n */\n/**\n * JwtRequirement - the endpoint's JWT authorization requirement, OPAQUE to the framework. The\n * default JwtHook.authorizeJwt enforces `roles` (any-of; empty = any authenticated user); apps\n * add their OWN fields (inOrg, tenant, feature, ...) via @Auth({...}) and override authorizeJwt to\n * enforce them. This is the pluggable seam: the framework authenticates, the app authorizes.\n */\nexport interface JwtRequirement {\n roles?: string[];\n // webpieces-disable no-any-unknown -- app-defined authorization fields (inOrg, tenant, ...)\n [field: string]: unknown;\n}\n\nexport type AuthMode =\n | { kind: 'public' }\n | { kind: 'jwt'; requirement: JwtRequirement }\n | { kind: 'oidc'; callers: string[] }\n | { kind: 'shared-secret'; secretKey: string };\n\n/**\n * Auth metadata attached to a class or method via one of the auth decorators\n * (@Public / @AuthJwt / @AuthOidc / @AuthSharedSecret) or the legacy @Authentication.\n *\n * Carries a discriminated {@link AuthMode}. The `authenticated`/`roles` getters are\n * kept for back-compat with readers that only understand the user-JWT model\n * (e.g. the example AuthFilter).\n */\nexport class AuthMeta {\n mode: AuthMode;\n\n constructor(mode: AuthMode) {\n this.mode = mode;\n }\n\n /** True for every non-public mode (jwt, oidc, shared-secret). */\n get authenticated(): boolean {\n return this.mode.kind !== 'public';\n }\n\n /** JWT roles, or empty for non-jwt modes (back-compat convenience over the requirement). */\n get roles(): string[] {\n return this.mode.kind === 'jwt' ? (this.mode.requirement.roles ?? []) : [];\n }\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 * @Authentication({authenticated: true})\n * @ApiPath('/api/save')\n * abstract class SaveApi {\n * @Endpoint('/item', 'rpc')\n * save(request: SaveRequest): Promise<SaveResponse> { ... }\n * }\n * ```\n */\nexport function ApiPath(basePath: string): ClassDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any) => {\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 })\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 * The path write to ENDPOINTS is UNCHANGED (every consumer iterates `[methodName, path]`); the kind\n * and options ride PARALLEL ENDPOINT_KIND / ENDPOINT_OPTIONS maps so nothing downstream changes shape.\n */\n// webpieces-disable no-function-outside-class -- decorator factory; decorators are inherently module-scope\nexport function Endpoint(path: string, kind: EndpointKind, options: EndpointOptions = {}): 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}\n\n/**\n * @MaskLog(fields) - declare which fields of THIS method's request/response DTOs the\n * {@link LogApiCall} 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 * Authentication config passed to @Authentication() decorator.\n */\nexport class AuthenticationConfig {\n authenticated: boolean;\n roles?: string[];\n\n constructor(authenticated: boolean, roles?: string[]) {\n this.authenticated = authenticated;\n this.roles = roles;\n }\n}\n\n/**\n * @Authentication(config) - Class or method decorator for auth requirements.\n *\n * Single decorator replaces @Public/@Authenticated/@Roles:\n * - @Authentication({authenticated: false}) → public, no auth check\n * - @Authentication({authenticated: true}) → requires authentication\n * - @Authentication({authenticated: true, roles: ['admin']}) → requires auth + roles\n *\n * Class-level is required. Methods can override class-level.\n * Throws if authenticated=false but roles are specified (contradictory).\n */\nexport function Authentication(config: AuthenticationConfig): ClassDecorator & MethodDecorator {\n // Validate: can't be public with roles\n if (!config.authenticated && config.roles && config.roles.length > 0) {\n throw new Error(\n `Invalid @Authentication config: authenticated=false but roles=${JSON.stringify(config.roles)}. ` +\n `Cannot require roles on a public endpoint. Set authenticated=true or remove roles.`\n );\n }\n\n const mode: AuthMode = config.authenticated\n ? { kind: 'jwt', requirement: { roles: config.roles ?? [] } }\n : { kind: 'public' };\n return defineAuthMode(mode);\n}\n\n/**\n * Shared implementation for every auth decorator: stores an {@link AuthMeta} for\n * the given {@link AuthMode} at class- or method-level, rejecting a second auth\n * decorator on the same target.\n */\nfunction defineAuthMode(mode: AuthMode): ClassDecorator & 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 // Method decorator\n const metadataTarget = typeof target === 'function' ? target : target.constructor;\n validateNoConflictingDecorators(metadataTarget, propertyKey as string);\n Reflect.defineMetadata(METADATA_KEYS.AUTH_META, authMeta, metadataTarget, propertyKey);\n } else {\n // Class decorator\n validateNoConflictingDecorators(target, undefined);\n Reflect.defineMetadata(METADATA_KEYS.AUTH_META, authMeta, target);\n }\n };\n}\n\n/**\n * @Public() - endpoint requires no authentication. Class- or method-level.\n */\nexport function Public(): ClassDecorator & MethodDecorator {\n return defineAuthMode({ kind: 'public' });\n}\n\n/**\n * @AuthJwt(...roles) - user-facing JWT auth, optionally role-gated. The app-level\n * AuthFilter validates the token; roles=[] means \"any authenticated user\".\n */\nexport function AuthJwt(...roles: string[]): ClassDecorator & MethodDecorator {\n return defineAuthMode({ kind: 'jwt', requirement: { roles } });\n}\n\n/**\n * @Auth(requirement) - user-facing JWT auth with an APP-DEFINED authorization requirement beyond\n * roles, e.g. `@Auth({ inOrg: true })` or `@Auth({ roles: ['admin'], tenantScoped: true })`. The\n * framework authenticates the JWT (JwtHook.parseJwt), then hands `requirement` + the parsed\n * values to JwtHook.authorizeJwt — which the app overrides to enforce its own policy. This is\n * how clients plug in their own JWT security without touching the framework.\n */\nexport function Auth(requirement: JwtRequirement): ClassDecorator & MethodDecorator {\n return defineAuthMode({ kind: 'jwt', requirement });\n}\n\n/**\n * @AuthOidc(...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 (`@AuthOidc('svc-a')`) only when you want\n * an additional app-level allow-list as defense-in-depth.\n */\nexport function AuthOidc(...callers: string[]): ClassDecorator & MethodDecorator {\n return defineAuthMode({ kind: 'oidc', callers });\n}\n\n/**\n * @AuthSharedSecret(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 */\nexport function AuthSharedSecret(key: string): ClassDecorator & MethodDecorator {\n return defineAuthMode({ kind: 'shared-secret', secretKey: key });\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 * 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 * 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 auth metadata for a specific method, falling back to class-level auth.\n * Method-level auth takes precedence over class-level auth.\n */\nexport function getAuthMeta(apiClass: Function, methodName?: string): AuthMeta | undefined {\n // Check method-level first\n if (methodName) {\n const methodAuth = Reflect.getMetadata(METADATA_KEYS.AUTH_META, apiClass, methodName);\n if (methodAuth) {\n return methodAuth;\n }\n }\n\n // Fall back to class-level\n return Reflect.getMetadata(METADATA_KEYS.AUTH_META, apiClass);\n}\n\n/**\n * Get the auth mode for a method (falling back to class-level), or undefined.\n * Convenience wrapper over getAuthMeta for callers that only want the mode.\n */\nexport function getAuthMode(apiClass: Function, methodName?: string): AuthMode | undefined {\n return getAuthMeta(apiClass, methodName)?.mode;\n}\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 @Authentication/@Public/@Auth* decorator.\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 `Add @Public(), @AuthJwt(...), @AuthOidc(...) or @AuthSharedSecret(...) ` +\n `to the class or method.`,\n );\n }\n }\n}\n\n// ============================================================\n// API kind (RPC vs PubSub/Cloud Tasks) + queue naming\n// ============================================================\n\n/**\n * API kind. 'rpc' = synchronous request/response (http-client ↔ ApiRoutingFactory).\n * 'pubsub' = fire-and-forget cloud task; the enqueue client (cloudtasks-client)\n * schedules a Cloud Task that is later delivered to the SAME controller endpoint.\n */\nexport type ApiKind = 'rpc' | 'pubsub';\n\n/**\n * @Rpc() - marks an API class as synchronous request/response (the default kind).\n * Present mostly for symmetry/readability; an undecorated API is treated as 'rpc'.\n */\nexport function Rpc(): ClassDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any) => {\n Reflect.defineMetadata(METADATA_KEYS.API_KIND, 'rpc' as ApiKind, target);\n };\n}\n\n/**\n * @PubSub() - marks an API class as fire-and-forget over Cloud Tasks. Every method\n * MUST return Promise<void> (a compile-time contract on the abstract API). The\n * enqueue client and the controller share this one class, exactly like RPC.\n */\nexport function PubSub(): ClassDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any) => {\n Reflect.defineMetadata(METADATA_KEYS.API_KIND, 'pubsub' as ApiKind, target);\n };\n}\n\n/**\n * @Queue(name) - override the Cloud Tasks queue name for a @PubSub method. Default\n * (no decorator) is `${ApiClassName}-${methodName}`, matched 1:1 by Terraform.\n */\nexport function Queue(name: string): MethodDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any, propertyKey: string | symbol, _descriptor: PropertyDescriptor) => {\n const metadataTarget = typeof target === 'function' ? target : target.constructor;\n const overrides: Record<string, string> =\n Reflect.getMetadata(METADATA_KEYS.QUEUE_OVERRIDE, metadataTarget) || {};\n overrides[propertyKey as string] = name;\n Reflect.defineMetadata(METADATA_KEYS.QUEUE_OVERRIDE, overrides, metadataTarget);\n };\n}\n\n/**\n * Get the API kind. Defaults to 'rpc' when neither @Rpc nor @PubSub is present.\n */\nexport function getApiKind(apiClass: Function): ApiKind {\n return (Reflect.getMetadata(METADATA_KEYS.API_KIND, apiClass) as ApiKind) ?? 'rpc';\n}\n\n/**\n * Assert the API class is of the expected kind (used by the clients: the RPC\n * client rejects a @PubSub api and vice-versa).\n * @throws Error if the kind doesn't match.\n */\nexport function assertApiKind(apiClass: Function, expected: ApiKind): void {\n const actual = getApiKind(apiClass);\n if (actual !== expected) {\n const apiName = apiClass.name || 'Unknown';\n throw new Error(\n `API ${apiName} is @${actual === 'pubsub' ? 'PubSub' : 'Rpc'} but a ` +\n `${expected === 'pubsub' ? '@PubSub (cloud task)' : '@Rpc'} API was required here.`,\n );\n }\n}\n\n/**\n * Which {@link EndpointKind}s each {@link ApiKind} may declare. A @PubSub contract is delivered\n * asynchronously by definition, so `rpc` is meaningless on it; an @Rpc contract has no queue, so\n * `cloudtasks`/`cron` on it would name a queue/schedule nothing could ever deliver to. `external`\n * is legal on both — a webhook posts synchronously, a push subscription does not.\n *\n * Shared so the wiring-time assert below and the build-time architecture scan enforce ONE rule.\n */\nexport const ENDPOINT_KINDS_BY_API_KIND: Record<ApiKind, readonly EndpointKind[]> = {\n rpc: ['rpc', 'external'],\n pubsub: ['cloudtasks', 'cron', 'external'],\n};\n\n/**\n * Validate @PubSub conventions at wiring time: the class must be @ApiPath + @PubSub, declare at\n * least one endpoint, and every endpoint must declare a kind this api kind can actually deliver.\n * (Return-type is Promise<void>, a compile-time contract — TS erases types at runtime so it cannot\n * be re-checked here.)\n * @throws Error if conventions are violated.\n */\nexport function assertPubSubConventions(apiClass: Function): void {\n assertApiKind(apiClass, 'pubsub');\n const apiName = apiClass.name || 'Unknown';\n if (!isApiPath(apiClass)) {\n throw new Error(`@PubSub API ${apiName} must also be decorated with @ApiPath()`);\n }\n const endpoints = getEndpoints(apiClass) || {};\n if (Object.keys(endpoints).length === 0) {\n throw new Error(`@PubSub API ${apiName} declares no @Endpoint methods`);\n }\n const allowed = ENDPOINT_KINDS_BY_API_KIND.pubsub;\n const kinds = getEndpointKinds(apiClass);\n for (const methodName of Object.keys(endpoints)) {\n const kind = kinds[methodName];\n if (kind !== undefined && allowed.includes(kind)) continue;\n throw new Error(\n `@PubSub API ${apiName}.${methodName} declares @Endpoint(..., '${kind ?? 'missing'}') — a ` +\n `@PubSub contract is delivered through a queue, so it must be one of: ${allowed.join(' | ')}.`,\n );\n }\n}\n\n/**\n * Resolve the Cloud Tasks queue name for a @PubSub method: the @Queue override if\n * present, else `${ApiClassName}-${methodName}`.\n */\nexport function getQueueName(apiClass: Function, methodName: string): string {\n const overrides: Record<string, string> =\n Reflect.getMetadata(METADATA_KEYS.QUEUE_OVERRIDE, apiClass) || {};\n return overrides[methodName] ?? `${apiClass.name || 'Unknown'}-${methodName}`;\n}\n\n/**\n * Validate that a class/method doesn't have conflicting auth decorators.\n * @throws Error if multiple @Authentication decorators are found on the same target.\n */\nexport function validateNoConflictingDecorators(apiClass: Function, methodName: string | undefined): 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 ? `method '${methodName}' of ${targetName}` : `class ${targetName}`;\n throw new Error(\n `Conflicting @Authentication on ${location}. ` +\n `Only one @Authentication() decorator allowed per target.`\n );\n }\n}\n"]}
package/src/index.d.ts CHANGED
@@ -17,8 +17,8 @@ export { ConsoleLogger } from './logging/ConsoleLogger';
17
17
  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
- export { ApiPath, Endpoint, Authentication, AuthenticationConfig, Public, AuthJwt, Auth, AuthOidc, AuthSharedSecret, Rpc, PubSub, Queue, MaskLog, getApiPath, getEndpoints, getEndpointOptions, getMaskSpec, isFormPost, isApiPath, getAuthMeta, getAuthMode, assertEveryEndpointHasAuthMode, getApiKind, assertApiKind, assertPubSubConventions, getQueueName, validateNoConflictingDecorators, AuthMeta, RouteMetadata, METADATA_KEYS, } from './http/decorators';
21
- export type { AuthMode, ApiKind, JwtRequirement, EndpointOptions } from './http/decorators';
20
+ export { ApiPath, Endpoint, Authentication, AuthenticationConfig, Public, AuthJwt, Auth, AuthOidc, AuthSharedSecret, Rpc, PubSub, Queue, MaskLog, getApiPath, getEndpoints, getEndpointOptions, getEndpointKind, getEndpointKinds, ENDPOINT_KINDS_BY_API_KIND, getMaskSpec, isFormPost, isApiPath, getAuthMeta, getAuthMode, assertEveryEndpointHasAuthMode, getApiKind, assertApiKind, assertPubSubConventions, getQueueName, validateNoConflictingDecorators, AuthMeta, RouteMetadata, METADATA_KEYS, } from './http/decorators';
21
+ export type { AuthMode, ApiKind, EndpointKind, JwtRequirement, EndpointOptions } from './http/decorators';
22
22
  export { Secrets, SECRETS } from './http/Secrets';
23
23
  export { ValidateImplementation } from './http/validators';
24
24
  export { ProtocolError, HttpError, HttpNotFoundError, EndpointNotFoundError, HttpBadRequestError, HttpUnauthorizedError, HttpForbiddenError, HttpTimeoutError, HttpBadGatewayError, HttpGatewayTimeoutError, HttpInternalServerError, HttpTooManyRequestsError, HttpVendorError, HttpUserError, OfflineError, ENTITY_NOT_FOUND, WRONG_LOGIN_TYPE, WRONG_LOGIN, NOT_APPROVED, EMAIL_NOT_CONFIRMED, WRONG_DOMAIN, WRONG_COMPANY, NO_REG_CODE, } from './http/errors';
package/src/index.js CHANGED
@@ -8,9 +8,9 @@
8
8
  * @packageDocumentation
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.EndpointNotFoundError = exports.HttpNotFoundError = exports.HttpError = exports.ProtocolError = exports.SECRETS = exports.Secrets = exports.METADATA_KEYS = exports.RouteMetadata = exports.AuthMeta = exports.validateNoConflictingDecorators = exports.getQueueName = exports.assertPubSubConventions = exports.assertApiKind = exports.getApiKind = exports.assertEveryEndpointHasAuthMode = exports.getAuthMode = exports.getAuthMeta = exports.isApiPath = exports.isFormPost = exports.getMaskSpec = exports.getEndpointOptions = exports.getEndpoints = exports.getApiPath = exports.MaskLog = exports.Queue = exports.PubSub = exports.Rpc = exports.AuthSharedSecret = exports.AuthOidc = exports.Auth = exports.AuthJwt = exports.Public = exports.AuthenticationConfig = exports.Authentication = 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.RecordSerializer = exports.getDoNotRecordFields = exports.DoNotRecord = exports.RecordedTestCase = exports.RecordedError = exports.RecordedEndpoint = exports.RecorderKeys = exports.ApiCallContextHolder = exports.ApiMethodInfo = exports.LOG_API_CALL_LOGGER_NAME = exports.ApiCallLogNameImpl = exports.ApiCallLogName = exports.ApiCallInfo = exports.MaskSpec = exports.LogApiCallImpl = exports.LogApiCall = exports.ContextMgr = exports.WebpiecesCoreHeaders = exports.templateDeriver = exports.WEBPIECES_DEFAULT_FAILURE_CLASSIFIER = exports.WebpiecesDefaultFailureClassifier = exports.KeyedFailureClassifier = exports.ErrorWireForm = exports.ServiceInfo = 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.OfflineError = exports.HttpUserError = exports.HttpVendorError = exports.HttpTooManyRequestsError = exports.HttpInternalServerError = exports.HttpGatewayTimeoutError = exports.HttpBadGatewayError = exports.HttpTimeoutError = exports.HttpForbiddenError = exports.HttpUnauthorizedError = exports.HttpBadRequestError = void 0;
13
- exports.SerializedError = exports.SerializedMap = void 0;
11
+ exports.ProtocolError = exports.SECRETS = exports.Secrets = exports.METADATA_KEYS = exports.RouteMetadata = exports.AuthMeta = exports.validateNoConflictingDecorators = exports.getQueueName = exports.assertPubSubConventions = exports.assertApiKind = exports.getApiKind = exports.assertEveryEndpointHasAuthMode = exports.getAuthMode = exports.getAuthMeta = exports.isApiPath = exports.isFormPost = exports.getMaskSpec = exports.ENDPOINT_KINDS_BY_API_KIND = exports.getEndpointKinds = exports.getEndpointKind = exports.getEndpointOptions = exports.getEndpoints = exports.getApiPath = exports.MaskLog = exports.Queue = exports.PubSub = exports.Rpc = exports.AuthSharedSecret = exports.AuthOidc = exports.Auth = exports.AuthJwt = exports.Public = exports.AuthenticationConfig = exports.Authentication = 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.RecordedTestCase = exports.RecordedError = exports.RecordedEndpoint = exports.RecorderKeys = exports.ApiCallContextHolder = exports.ApiMethodInfo = exports.LOG_API_CALL_LOGGER_NAME = exports.ApiCallLogNameImpl = exports.ApiCallLogName = exports.ApiCallInfo = exports.MaskSpec = exports.LogApiCallImpl = exports.LogApiCall = exports.ContextMgr = exports.WebpiecesCoreHeaders = exports.templateDeriver = exports.WEBPIECES_DEFAULT_FAILURE_CLASSIFIER = exports.WebpiecesDefaultFailureClassifier = exports.KeyedFailureClassifier = exports.ErrorWireForm = exports.ServiceInfo = 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.OfflineError = exports.HttpUserError = exports.HttpVendorError = exports.HttpTooManyRequestsError = exports.HttpInternalServerError = exports.HttpGatewayTimeoutError = exports.HttpBadGatewayError = exports.HttpTimeoutError = exports.HttpForbiddenError = exports.HttpUnauthorizedError = exports.HttpBadRequestError = exports.EndpointNotFoundError = exports.HttpNotFoundError = exports.HttpError = void 0;
13
+ exports.SerializedError = exports.SerializedMap = exports.RecordSerializer = exports.getDoNotRecordFields = exports.DoNotRecord = void 0;
14
14
  var errorUtils_1 = require("./lib/errorUtils");
15
15
  Object.defineProperty(exports, "toError", { enumerable: true, get: function () { return errorUtils_1.toError; } });
16
16
  var ContextKey_1 = require("./ContextKey");
@@ -61,6 +61,9 @@ Object.defineProperty(exports, "MaskLog", { enumerable: true, get: function () {
61
61
  Object.defineProperty(exports, "getApiPath", { enumerable: true, get: function () { return decorators_1.getApiPath; } });
62
62
  Object.defineProperty(exports, "getEndpoints", { enumerable: true, get: function () { return decorators_1.getEndpoints; } });
63
63
  Object.defineProperty(exports, "getEndpointOptions", { enumerable: true, get: function () { return decorators_1.getEndpointOptions; } });
64
+ Object.defineProperty(exports, "getEndpointKind", { enumerable: true, get: function () { return decorators_1.getEndpointKind; } });
65
+ Object.defineProperty(exports, "getEndpointKinds", { enumerable: true, get: function () { return decorators_1.getEndpointKinds; } });
66
+ Object.defineProperty(exports, "ENDPOINT_KINDS_BY_API_KIND", { enumerable: true, get: function () { return decorators_1.ENDPOINT_KINDS_BY_API_KIND; } });
64
67
  Object.defineProperty(exports, "getMaskSpec", { enumerable: true, get: function () { return decorators_1.getMaskSpec; } });
65
68
  Object.defineProperty(exports, "isFormPost", { enumerable: true, get: function () { return decorators_1.isFormPost; } });
66
69
  Object.defineProperty(exports, "isApiPath", { enumerable: true, get: function () { return decorators_1.isApiPath; } });
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 { AnyContextKey } 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 { LogChunker, LogChunkerImpl, LogChunkInfo, MAX_GCP_LOG_BYTES, GCP_LOG_BUDGET_BYTES } 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 HttpError hierarchy, 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 Authentication,\n AuthenticationConfig,\n // Auth mode decorators (clean service-to-service + user JWT model)\n Public,\n AuthJwt,\n Auth,\n AuthOidc,\n AuthSharedSecret,\n // API kind (RPC vs PubSub/Cloud Tasks) + queue naming\n Rpc,\n PubSub,\n Queue,\n MaskLog,\n getApiPath,\n getEndpoints,\n getEndpointOptions,\n getMaskSpec,\n isFormPost,\n isApiPath,\n getAuthMeta,\n getAuthMode,\n assertEveryEndpointHasAuthMode,\n getApiKind,\n assertApiKind,\n assertPubSubConventions,\n getQueueName,\n validateNoConflictingDecorators,\n AuthMeta,\n RouteMetadata,\n METADATA_KEYS,\n} from './http/decorators';\nexport type { AuthMode, ApiKind, JwtRequirement, EndpointOptions } from './http/decorators';\n// Client-side shared-secret store (the value THIS service sends per @AuthSharedSecret key).\nexport { Secrets, SECRETS } from './http/Secrets';\n\n// Type validators\nexport { ValidateImplementation } from './http/validators';\n\n// HTTP errors\nexport {\n ProtocolError,\n HttpError,\n HttpNotFoundError,\n EndpointNotFoundError,\n HttpBadRequestError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpTimeoutError,\n HttpBadGatewayError,\n HttpGatewayTimeoutError,\n HttpInternalServerError,\n HttpTooManyRequestsError,\n HttpVendorError,\n HttpUserError,\n OfflineError,\n // Error subtype constants\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 './http/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 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// Pluggable, bidirectional error translation (app exception <-> wire form). Registered on\n// ClientRegistry at startup; consulted before the built-in webpieces mapping on BOTH sides.\nexport { ErrorWireForm } from './http/ErrorTranslation';\nexport type { ErrorTranslation } from './http/ErrorTranslation';\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// 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). Singleton: use the LogApiCall constant, not `new`.\nexport { LogApiCall, 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 is installed by @webpieces/core-context; the browser gets the no-op.\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 { ApiCallLogName, ApiCallLogNameImpl, LOG_API_CALL_LOGGER_NAME } from './http/ApiCallLogName';\nexport { ApiMethodInfo } from './http/ApiMethodInfo';\nexport type { ApiSide } from './http/ApiMethodInfo';\nexport { ApiCallContextHolder } from './http/ApiCallContext';\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 { RecordedEndpoint, RecordedError, RecordedTestCase } from './http/recorder/RecordedEndpoint';\nexport { DoNotRecord, getDoNotRecordFields } from './http/recorder/DoNotRecord';\nexport { RecordSerializer, SerializedMap, SerializedError } from './http/recorder/RecordSerializer';\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 { AnyContextKey } 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 { LogChunker, LogChunkerImpl, LogChunkInfo, MAX_GCP_LOG_BYTES, GCP_LOG_BUDGET_BYTES } 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 HttpError hierarchy, 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 Authentication,\n AuthenticationConfig,\n // Auth mode decorators (clean service-to-service + user JWT model)\n Public,\n AuthJwt,\n Auth,\n AuthOidc,\n AuthSharedSecret,\n // API kind (RPC vs PubSub/Cloud Tasks) + queue naming\n Rpc,\n PubSub,\n Queue,\n MaskLog,\n getApiPath,\n getEndpoints,\n getEndpointOptions,\n getEndpointKind,\n getEndpointKinds,\n ENDPOINT_KINDS_BY_API_KIND,\n getMaskSpec,\n isFormPost,\n isApiPath,\n getAuthMeta,\n getAuthMode,\n assertEveryEndpointHasAuthMode,\n getApiKind,\n assertApiKind,\n assertPubSubConventions,\n getQueueName,\n validateNoConflictingDecorators,\n AuthMeta,\n RouteMetadata,\n METADATA_KEYS,\n} from './http/decorators';\nexport type { AuthMode, ApiKind, EndpointKind, JwtRequirement, EndpointOptions } from './http/decorators';\n// Client-side shared-secret store (the value THIS service sends per @AuthSharedSecret key).\nexport { Secrets, SECRETS } from './http/Secrets';\n\n// Type validators\nexport { ValidateImplementation } from './http/validators';\n\n// HTTP errors\nexport {\n ProtocolError,\n HttpError,\n HttpNotFoundError,\n EndpointNotFoundError,\n HttpBadRequestError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpTimeoutError,\n HttpBadGatewayError,\n HttpGatewayTimeoutError,\n HttpInternalServerError,\n HttpTooManyRequestsError,\n HttpVendorError,\n HttpUserError,\n OfflineError,\n // Error subtype constants\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 './http/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 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// Pluggable, bidirectional error translation (app exception <-> wire form). Registered on\n// ClientRegistry at startup; consulted before the built-in webpieces mapping on BOTH sides.\nexport { ErrorWireForm } from './http/ErrorTranslation';\nexport type { ErrorTranslation } from './http/ErrorTranslation';\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// 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). Singleton: use the LogApiCall constant, not `new`.\nexport { LogApiCall, 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 is installed by @webpieces/core-context; the browser gets the no-op.\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 { ApiCallLogName, ApiCallLogNameImpl, LOG_API_CALL_LOGGER_NAME } from './http/ApiCallLogName';\nexport { ApiMethodInfo } from './http/ApiMethodInfo';\nexport type { ApiSide } from './http/ApiMethodInfo';\nexport { ApiCallContextHolder } from './http/ApiCallContext';\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 { RecordedEndpoint, RecordedError, RecordedTestCase } from './http/recorder/RecordedEndpoint';\nexport { DoNotRecord, getDoNotRecordFields } from './http/recorder/DoNotRecord';\nexport { RecordSerializer, SerializedMap, SerializedError } from './http/recorder/RecordSerializer';\n"]}