@webpieces/core-util 0.3.263 → 0.3.264

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/package.json +1 -1
  2. package/src/http/ContextReader.d.ts +37 -0
  3. package/src/http/ContextReader.js +3 -0
  4. package/src/http/ContextReader.js.map +1 -0
  5. package/src/http/HeaderMethods.d.ts +71 -0
  6. package/src/http/HeaderMethods.js +124 -0
  7. package/src/http/HeaderMethods.js.map +1 -0
  8. package/src/http/HeaderRegistry.d.ts +58 -0
  9. package/src/http/HeaderRegistry.js +121 -0
  10. package/src/http/HeaderRegistry.js.map +1 -0
  11. package/src/http/HeaderTypes.d.ts +29 -0
  12. package/src/http/HeaderTypes.js +33 -0
  13. package/src/http/HeaderTypes.js.map +1 -0
  14. package/src/http/LogApiCall.d.ts +41 -0
  15. package/src/http/LogApiCall.js +83 -0
  16. package/src/http/LogApiCall.js.map +1 -0
  17. package/src/http/PlatformHeader.d.ts +51 -0
  18. package/src/http/PlatformHeader.js +63 -0
  19. package/src/http/PlatformHeader.js.map +1 -0
  20. package/src/http/PlatformHeadersExtension.d.ts +51 -0
  21. package/src/http/PlatformHeadersExtension.js +59 -0
  22. package/src/http/PlatformHeadersExtension.js.map +1 -0
  23. package/src/http/WebpiecesCoreHeaders.d.ts +62 -0
  24. package/src/http/WebpiecesCoreHeaders.js +87 -0
  25. package/src/http/WebpiecesCoreHeaders.js.map +1 -0
  26. package/src/http/datetime.d.ts +284 -0
  27. package/src/http/datetime.js +270 -0
  28. package/src/http/datetime.js.map +1 -0
  29. package/src/http/decorators.d.ts +212 -0
  30. package/src/http/decorators.js +365 -0
  31. package/src/http/decorators.js.map +1 -0
  32. package/src/http/errors.d.ts +118 -0
  33. package/src/http/errors.js +189 -0
  34. package/src/http/errors.js.map +1 -0
  35. package/src/http/recorder/DoNotRecord.d.ts +22 -0
  36. package/src/http/recorder/DoNotRecord.js +39 -0
  37. package/src/http/recorder/DoNotRecord.js.map +1 -0
  38. package/src/http/recorder/RecordSerializer.d.ts +39 -0
  39. package/src/http/recorder/RecordSerializer.js +80 -0
  40. package/src/http/recorder/RecordSerializer.js.map +1 -0
  41. package/src/http/recorder/RecordedEndpoint.d.ts +49 -0
  42. package/src/http/recorder/RecordedEndpoint.js +66 -0
  43. package/src/http/recorder/RecordedEndpoint.js.map +1 -0
  44. package/src/http/recorder/TestCaseRecorder.d.ts +36 -0
  45. package/src/http/recorder/TestCaseRecorder.js +16 -0
  46. package/src/http/recorder/TestCaseRecorder.js.map +1 -0
  47. package/src/http/validators.d.ts +41 -0
  48. package/src/http/validators.js +3 -0
  49. package/src/http/validators.js.map +1 -0
  50. package/src/index.d.ts +22 -0
  51. package/src/index.js +102 -1
  52. package/src/index.js.map +1 -1
  53. package/src/logging/ConsoleLogger.d.ts +25 -0
  54. package/src/logging/ConsoleLogger.js +43 -0
  55. package/src/logging/ConsoleLogger.js.map +1 -0
  56. package/src/logging/ConsoleLoggerFactory.d.ts +13 -0
  57. package/src/logging/ConsoleLoggerFactory.js +24 -0
  58. package/src/logging/ConsoleLoggerFactory.js.map +1 -0
  59. package/src/logging/LogManager.d.ts +36 -0
  60. package/src/logging/LogManager.js +46 -0
  61. package/src/logging/LogManager.js.map +1 -0
  62. package/src/logging/Logger.d.ts +36 -0
  63. package/src/logging/Logger.js +3 -0
  64. package/src/logging/Logger.js.map +1 -0
  65. package/src/logging/LoggerFactory.d.ts +16 -0
  66. package/src/logging/LoggerFactory.js +3 -0
  67. package/src/logging/LoggerFactory.js.map +1 -0
@@ -0,0 +1,212 @@
1
+ import 'reflect-metadata';
2
+ /**
3
+ * Metadata keys for storing API routing information.
4
+ * These keys are used by both server-side (routing) and client-side (client generation).
5
+ */
6
+ export declare const METADATA_KEYS: {
7
+ API_PATH: string;
8
+ ENDPOINTS: string;
9
+ AUTH_META: string;
10
+ /** 'rpc' (default, sync request/response) vs 'pubsub' (fire-and-forget cloud task). */
11
+ API_KIND: string;
12
+ /** Per-method Cloud Tasks queue-name override (set via @Queue). */
13
+ QUEUE_OVERRIDE: string;
14
+ };
15
+ /**
16
+ * Route metadata stored per-method at runtime.
17
+ * Used internally by http-routing and http-client as the runtime representation
18
+ * of a route. Constructed from @ApiPath + @Endpoint metadata by createApiClient
19
+ * and ApiRoutingFactory.
20
+ */
21
+ export declare class RouteMetadata {
22
+ httpMethod: string;
23
+ path: string;
24
+ methodName: string;
25
+ controllerClassName?: string;
26
+ authMeta?: AuthMeta;
27
+ constructor(httpMethod: string, path: string, methodName: string, controllerClassName?: string, authMeta?: AuthMeta);
28
+ }
29
+ /**
30
+ * The service-to-service / user auth mode of an endpoint. Discriminated union so
31
+ * a filter can `switch (mode.kind)` and get the data it needs, exhaustively.
32
+ *
33
+ * - `public` → no auth check
34
+ * - `jwt` → user-facing JWT (optionally role-gated), validated by the app AuthFilter
35
+ * - `oidc` → Google OIDC service-to-service (Cloud Tasks delivery / cross-service RPC);
36
+ * `callers` is the allow-list of caller service accounts ('self' = this service's SA)
37
+ * - `shared-secret` → constant-time compare of a header against `process.env[secretEnv]`
38
+ */
39
+ export type AuthMode = {
40
+ kind: 'public';
41
+ } | {
42
+ kind: 'jwt';
43
+ roles: string[];
44
+ } | {
45
+ kind: 'oidc';
46
+ callers: string[];
47
+ } | {
48
+ kind: 'shared-secret';
49
+ secretEnv: string;
50
+ };
51
+ /**
52
+ * Auth metadata attached to a class or method via one of the auth decorators
53
+ * (@Public / @AuthJwt / @AuthOidc / @AuthSharedSecret) or the legacy @Authentication.
54
+ *
55
+ * Carries a discriminated {@link AuthMode}. The `authenticated`/`roles` getters are
56
+ * kept for back-compat with readers that only understand the user-JWT model
57
+ * (e.g. the example AuthFilter).
58
+ */
59
+ export declare class AuthMeta {
60
+ mode: AuthMode;
61
+ constructor(mode: AuthMode);
62
+ /** True for every non-public mode (jwt, oidc, shared-secret). */
63
+ get authenticated(): boolean;
64
+ /** JWT roles, or empty for non-jwt modes. */
65
+ get roles(): string[];
66
+ }
67
+ /**
68
+ * @ApiPath(basePath) - Class decorator that marks a class as an API definition
69
+ * and sets the base path for all endpoints.
70
+ *
71
+ * Usage:
72
+ * ```typescript
73
+ * @Authentication({authenticated: true})
74
+ * @ApiPath('/api/save')
75
+ * abstract class SaveApi {
76
+ * @Endpoint('/item')
77
+ * save(request: SaveRequest): Promise<SaveResponse> { ... }
78
+ * }
79
+ * ```
80
+ */
81
+ export declare function ApiPath(basePath: string): ClassDecorator;
82
+ /**
83
+ * @Endpoint(path) - Method decorator that registers a POST endpoint at the given path.
84
+ *
85
+ * All endpoints are POST-only (matching gRPC/thrift style).
86
+ *
87
+ * Usage:
88
+ * ```typescript
89
+ * @Endpoint('/item')
90
+ * save(request: SaveRequest): Promise<SaveResponse> { ... }
91
+ * ```
92
+ */
93
+ export declare function Endpoint(path: string): MethodDecorator;
94
+ /**
95
+ * Authentication config passed to @Authentication() decorator.
96
+ */
97
+ export declare class AuthenticationConfig {
98
+ authenticated: boolean;
99
+ roles?: string[];
100
+ constructor(authenticated: boolean, roles?: string[]);
101
+ }
102
+ /**
103
+ * @Authentication(config) - Class or method decorator for auth requirements.
104
+ *
105
+ * Single decorator replaces @Public/@Authenticated/@Roles:
106
+ * - @Authentication({authenticated: false}) → public, no auth check
107
+ * - @Authentication({authenticated: true}) → requires authentication
108
+ * - @Authentication({authenticated: true, roles: ['admin']}) → requires auth + roles
109
+ *
110
+ * Class-level is required. Methods can override class-level.
111
+ * Throws if authenticated=false but roles are specified (contradictory).
112
+ */
113
+ export declare function Authentication(config: AuthenticationConfig): ClassDecorator & MethodDecorator;
114
+ /**
115
+ * @Public() - endpoint requires no authentication. Class- or method-level.
116
+ */
117
+ export declare function Public(): ClassDecorator & MethodDecorator;
118
+ /**
119
+ * @AuthJwt(...roles) - user-facing JWT auth, optionally role-gated. The app-level
120
+ * AuthFilter validates the token; roles=[] means "any authenticated user".
121
+ */
122
+ export declare function AuthJwt(...roles: string[]): ClassDecorator & MethodDecorator;
123
+ /**
124
+ * @AuthOidc(...callers) - Google OIDC service-to-service auth (Cloud Tasks delivery
125
+ * / cross-service RPC). `callers` is the allow-list of caller service accounts;
126
+ * defaults to ['self'] (only this service's own runtime SA, e.g. self-enqueue).
127
+ */
128
+ export declare function AuthOidc(...callers: string[]): ClassDecorator & MethodDecorator;
129
+ /**
130
+ * @AuthSharedSecret(envVarName) - constant-time compare of an inbound header against
131
+ * process.env[envVarName]. For internal callers that cannot mint OIDC tokens.
132
+ */
133
+ export declare function AuthSharedSecret(envVarName: string): ClassDecorator & MethodDecorator;
134
+ /**
135
+ * Get the base path from @ApiPath decorator.
136
+ */
137
+ export declare function getApiPath(apiClass: Function): string | undefined;
138
+ /**
139
+ * Get all endpoints from @Endpoint decorators.
140
+ * Returns a record of methodName -> endpoint path.
141
+ */
142
+ export declare function getEndpoints(apiClass: Function): Record<string, string> | undefined;
143
+ /**
144
+ * Check if a class has @ApiPath decorator.
145
+ */
146
+ export declare function isApiPath(apiClass: Function): boolean;
147
+ /**
148
+ * Get auth metadata for a specific method, falling back to class-level auth.
149
+ * Method-level auth takes precedence over class-level auth.
150
+ */
151
+ export declare function getAuthMeta(apiClass: Function, methodName?: string): AuthMeta | undefined;
152
+ /**
153
+ * Get the auth mode for a method (falling back to class-level), or undefined.
154
+ * Convenience wrapper over getAuthMeta for callers that only want the mode.
155
+ */
156
+ export declare function getAuthMode(apiClass: Function, methodName?: string): AuthMode | undefined;
157
+ /**
158
+ * Fail-fast at wiring time if any endpoint lacks an auth mode. Both the server
159
+ * (ApiRoutingFactory) and the task/rpc clients call this so a missing auth
160
+ * decorator is a startup error, never a silent open endpoint.
161
+ * @throws Error naming the first endpoint with no @Authentication/@Public/@Auth* decorator.
162
+ */
163
+ export declare function assertEveryEndpointHasAuthMode(apiClass: Function): void;
164
+ /**
165
+ * API kind. 'rpc' = synchronous request/response (http-client ↔ ApiRoutingFactory).
166
+ * 'pubsub' = fire-and-forget cloud task; the enqueue client (cloudtasks-client)
167
+ * schedules a Cloud Task that is later delivered to the SAME controller endpoint.
168
+ */
169
+ export type ApiKind = 'rpc' | 'pubsub';
170
+ /**
171
+ * @Rpc() - marks an API class as synchronous request/response (the default kind).
172
+ * Present mostly for symmetry/readability; an undecorated API is treated as 'rpc'.
173
+ */
174
+ export declare function Rpc(): ClassDecorator;
175
+ /**
176
+ * @PubSub() - marks an API class as fire-and-forget over Cloud Tasks. Every method
177
+ * MUST return Promise<void> (a compile-time contract on the abstract API). The
178
+ * enqueue client and the controller share this one class, exactly like RPC.
179
+ */
180
+ export declare function PubSub(): ClassDecorator;
181
+ /**
182
+ * @Queue(name) - override the Cloud Tasks queue name for a @PubSub method. Default
183
+ * (no decorator) is `${ApiClassName}-${methodName}`, matched 1:1 by Terraform.
184
+ */
185
+ export declare function Queue(name: string): MethodDecorator;
186
+ /**
187
+ * Get the API kind. Defaults to 'rpc' when neither @Rpc nor @PubSub is present.
188
+ */
189
+ export declare function getApiKind(apiClass: Function): ApiKind;
190
+ /**
191
+ * Assert the API class is of the expected kind (used by the clients: the RPC
192
+ * client rejects a @PubSub api and vice-versa).
193
+ * @throws Error if the kind doesn't match.
194
+ */
195
+ export declare function assertApiKind(apiClass: Function, expected: ApiKind): void;
196
+ /**
197
+ * Validate @PubSub conventions at wiring time: the class must be @ApiPath + @PubSub
198
+ * and declare at least one endpoint. (Return-type is Promise<void>, a compile-time
199
+ * contract — TS erases types at runtime so it cannot be re-checked here.)
200
+ * @throws Error if conventions are violated.
201
+ */
202
+ export declare function assertPubSubConventions(apiClass: Function): void;
203
+ /**
204
+ * Resolve the Cloud Tasks queue name for a @PubSub method: the @Queue override if
205
+ * present, else `${ApiClassName}-${methodName}`.
206
+ */
207
+ export declare function getQueueName(apiClass: Function, methodName: string): string;
208
+ /**
209
+ * Validate that a class/method doesn't have conflicting auth decorators.
210
+ * @throws Error if multiple @Authentication decorators are found on the same target.
211
+ */
212
+ export declare function validateNoConflictingDecorators(apiClass: Function, methodName: string | undefined): void;
@@ -0,0 +1,365 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AuthenticationConfig = exports.AuthMeta = exports.RouteMetadata = exports.METADATA_KEYS = void 0;
4
+ exports.ApiPath = ApiPath;
5
+ exports.Endpoint = Endpoint;
6
+ exports.Authentication = Authentication;
7
+ exports.Public = Public;
8
+ exports.AuthJwt = AuthJwt;
9
+ exports.AuthOidc = AuthOidc;
10
+ exports.AuthSharedSecret = AuthSharedSecret;
11
+ exports.getApiPath = getApiPath;
12
+ exports.getEndpoints = getEndpoints;
13
+ exports.isApiPath = isApiPath;
14
+ exports.getAuthMeta = getAuthMeta;
15
+ exports.getAuthMode = getAuthMode;
16
+ exports.assertEveryEndpointHasAuthMode = assertEveryEndpointHasAuthMode;
17
+ exports.Rpc = Rpc;
18
+ exports.PubSub = PubSub;
19
+ exports.Queue = Queue;
20
+ exports.getApiKind = getApiKind;
21
+ exports.assertApiKind = assertApiKind;
22
+ exports.assertPubSubConventions = assertPubSubConventions;
23
+ exports.getQueueName = getQueueName;
24
+ exports.validateNoConflictingDecorators = validateNoConflictingDecorators;
25
+ require("reflect-metadata");
26
+ /**
27
+ * Metadata keys for storing API routing information.
28
+ * These keys are used by both server-side (routing) and client-side (client generation).
29
+ */
30
+ exports.METADATA_KEYS = {
31
+ API_PATH: 'webpieces:api-path',
32
+ ENDPOINTS: 'webpieces:endpoints',
33
+ AUTH_META: 'webpieces:auth-meta',
34
+ /** 'rpc' (default, sync request/response) vs 'pubsub' (fire-and-forget cloud task). */
35
+ API_KIND: 'webpieces:api-kind',
36
+ /** Per-method Cloud Tasks queue-name override (set via @Queue). */
37
+ QUEUE_OVERRIDE: 'webpieces:queue-override',
38
+ };
39
+ /**
40
+ * Route metadata stored per-method at runtime.
41
+ * Used internally by http-routing and http-client as the runtime representation
42
+ * of a route. Constructed from @ApiPath + @Endpoint metadata by createApiClient
43
+ * and ApiRoutingFactory.
44
+ */
45
+ class RouteMetadata {
46
+ httpMethod;
47
+ path;
48
+ methodName;
49
+ controllerClassName;
50
+ authMeta;
51
+ constructor(httpMethod, path, methodName, controllerClassName, authMeta) {
52
+ this.httpMethod = httpMethod;
53
+ this.path = path;
54
+ this.methodName = methodName;
55
+ this.controllerClassName = controllerClassName;
56
+ this.authMeta = authMeta;
57
+ }
58
+ }
59
+ exports.RouteMetadata = RouteMetadata;
60
+ /**
61
+ * Auth metadata attached to a class or method via one of the auth decorators
62
+ * (@Public / @AuthJwt / @AuthOidc / @AuthSharedSecret) or the legacy @Authentication.
63
+ *
64
+ * Carries a discriminated {@link AuthMode}. The `authenticated`/`roles` getters are
65
+ * kept for back-compat with readers that only understand the user-JWT model
66
+ * (e.g. the example AuthFilter).
67
+ */
68
+ class AuthMeta {
69
+ mode;
70
+ constructor(mode) {
71
+ this.mode = mode;
72
+ }
73
+ /** True for every non-public mode (jwt, oidc, shared-secret). */
74
+ get authenticated() {
75
+ return this.mode.kind !== 'public';
76
+ }
77
+ /** JWT roles, or empty for non-jwt modes. */
78
+ get roles() {
79
+ return this.mode.kind === 'jwt' ? this.mode.roles : [];
80
+ }
81
+ }
82
+ exports.AuthMeta = AuthMeta;
83
+ /**
84
+ * @ApiPath(basePath) - Class decorator that marks a class as an API definition
85
+ * and sets the base path for all endpoints.
86
+ *
87
+ * Usage:
88
+ * ```typescript
89
+ * @Authentication({authenticated: true})
90
+ * @ApiPath('/api/save')
91
+ * abstract class SaveApi {
92
+ * @Endpoint('/item')
93
+ * save(request: SaveRequest): Promise<SaveResponse> { ... }
94
+ * }
95
+ * ```
96
+ */
97
+ function ApiPath(basePath) {
98
+ // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any
99
+ return (target) => {
100
+ Reflect.defineMetadata(exports.METADATA_KEYS.API_PATH, basePath, target);
101
+ // Initialize endpoints map if not exists
102
+ if (!Reflect.hasMetadata(exports.METADATA_KEYS.ENDPOINTS, target)) {
103
+ Reflect.defineMetadata(exports.METADATA_KEYS.ENDPOINTS, {}, target);
104
+ }
105
+ };
106
+ }
107
+ /**
108
+ * @Endpoint(path) - Method decorator that registers a POST endpoint at the given path.
109
+ *
110
+ * All endpoints are POST-only (matching gRPC/thrift style).
111
+ *
112
+ * Usage:
113
+ * ```typescript
114
+ * @Endpoint('/item')
115
+ * save(request: SaveRequest): Promise<SaveResponse> { ... }
116
+ * ```
117
+ */
118
+ function Endpoint(path) {
119
+ // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any
120
+ return (target, propertyKey, _descriptor) => {
121
+ const metadataTarget = typeof target === 'function' ? target : target.constructor;
122
+ const endpoints = Reflect.getMetadata(exports.METADATA_KEYS.ENDPOINTS, metadataTarget) || {};
123
+ endpoints[propertyKey] = path;
124
+ Reflect.defineMetadata(exports.METADATA_KEYS.ENDPOINTS, endpoints, metadataTarget);
125
+ };
126
+ }
127
+ /**
128
+ * Authentication config passed to @Authentication() decorator.
129
+ */
130
+ class AuthenticationConfig {
131
+ authenticated;
132
+ roles;
133
+ constructor(authenticated, roles) {
134
+ this.authenticated = authenticated;
135
+ this.roles = roles;
136
+ }
137
+ }
138
+ exports.AuthenticationConfig = AuthenticationConfig;
139
+ /**
140
+ * @Authentication(config) - Class or method decorator for auth requirements.
141
+ *
142
+ * Single decorator replaces @Public/@Authenticated/@Roles:
143
+ * - @Authentication({authenticated: false}) → public, no auth check
144
+ * - @Authentication({authenticated: true}) → requires authentication
145
+ * - @Authentication({authenticated: true, roles: ['admin']}) → requires auth + roles
146
+ *
147
+ * Class-level is required. Methods can override class-level.
148
+ * Throws if authenticated=false but roles are specified (contradictory).
149
+ */
150
+ function Authentication(config) {
151
+ // Validate: can't be public with roles
152
+ if (!config.authenticated && config.roles && config.roles.length > 0) {
153
+ throw new Error(`Invalid @Authentication config: authenticated=false but roles=${JSON.stringify(config.roles)}. ` +
154
+ `Cannot require roles on a public endpoint. Set authenticated=true or remove roles.`);
155
+ }
156
+ const mode = config.authenticated
157
+ ? { kind: 'jwt', roles: config.roles ?? [] }
158
+ : { kind: 'public' };
159
+ return defineAuthMode(mode);
160
+ }
161
+ /**
162
+ * Shared implementation for every auth decorator: stores an {@link AuthMeta} for
163
+ * the given {@link AuthMode} at class- or method-level, rejecting a second auth
164
+ * decorator on the same target.
165
+ */
166
+ function defineAuthMode(mode) {
167
+ const authMeta = new AuthMeta(mode);
168
+ // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any
169
+ return (target, propertyKey, _descriptor) => {
170
+ if (propertyKey !== undefined) {
171
+ // Method decorator
172
+ const metadataTarget = typeof target === 'function' ? target : target.constructor;
173
+ validateNoConflictingDecorators(metadataTarget, propertyKey);
174
+ Reflect.defineMetadata(exports.METADATA_KEYS.AUTH_META, authMeta, metadataTarget, propertyKey);
175
+ }
176
+ else {
177
+ // Class decorator
178
+ validateNoConflictingDecorators(target, undefined);
179
+ Reflect.defineMetadata(exports.METADATA_KEYS.AUTH_META, authMeta, target);
180
+ }
181
+ };
182
+ }
183
+ /**
184
+ * @Public() - endpoint requires no authentication. Class- or method-level.
185
+ */
186
+ function Public() {
187
+ return defineAuthMode({ kind: 'public' });
188
+ }
189
+ /**
190
+ * @AuthJwt(...roles) - user-facing JWT auth, optionally role-gated. The app-level
191
+ * AuthFilter validates the token; roles=[] means "any authenticated user".
192
+ */
193
+ function AuthJwt(...roles) {
194
+ return defineAuthMode({ kind: 'jwt', roles });
195
+ }
196
+ /**
197
+ * @AuthOidc(...callers) - Google OIDC service-to-service auth (Cloud Tasks delivery
198
+ * / cross-service RPC). `callers` is the allow-list of caller service accounts;
199
+ * defaults to ['self'] (only this service's own runtime SA, e.g. self-enqueue).
200
+ */
201
+ function AuthOidc(...callers) {
202
+ return defineAuthMode({ kind: 'oidc', callers: callers.length > 0 ? callers : ['self'] });
203
+ }
204
+ /**
205
+ * @AuthSharedSecret(envVarName) - constant-time compare of an inbound header against
206
+ * process.env[envVarName]. For internal callers that cannot mint OIDC tokens.
207
+ */
208
+ function AuthSharedSecret(envVarName) {
209
+ return defineAuthMode({ kind: 'shared-secret', secretEnv: envVarName });
210
+ }
211
+ // ============================================================
212
+ // Helper functions
213
+ // ============================================================
214
+ /**
215
+ * Get the base path from @ApiPath decorator.
216
+ */
217
+ function getApiPath(apiClass) {
218
+ return Reflect.getMetadata(exports.METADATA_KEYS.API_PATH, apiClass);
219
+ }
220
+ /**
221
+ * Get all endpoints from @Endpoint decorators.
222
+ * Returns a record of methodName -> endpoint path.
223
+ */
224
+ function getEndpoints(apiClass) {
225
+ return Reflect.getMetadata(exports.METADATA_KEYS.ENDPOINTS, apiClass);
226
+ }
227
+ /**
228
+ * Check if a class has @ApiPath decorator.
229
+ */
230
+ function isApiPath(apiClass) {
231
+ return Reflect.hasMetadata(exports.METADATA_KEYS.API_PATH, apiClass);
232
+ }
233
+ /**
234
+ * Get auth metadata for a specific method, falling back to class-level auth.
235
+ * Method-level auth takes precedence over class-level auth.
236
+ */
237
+ function getAuthMeta(apiClass, methodName) {
238
+ // Check method-level first
239
+ if (methodName) {
240
+ const methodAuth = Reflect.getMetadata(exports.METADATA_KEYS.AUTH_META, apiClass, methodName);
241
+ if (methodAuth) {
242
+ return methodAuth;
243
+ }
244
+ }
245
+ // Fall back to class-level
246
+ return Reflect.getMetadata(exports.METADATA_KEYS.AUTH_META, apiClass);
247
+ }
248
+ /**
249
+ * Get the auth mode for a method (falling back to class-level), or undefined.
250
+ * Convenience wrapper over getAuthMeta for callers that only want the mode.
251
+ */
252
+ function getAuthMode(apiClass, methodName) {
253
+ return getAuthMeta(apiClass, methodName)?.mode;
254
+ }
255
+ /**
256
+ * Fail-fast at wiring time if any endpoint lacks an auth mode. Both the server
257
+ * (ApiRoutingFactory) and the task/rpc clients call this so a missing auth
258
+ * decorator is a startup error, never a silent open endpoint.
259
+ * @throws Error naming the first endpoint with no @Authentication/@Public/@Auth* decorator.
260
+ */
261
+ function assertEveryEndpointHasAuthMode(apiClass) {
262
+ const apiName = apiClass.name || 'Unknown';
263
+ const endpoints = getEndpoints(apiClass) || {};
264
+ for (const methodName of Object.keys(endpoints)) {
265
+ if (!getAuthMeta(apiClass, methodName)) {
266
+ throw new Error(`Endpoint '${methodName}' in ${apiName} has no auth decorator. ` +
267
+ `Add @Public(), @AuthJwt(...), @AuthOidc(...) or @AuthSharedSecret(...) ` +
268
+ `to the class or method.`);
269
+ }
270
+ }
271
+ }
272
+ /**
273
+ * @Rpc() - marks an API class as synchronous request/response (the default kind).
274
+ * Present mostly for symmetry/readability; an undecorated API is treated as 'rpc'.
275
+ */
276
+ function Rpc() {
277
+ // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any
278
+ return (target) => {
279
+ Reflect.defineMetadata(exports.METADATA_KEYS.API_KIND, 'rpc', target);
280
+ };
281
+ }
282
+ /**
283
+ * @PubSub() - marks an API class as fire-and-forget over Cloud Tasks. Every method
284
+ * MUST return Promise<void> (a compile-time contract on the abstract API). The
285
+ * enqueue client and the controller share this one class, exactly like RPC.
286
+ */
287
+ function PubSub() {
288
+ // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any
289
+ return (target) => {
290
+ Reflect.defineMetadata(exports.METADATA_KEYS.API_KIND, 'pubsub', target);
291
+ };
292
+ }
293
+ /**
294
+ * @Queue(name) - override the Cloud Tasks queue name for a @PubSub method. Default
295
+ * (no decorator) is `${ApiClassName}-${methodName}`, matched 1:1 by Terraform.
296
+ */
297
+ function Queue(name) {
298
+ // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any
299
+ return (target, propertyKey, _descriptor) => {
300
+ const metadataTarget = typeof target === 'function' ? target : target.constructor;
301
+ const overrides = Reflect.getMetadata(exports.METADATA_KEYS.QUEUE_OVERRIDE, metadataTarget) || {};
302
+ overrides[propertyKey] = name;
303
+ Reflect.defineMetadata(exports.METADATA_KEYS.QUEUE_OVERRIDE, overrides, metadataTarget);
304
+ };
305
+ }
306
+ /**
307
+ * Get the API kind. Defaults to 'rpc' when neither @Rpc nor @PubSub is present.
308
+ */
309
+ function getApiKind(apiClass) {
310
+ return Reflect.getMetadata(exports.METADATA_KEYS.API_KIND, apiClass) ?? 'rpc';
311
+ }
312
+ /**
313
+ * Assert the API class is of the expected kind (used by the clients: the RPC
314
+ * client rejects a @PubSub api and vice-versa).
315
+ * @throws Error if the kind doesn't match.
316
+ */
317
+ function assertApiKind(apiClass, expected) {
318
+ const actual = getApiKind(apiClass);
319
+ if (actual !== expected) {
320
+ const apiName = apiClass.name || 'Unknown';
321
+ throw new Error(`API ${apiName} is @${actual === 'pubsub' ? 'PubSub' : 'Rpc'} but a ` +
322
+ `${expected === 'pubsub' ? '@PubSub (cloud task)' : '@Rpc'} API was required here.`);
323
+ }
324
+ }
325
+ /**
326
+ * Validate @PubSub conventions at wiring time: the class must be @ApiPath + @PubSub
327
+ * and declare at least one endpoint. (Return-type is Promise<void>, a compile-time
328
+ * contract — TS erases types at runtime so it cannot be re-checked here.)
329
+ * @throws Error if conventions are violated.
330
+ */
331
+ function assertPubSubConventions(apiClass) {
332
+ assertApiKind(apiClass, 'pubsub');
333
+ const apiName = apiClass.name || 'Unknown';
334
+ if (!isApiPath(apiClass)) {
335
+ throw new Error(`@PubSub API ${apiName} must also be decorated with @ApiPath()`);
336
+ }
337
+ const endpoints = getEndpoints(apiClass) || {};
338
+ if (Object.keys(endpoints).length === 0) {
339
+ throw new Error(`@PubSub API ${apiName} declares no @Endpoint methods`);
340
+ }
341
+ }
342
+ /**
343
+ * Resolve the Cloud Tasks queue name for a @PubSub method: the @Queue override if
344
+ * present, else `${ApiClassName}-${methodName}`.
345
+ */
346
+ function getQueueName(apiClass, methodName) {
347
+ const overrides = Reflect.getMetadata(exports.METADATA_KEYS.QUEUE_OVERRIDE, apiClass) || {};
348
+ return overrides[methodName] ?? `${apiClass.name || 'Unknown'}-${methodName}`;
349
+ }
350
+ /**
351
+ * Validate that a class/method doesn't have conflicting auth decorators.
352
+ * @throws Error if multiple @Authentication decorators are found on the same target.
353
+ */
354
+ function validateNoConflictingDecorators(apiClass, methodName) {
355
+ const existing = methodName
356
+ ? Reflect.getMetadata(exports.METADATA_KEYS.AUTH_META, apiClass, methodName)
357
+ : Reflect.getMetadata(exports.METADATA_KEYS.AUTH_META, apiClass);
358
+ if (existing) {
359
+ const targetName = apiClass.name || 'Unknown';
360
+ const location = methodName ? `method '${methodName}' of ${targetName}` : `class ${targetName}`;
361
+ throw new Error(`Conflicting @Authentication on ${location}. ` +
362
+ `Only one @Authentication() decorator allowed per target.`);
363
+ }
364
+ }
365
+ //# sourceMappingURL=decorators.js.map
@@ -0,0 +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';\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};\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 createApiClient\n * and ApiRoutingFactory.\n */\nexport class RouteMetadata {\n httpMethod: string;\n path: string;\n methodName: string;\n controllerClassName?: string;\n authMeta?: AuthMeta;\n\n constructor(\n httpMethod: string,\n path: string,\n methodName: string,\n controllerClassName?: string,\n authMeta?: AuthMeta,\n ) {\n this.httpMethod = httpMethod;\n this.path = path;\n this.methodName = methodName;\n this.controllerClassName = controllerClassName;\n this.authMeta = authMeta;\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 `process.env[secretEnv]`\n */\nexport type AuthMode =\n | { kind: 'public' }\n | { kind: 'jwt'; roles: string[] }\n | { kind: 'oidc'; callers: string[] }\n | { kind: 'shared-secret'; secretEnv: 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. */\n get roles(): string[] {\n return this.mode.kind === 'jwt' ? this.mode.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) - 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 */\nexport function Endpoint(path: 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\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}\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', 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', roles });\n}\n\n/**\n * @AuthOidc(...callers) - Google OIDC service-to-service auth (Cloud Tasks delivery\n * / cross-service RPC). `callers` is the allow-list of caller service accounts;\n * defaults to ['self'] (only this service's own runtime SA, e.g. self-enqueue).\n */\nexport function AuthOidc(...callers: string[]): ClassDecorator & MethodDecorator {\n return defineAuthMode({ kind: 'oidc', callers: callers.length > 0 ? callers : ['self'] });\n}\n\n/**\n * @AuthSharedSecret(envVarName) - constant-time compare of an inbound header against\n * process.env[envVarName]. For internal callers that cannot mint OIDC tokens.\n */\nexport function AuthSharedSecret(envVarName: string): ClassDecorator & MethodDecorator {\n return defineAuthMode({ kind: 'shared-secret', secretEnv: envVarName });\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 * 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"]}