@webpieces/cloudtasks-client 0.3.312 → 0.3.313

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,11 +10,17 @@ where it runs through the full server filter chain.
10
10
  @PubSub() @AuthOidc() @ApiPath('/email')
11
11
  abstract class EmailApi { @Endpoint('/send') sendEmail(r: SendEmailRequest): Promise<void> {…} }
12
12
 
13
+ // build the client once (sync); 'email-svc' is the callee's Cloud Run service name
14
+ const emailTasks = factory.createClient(EmailApi, new TaskClientConfig('email-svc'));
15
+
13
16
  // producer (inside a request → RequestContext active)
14
17
  await scheduler.addToQueue(() => emailTasks.sendEmail(req), { dedupName: req.id });
15
18
  ```
16
19
 
17
- - `createTaskClient(Api, TaskClientConfig)` / `TaskClientCreator` the enqueue proxy
20
+ - `ClientCloudTasksFactory.createClient(Api, TaskClientConfig)` builds the enqueue proxy,
21
+ backed by `TaskProxyClient`. The delivery URL is resolved from the service name at enqueue
22
+ time via `getCloudRunUrl`, which honours a `CLOUD_RUN_URL_<UPPER_SNAKE_NAME>` env override
23
+ for local multi-service runs and integration tests
18
24
  - `CloudTaskScheduler` — `addToQueue` / `schedule` / `cancelJob`; carries scheduling
19
25
  options out-of-band so the contract signature stays identical on both sides
20
26
  - `TaskInvoker` (abstract token) with two impls:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/cloudtasks-client",
3
- "version": "0.3.312",
3
+ "version": "0.3.313",
4
4
  "description": "Cloud Tasks enqueue client generated from a shared @PubSub API contract (twin of http-client)",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -23,9 +23,9 @@
23
23
  "access": "public"
24
24
  },
25
25
  "dependencies": {
26
- "@webpieces/core-context": "0.3.312",
27
- "@webpieces/core-util": "0.3.312",
28
- "@webpieces/gcp-identity": "0.3.312",
26
+ "@webpieces/core-context": "0.3.313",
27
+ "@webpieces/core-util": "0.3.313",
28
+ "@webpieces/gcp-identity": "0.3.313",
29
29
  "@google-cloud/tasks": "5.5.2",
30
30
  "inversify": "7.10.4",
31
31
  "reflect-metadata": "0.2.2"
@@ -0,0 +1,30 @@
1
+ import { TaskInvoker } from './TaskTypes';
2
+ import { ApiPrototype, TaskClientConfig } from './TaskClientConfig';
3
+ /**
4
+ * ClientCloudTasksFactory - builds Cloud Tasks enqueue clients from a shared @PubSub API
5
+ * contract. The fire-and-forget twin of http-client's ClientHttpFactory.
6
+ *
7
+ * Calling a method on the returned client ENQUEUES a task (it does not call remotely);
8
+ * the task is later delivered to the same endpoint's controller through the full server
9
+ * filter chain. A service just asks for a typed client:
10
+ *
11
+ * ```typescript
12
+ * const emailTasks = factory.createClient(EmailApi, new TaskClientConfig('email-svc'));
13
+ * await scheduler.addToQueue(() => emailTasks.sendEmail(req), { dedupName });
14
+ * ```
15
+ *
16
+ * The factory holds the COLLABORATORS every client shares (the bound TaskInvoker, and a
17
+ * context-propagating ContextMgr); {@link TaskClientConfig} holds only that one client's
18
+ * STATE (which Cloud Run service the task is delivered to).
19
+ *
20
+ * Unlike http-client — which Angular bundles into a browser and which therefore must stay
21
+ * DI-agnostic — this package is node-only, so the factory IS the inversify entry point and
22
+ * the ContextMgr is a fixed field: RequestContextReader is the one and only right reader.
23
+ */
24
+ export declare class ClientCloudTasksFactory {
25
+ private readonly invoker;
26
+ private readonly contextMgr;
27
+ constructor(invoker: TaskInvoker);
28
+ /** Typed enqueue client for a @PubSub contract, delivered to `config.gcpCloudRunSvcName`. */
29
+ createClient<T extends object>(apiClass: ApiPrototype<T>, config: TaskClientConfig): T;
30
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ClientCloudTasksFactory = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const inversify_1 = require("inversify");
6
+ const core_context_1 = require("@webpieces/core-context");
7
+ const TaskTypes_1 = require("./TaskTypes");
8
+ const TaskProxyClient_1 = require("./TaskProxyClient");
9
+ /**
10
+ * Properties DI frameworks / Promise checks / serializers probe on the proxy; return
11
+ * undefined instead of treating them as endpoints. Mirrors http-client's ClientHttpFactory.
12
+ */
13
+ const FRAMEWORK_INSPECTION_PROPERTIES = new Set([
14
+ 'constructor', 'prototype', '__proto__', 'name', 'then', 'catch', 'finally',
15
+ 'toJSON', 'valueOf', 'toString', 'nodeType', 'tagName', '$$typeof',
16
+ ]);
17
+ /**
18
+ * ClientCloudTasksFactory - builds Cloud Tasks enqueue clients from a shared @PubSub API
19
+ * contract. The fire-and-forget twin of http-client's ClientHttpFactory.
20
+ *
21
+ * Calling a method on the returned client ENQUEUES a task (it does not call remotely);
22
+ * the task is later delivered to the same endpoint's controller through the full server
23
+ * filter chain. A service just asks for a typed client:
24
+ *
25
+ * ```typescript
26
+ * const emailTasks = factory.createClient(EmailApi, new TaskClientConfig('email-svc'));
27
+ * await scheduler.addToQueue(() => emailTasks.sendEmail(req), { dedupName });
28
+ * ```
29
+ *
30
+ * The factory holds the COLLABORATORS every client shares (the bound TaskInvoker, and a
31
+ * context-propagating ContextMgr); {@link TaskClientConfig} holds only that one client's
32
+ * STATE (which Cloud Run service the task is delivered to).
33
+ *
34
+ * Unlike http-client — which Angular bundles into a browser and which therefore must stay
35
+ * DI-agnostic — this package is node-only, so the factory IS the inversify entry point and
36
+ * the ContextMgr is a fixed field: RequestContextReader is the one and only right reader.
37
+ */
38
+ let ClientCloudTasksFactory = class ClientCloudTasksFactory {
39
+ invoker;
40
+ contextMgr = new core_context_1.ContextMgr(new core_context_1.RequestContextReader());
41
+ constructor(invoker) {
42
+ this.invoker = invoker;
43
+ }
44
+ /** Typed enqueue client for a @PubSub contract, delivered to `config.gcpCloudRunSvcName`. */
45
+ createClient(apiClass, config) {
46
+ // TaskProxyClient owns @PubSub validation + plan building from the contract's
47
+ // decorators. It is the @DocumentDesign design root for this package.
48
+ const proxyClient = new TaskProxyClient_1.TaskProxyClient(apiClass, config, this.invoker, this.contextMgr);
49
+ return new Proxy({}, {
50
+ // webpieces-disable no-any-unknown -- proxy get trap returns either an endpoint method or undefined
51
+ get(target, prop) {
52
+ if (typeof prop !== 'string' || FRAMEWORK_INSPECTION_PROPERTIES.has(prop)) {
53
+ return undefined;
54
+ }
55
+ if (!proxyClient.hasEndpoint(prop)) {
56
+ throw new Error(`No @PubSub endpoint '${prop}' on ${apiClass.name || 'Unknown'}. ` +
57
+ `Check for typos or a missing @Endpoint() decorator.`);
58
+ }
59
+ // webpieces-disable no-any-unknown -- request DTO type is erased at the proxy layer
60
+ return (requestDto) => proxyClient.enqueue(prop, requestDto);
61
+ },
62
+ });
63
+ }
64
+ };
65
+ exports.ClientCloudTasksFactory = ClientCloudTasksFactory;
66
+ exports.ClientCloudTasksFactory = ClientCloudTasksFactory = tslib_1.__decorate([
67
+ (0, core_context_1.provideFrameworkSingleton)(),
68
+ (0, inversify_1.injectable)(),
69
+ tslib_1.__param(0, (0, inversify_1.inject)(TaskTypes_1.TaskInvoker)),
70
+ tslib_1.__metadata("design:paramtypes", [TaskTypes_1.TaskInvoker])
71
+ ], ClientCloudTasksFactory);
72
+ //# sourceMappingURL=ClientCloudTasksFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClientCloudTasksFactory.js","sourceRoot":"","sources":["../../../../../packages/cloud/cloudtasks-client/src/ClientCloudTasksFactory.ts"],"names":[],"mappings":";;;;AAAA,yCAA+C;AAC/C,0DAAsG;AACtG,2CAA0C;AAC1C,uDAAoD;AAGpD;;;GAGG;AACH,MAAM,+BAA+B,GAAG,IAAI,GAAG,CAAS;IACpD,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS;IAC3E,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU;CACrE,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;GAoBG;AAGI,IAAM,uBAAuB,GAA7B,MAAM,uBAAuB;IAIU;IAHzB,UAAU,GAAG,IAAI,yBAAU,CAAC,IAAI,mCAAoB,EAAE,CAAC,CAAC;IAEzE,YAC0C,OAAoB;QAApB,YAAO,GAAP,OAAO,CAAa;IAC3D,CAAC;IAEJ,6FAA6F;IAC7F,YAAY,CAAmB,QAAyB,EAAE,MAAwB;QAC9E,8EAA8E;QAC9E,sEAAsE;QACtE,MAAM,WAAW,GAAG,IAAI,iCAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAEzF,OAAO,IAAI,KAAK,CAAC,EAAO,EAAE;YACtB,oGAAoG;YACpG,GAAG,CAAC,MAAS,EAAE,IAAqB;gBAChC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,+BAA+B,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;oBACxE,OAAO,SAAS,CAAC;gBACrB,CAAC;gBACD,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;oBACjC,MAAM,IAAI,KAAK,CACX,wBAAwB,IAAI,QAAQ,QAAQ,CAAC,IAAI,IAAI,SAAS,IAAI;wBAClE,qDAAqD,CACxD,CAAC;gBACN,CAAC;gBACD,oFAAoF;gBACpF,OAAO,CAAC,UAAmB,EAAiB,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;YACzF,CAAC;SACJ,CAAC,CAAC;IACP,CAAC;CACJ,CAAA;AA9BY,0DAAuB;kCAAvB,uBAAuB;IAFnC,IAAA,wCAAyB,GAAE;IAC3B,IAAA,sBAAU,GAAE;IAKJ,mBAAA,IAAA,kBAAM,EAAC,uBAAW,CAAC,CAAA;6CAA2B,uBAAW;GAJrD,uBAAuB,CA8BnC","sourcesContent":["import { inject, injectable } from 'inversify';\nimport { provideFrameworkSingleton, RequestContextReader, ContextMgr } from '@webpieces/core-context';\nimport { TaskInvoker } from './TaskTypes';\nimport { TaskProxyClient } from './TaskProxyClient';\nimport { ApiPrototype, TaskClientConfig } from './TaskClientConfig';\n\n/**\n * Properties DI frameworks / Promise checks / serializers probe on the proxy; return\n * undefined instead of treating them as endpoints. Mirrors http-client's ClientHttpFactory.\n */\nconst FRAMEWORK_INSPECTION_PROPERTIES = new Set<string>([\n 'constructor', 'prototype', '__proto__', 'name', 'then', 'catch', 'finally',\n 'toJSON', 'valueOf', 'toString', 'nodeType', 'tagName', '$$typeof',\n]);\n\n/**\n * ClientCloudTasksFactory - builds Cloud Tasks enqueue clients from a shared @PubSub API\n * contract. The fire-and-forget twin of http-client's ClientHttpFactory.\n *\n * Calling a method on the returned client ENQUEUES a task (it does not call remotely);\n * the task is later delivered to the same endpoint's controller through the full server\n * filter chain. A service just asks for a typed client:\n *\n * ```typescript\n * const emailTasks = factory.createClient(EmailApi, new TaskClientConfig('email-svc'));\n * await scheduler.addToQueue(() => emailTasks.sendEmail(req), { dedupName });\n * ```\n *\n * The factory holds the COLLABORATORS every client shares (the bound TaskInvoker, and a\n * context-propagating ContextMgr); {@link TaskClientConfig} holds only that one client's\n * STATE (which Cloud Run service the task is delivered to).\n *\n * Unlike http-client — which Angular bundles into a browser and which therefore must stay\n * DI-agnostic — this package is node-only, so the factory IS the inversify entry point and\n * the ContextMgr is a fixed field: RequestContextReader is the one and only right reader.\n */\n@provideFrameworkSingleton()\n@injectable()\nexport class ClientCloudTasksFactory {\n private readonly contextMgr = new ContextMgr(new RequestContextReader());\n\n constructor(\n @inject(TaskInvoker) private readonly invoker: TaskInvoker,\n ) {}\n\n /** Typed enqueue client for a @PubSub contract, delivered to `config.gcpCloudRunSvcName`. */\n createClient<T extends object>(apiClass: ApiPrototype<T>, config: TaskClientConfig): T {\n // TaskProxyClient owns @PubSub validation + plan building from the contract's\n // decorators. It is the @DocumentDesign design root for this package.\n const proxyClient = new TaskProxyClient(apiClass, config, this.invoker, this.contextMgr);\n\n return new Proxy({} as T, {\n // webpieces-disable no-any-unknown -- proxy get trap returns either an endpoint method or undefined\n get(target: T, prop: string | symbol): unknown {\n if (typeof prop !== 'string' || FRAMEWORK_INSPECTION_PROPERTIES.has(prop)) {\n return undefined;\n }\n if (!proxyClient.hasEndpoint(prop)) {\n throw new Error(\n `No @PubSub endpoint '${prop}' on ${apiClass.name || 'Unknown'}. ` +\n `Check for typos or a missing @Endpoint() decorator.`,\n );\n }\n // webpieces-disable no-any-unknown -- request DTO type is erased at the proxy layer\n return (requestDto: unknown): Promise<void> => proxyClient.enqueue(prop, requestDto);\n },\n });\n }\n}\n"]}
@@ -0,0 +1,20 @@
1
+ /** Constructor whose prototype is T (the abstract @PubSub API class). */
2
+ export type ApiPrototype<T> = Function & {
3
+ prototype: T;
4
+ };
5
+ /**
6
+ * Per-client STATE for a Cloud Tasks enqueue client — nothing else.
7
+ *
8
+ * Only the callee's Cloud Run service name. The base URL is derived from it at enqueue
9
+ * time by `getCloudRunUrl`, which honours a `CLOUD_RUN_URL_<UPPER_SNAKE_NAME>` env
10
+ * override (local multi-service runs, integration tests) and falls back off-GCP to
11
+ * `http://<svc>.localhost.invalid`. So there is no separate "fixed URL" client shape.
12
+ *
13
+ * Collaborators (TaskInvoker, ContextMgr) are NOT config: they are dependencies of
14
+ * {@link ClientCloudTasksFactory} and shared by every client it builds.
15
+ */
16
+ export declare class TaskClientConfig {
17
+ /** The callee's Cloud Run service name (e.g. 'email-svc'). */
18
+ gcpCloudRunSvcName: string;
19
+ constructor(gcpCloudRunSvcName: string);
20
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TaskClientConfig = void 0;
4
+ /**
5
+ * Per-client STATE for a Cloud Tasks enqueue client — nothing else.
6
+ *
7
+ * Only the callee's Cloud Run service name. The base URL is derived from it at enqueue
8
+ * time by `getCloudRunUrl`, which honours a `CLOUD_RUN_URL_<UPPER_SNAKE_NAME>` env
9
+ * override (local multi-service runs, integration tests) and falls back off-GCP to
10
+ * `http://<svc>.localhost.invalid`. So there is no separate "fixed URL" client shape.
11
+ *
12
+ * Collaborators (TaskInvoker, ContextMgr) are NOT config: they are dependencies of
13
+ * {@link ClientCloudTasksFactory} and shared by every client it builds.
14
+ */
15
+ class TaskClientConfig {
16
+ /** The callee's Cloud Run service name (e.g. 'email-svc'). */
17
+ gcpCloudRunSvcName;
18
+ constructor(gcpCloudRunSvcName) {
19
+ this.gcpCloudRunSvcName = gcpCloudRunSvcName;
20
+ }
21
+ }
22
+ exports.TaskClientConfig = TaskClientConfig;
23
+ //# sourceMappingURL=TaskClientConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TaskClientConfig.js","sourceRoot":"","sources":["../../../../../packages/cloud/cloudtasks-client/src/TaskClientConfig.ts"],"names":[],"mappings":";;;AAGA;;;;;;;;;;GAUG;AACH,MAAa,gBAAgB;IACzB,8DAA8D;IAC9D,kBAAkB,CAAS;IAE3B,YAAY,kBAA0B;QAClC,IAAI,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;IACjD,CAAC;CACJ;AAPD,4CAOC","sourcesContent":["/** Constructor whose prototype is T (the abstract @PubSub API class). */\nexport type ApiPrototype<T> = Function & { prototype: T };\n\n/**\n * Per-client STATE for a Cloud Tasks enqueue client — nothing else.\n *\n * Only the callee's Cloud Run service name. The base URL is derived from it at enqueue\n * time by `getCloudRunUrl`, which honours a `CLOUD_RUN_URL_<UPPER_SNAKE_NAME>` env\n * override (local multi-service runs, integration tests) and falls back off-GCP to\n * `http://<svc>.localhost.invalid`. So there is no separate \"fixed URL\" client shape.\n *\n * Collaborators (TaskInvoker, ContextMgr) are NOT config: they are dependencies of\n * {@link ClientCloudTasksFactory} and shared by every client it builds.\n */\nexport class TaskClientConfig {\n /** The callee's Cloud Run service name (e.g. 'email-svc'). */\n gcpCloudRunSvcName: string;\n\n constructor(gcpCloudRunSvcName: string) {\n this.gcpCloudRunSvcName = gcpCloudRunSvcName;\n }\n}\n"]}
@@ -0,0 +1,37 @@
1
+ import { ContextMgr } from '@webpieces/core-context';
2
+ import { TaskInvoker } from './TaskTypes';
3
+ import { ApiPrototype, TaskClientConfig } from './TaskClientConfig';
4
+ /**
5
+ * TaskProxyClient - the enqueue engine behind one @PubSub API contract's client proxy.
6
+ *
7
+ * The fire-and-forget twin of http-client's ProxyClient, and the @DocumentDesign design
8
+ * root for this package: its constructor params ARE the enqueue client's dependency graph.
9
+ * Built by {@link ClientCloudTasksFactory} (one per API contract), it owns:
10
+ * - @ApiPath / @PubSub convention validation + the endpoint plans from the contract's decorators
11
+ * - Resolving the callee's Cloud Run base URL from the service name
12
+ * - Context propagation onto the task headers (MINUS the caller's auth headers)
13
+ * - Handing a fully-built TaskRequest to the bound {@link TaskInvoker}
14
+ *
15
+ * Calling an endpoint ENQUEUES a task (it does not call remotely); the task is later
16
+ * delivered to the same endpoint's controller through the full server filter chain.
17
+ */
18
+ export declare class TaskProxyClient {
19
+ private config;
20
+ private invoker;
21
+ private contextMgr;
22
+ private plans;
23
+ private apiName;
24
+ constructor(apiClass: ApiPrototype<object>, config: TaskClientConfig, invoker: TaskInvoker, contextMgr: ContextMgr);
25
+ /** Check whether the contract declares a @PubSub endpoint with this method name. */
26
+ hasEndpoint(methodName: string): boolean;
27
+ /**
28
+ * Enqueue one task for the named endpoint. Must run inside a CloudTaskScheduler lambda
29
+ * (which supplies the ScheduleInfo) within an active RequestContext, e.g.:
30
+ * scheduler.addToQueue(() => taskClient.foo(req), { dedupName });
31
+ */
32
+ enqueue(methodName: string, requestDto: unknown): Promise<void>;
33
+ /** Endpoint name -> its resolved path / queue / auth mode, read once from the decorators. */
34
+ private buildPlans;
35
+ /** Transferred context keys (txId/requestId/tenant…) MINUS the caller's auth credentials. */
36
+ private buildContextHeaders;
37
+ }
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TaskProxyClient = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const core_util_1 = require("@webpieces/core-util");
6
+ const core_context_1 = require("@webpieces/core-context");
7
+ const gcp_identity_1 = require("@webpieces/gcp-identity");
8
+ const TaskTypes_1 = require("./TaskTypes");
9
+ const ScheduleContext_1 = require("./ScheduleContext");
10
+ const TaskClientConfig_1 = require("./TaskClientConfig");
11
+ const log = core_util_1.LogManager.getLogger('TaskProxyClient');
12
+ /**
13
+ * Auth headers are NEVER propagated from the caller's context onto an enqueued task:
14
+ * the caller's inbound user JWT / secret must not leak to an internal service, and
15
+ * the invoker mints fresh delivery auth (OIDC / shared-secret) per the endpoint's mode.
16
+ */
17
+ const AUTH_HEADER_NAMES = new Set(['authorization', 'x-webpieces-shared-secret']);
18
+ /** Per-endpoint routing plan resolved once from the contract's decorators. */
19
+ class EndpointPlan {
20
+ path;
21
+ queueName;
22
+ authMode;
23
+ constructor(path, queueName, authMode) {
24
+ this.path = path;
25
+ this.queueName = queueName;
26
+ this.authMode = authMode;
27
+ }
28
+ }
29
+ /**
30
+ * TaskProxyClient - the enqueue engine behind one @PubSub API contract's client proxy.
31
+ *
32
+ * The fire-and-forget twin of http-client's ProxyClient, and the @DocumentDesign design
33
+ * root for this package: its constructor params ARE the enqueue client's dependency graph.
34
+ * Built by {@link ClientCloudTasksFactory} (one per API contract), it owns:
35
+ * - @ApiPath / @PubSub convention validation + the endpoint plans from the contract's decorators
36
+ * - Resolving the callee's Cloud Run base URL from the service name
37
+ * - Context propagation onto the task headers (MINUS the caller's auth headers)
38
+ * - Handing a fully-built TaskRequest to the bound {@link TaskInvoker}
39
+ *
40
+ * Calling an endpoint ENQUEUES a task (it does not call remotely); the task is later
41
+ * delivered to the same endpoint's controller through the full server filter chain.
42
+ */
43
+ let TaskProxyClient = class TaskProxyClient {
44
+ config;
45
+ invoker;
46
+ contextMgr;
47
+ plans;
48
+ apiName;
49
+ constructor(apiClass, config, invoker, contextMgr) {
50
+ this.config = config;
51
+ this.invoker = invoker;
52
+ this.contextMgr = contextMgr;
53
+ if (!(0, core_util_1.isApiPath)(apiClass)) {
54
+ throw new Error(`Class ${apiClass.name || 'Unknown'} must be decorated with @ApiPath()`);
55
+ }
56
+ (0, core_util_1.assertPubSubConventions)(apiClass);
57
+ (0, core_util_1.assertEveryEndpointHasAuthMode)(apiClass);
58
+ this.apiName = apiClass.name || 'UnknownApi';
59
+ this.plans = this.buildPlans(apiClass);
60
+ }
61
+ /** Check whether the contract declares a @PubSub endpoint with this method name. */
62
+ hasEndpoint(methodName) {
63
+ return this.plans.has(methodName);
64
+ }
65
+ /**
66
+ * Enqueue one task for the named endpoint. Must run inside a CloudTaskScheduler lambda
67
+ * (which supplies the ScheduleInfo) within an active RequestContext, e.g.:
68
+ * scheduler.addToQueue(() => taskClient.foo(req), { dedupName });
69
+ */
70
+ // webpieces-disable no-any-unknown -- the request DTO's type is erased at the proxy boundary
71
+ async enqueue(methodName, requestDto) {
72
+ const plan = this.plans.get(methodName);
73
+ if (!plan) {
74
+ throw new Error(`No @PubSub endpoint '${methodName}' on ${this.apiName}`);
75
+ }
76
+ const frame = (0, ScheduleContext_1.currentScheduleFrame)();
77
+ if (!frame) {
78
+ throw new Error('Cloud task enqueue must run inside a CloudTaskScheduler lambda, e.g. ' +
79
+ 'scheduler.addToQueue(() => taskClient.method(req), { dedupName }).');
80
+ }
81
+ // Resolved lazily (not at client construction) so building a client stays synchronous.
82
+ // Every metadata read beneath getCloudRunUrl is memoized process-wide, so only the
83
+ // first enqueue in the process pays a lookup.
84
+ const targetUrl = await (0, gcp_identity_1.getCloudRunUrl)(this.config.gcpCloudRunSvcName);
85
+ const request = new TaskTypes_1.TaskRequest(targetUrl, plan.path, plan.queueName, requestDto, this.buildContextHeaders(), plan.authMode, frame.info ?? new TaskTypes_1.ScheduleInfo());
86
+ log.debug(`enqueue task ${plan.queueName} -> ${targetUrl}${plan.path}`);
87
+ frame.jobRef = await this.invoker.enqueue(request);
88
+ }
89
+ /** Endpoint name -> its resolved path / queue / auth mode, read once from the decorators. */
90
+ buildPlans(apiClass) {
91
+ const basePath = (0, core_util_1.getApiPath)(apiClass) ?? '';
92
+ const endpoints = (0, core_util_1.getEndpoints)(apiClass) ?? {};
93
+ const plans = new Map();
94
+ for (const methodName of Object.keys(endpoints)) {
95
+ const authMode = (0, core_util_1.getAuthMode)(apiClass, methodName);
96
+ if (!authMode) {
97
+ throw new Error(`Endpoint '${methodName}' on ${this.apiName} has no auth mode`);
98
+ }
99
+ const plan = new EndpointPlan(basePath + endpoints[methodName], (0, core_util_1.getQueueName)(apiClass, methodName), authMode);
100
+ plans.set(methodName, plan);
101
+ }
102
+ return plans;
103
+ }
104
+ /** Transferred context keys (txId/requestId/tenant…) MINUS the caller's auth credentials. */
105
+ buildContextHeaders() {
106
+ const headers = new Map();
107
+ for (const entry of this.contextMgr.buildOutboundHeaders().entries()) {
108
+ if (!AUTH_HEADER_NAMES.has(entry[0].toLowerCase())) {
109
+ headers.set(entry[0], entry[1]);
110
+ }
111
+ }
112
+ return headers;
113
+ }
114
+ };
115
+ exports.TaskProxyClient = TaskProxyClient;
116
+ exports.TaskProxyClient = TaskProxyClient = tslib_1.__decorate([
117
+ (0, core_util_1.DocumentDesign)(),
118
+ tslib_1.__metadata("design:paramtypes", [Object, TaskClientConfig_1.TaskClientConfig,
119
+ TaskTypes_1.TaskInvoker,
120
+ core_context_1.ContextMgr])
121
+ ], TaskProxyClient);
122
+ //# sourceMappingURL=TaskProxyClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TaskProxyClient.js","sourceRoot":"","sources":["../../../../../packages/cloud/cloudtasks-client/src/TaskProxyClient.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n isApiPath,\n getApiPath,\n getEndpoints,\n getAuthMode,\n getQueueName,\n assertPubSubConventions,\n assertEveryEndpointHasAuthMode,\n AuthMode,\n DocumentDesign,\n LogManager,\n} from '@webpieces/core-util';\nimport { ContextMgr } from '@webpieces/core-context';\nimport { getCloudRunUrl } from '@webpieces/gcp-identity';\nimport { TaskInvoker, TaskRequest, ScheduleInfo } from './TaskTypes';\nimport { currentScheduleFrame } from './ScheduleContext';\nimport { ApiPrototype, TaskClientConfig } from './TaskClientConfig';\n\nconst log = LogManager.getLogger('TaskProxyClient');\n\n/**\n * Auth headers are NEVER propagated from the caller's context onto an enqueued task:\n * the caller's inbound user JWT / secret must not leak to an internal service, and\n * the invoker mints fresh delivery auth (OIDC / shared-secret) per the endpoint's mode.\n */\nconst AUTH_HEADER_NAMES = new Set<string>(['authorization', 'x-webpieces-shared-secret']);\n\n/** Per-endpoint routing plan resolved once from the contract's decorators. */\nclass EndpointPlan {\n path: string;\n queueName: string;\n authMode: AuthMode;\n\n constructor(path: string, queueName: string, authMode: AuthMode) {\n this.path = path;\n this.queueName = queueName;\n this.authMode = authMode;\n }\n}\n\n/**\n * TaskProxyClient - the enqueue engine behind one @PubSub API contract's client proxy.\n *\n * The fire-and-forget twin of http-client's ProxyClient, and the @DocumentDesign design\n * root for this package: its constructor params ARE the enqueue client's dependency graph.\n * Built by {@link ClientCloudTasksFactory} (one per API contract), it owns:\n * - @ApiPath / @PubSub convention validation + the endpoint plans from the contract's decorators\n * - Resolving the callee's Cloud Run base URL from the service name\n * - Context propagation onto the task headers (MINUS the caller's auth headers)\n * - Handing a fully-built TaskRequest to the bound {@link TaskInvoker}\n *\n * Calling an endpoint ENQUEUES a task (it does not call remotely); the task is later\n * delivered to the same endpoint's controller through the full server filter chain.\n */\n@DocumentDesign()\nexport class TaskProxyClient {\n private plans: Map<string, EndpointPlan>;\n private apiName: string;\n\n constructor(\n apiClass: ApiPrototype<object>,\n private config: TaskClientConfig,\n private invoker: TaskInvoker,\n private contextMgr: ContextMgr,\n ) {\n if (!isApiPath(apiClass)) {\n throw new Error(`Class ${apiClass.name || 'Unknown'} must be decorated with @ApiPath()`);\n }\n assertPubSubConventions(apiClass);\n assertEveryEndpointHasAuthMode(apiClass);\n\n this.apiName = apiClass.name || 'UnknownApi';\n this.plans = this.buildPlans(apiClass);\n }\n\n /** Check whether the contract declares a @PubSub endpoint with this method name. */\n hasEndpoint(methodName: string): boolean {\n return this.plans.has(methodName);\n }\n\n /**\n * Enqueue one task for the named endpoint. Must run inside a CloudTaskScheduler lambda\n * (which supplies the ScheduleInfo) within an active RequestContext, e.g.:\n * scheduler.addToQueue(() => taskClient.foo(req), { dedupName });\n */\n // webpieces-disable no-any-unknown -- the request DTO's type is erased at the proxy boundary\n async enqueue(methodName: string, requestDto: unknown): Promise<void> {\n const plan = this.plans.get(methodName);\n if (!plan) {\n throw new Error(`No @PubSub endpoint '${methodName}' on ${this.apiName}`);\n }\n\n const frame = currentScheduleFrame();\n if (!frame) {\n throw new Error(\n 'Cloud task enqueue must run inside a CloudTaskScheduler lambda, e.g. ' +\n 'scheduler.addToQueue(() => taskClient.method(req), { dedupName }).',\n );\n }\n\n // Resolved lazily (not at client construction) so building a client stays synchronous.\n // Every metadata read beneath getCloudRunUrl is memoized process-wide, so only the\n // first enqueue in the process pays a lookup.\n const targetUrl = await getCloudRunUrl(this.config.gcpCloudRunSvcName);\n\n const request = new TaskRequest(\n targetUrl,\n plan.path,\n plan.queueName,\n requestDto,\n this.buildContextHeaders(),\n plan.authMode,\n frame.info ?? new ScheduleInfo(),\n );\n\n log.debug(`enqueue task ${plan.queueName} -> ${targetUrl}${plan.path}`);\n frame.jobRef = await this.invoker.enqueue(request);\n }\n\n /** Endpoint name -> its resolved path / queue / auth mode, read once from the decorators. */\n private buildPlans(apiClass: ApiPrototype<object>): Map<string, EndpointPlan> {\n const basePath = getApiPath(apiClass) ?? '';\n const endpoints = getEndpoints(apiClass) ?? {};\n const plans = new Map<string, EndpointPlan>();\n\n for (const methodName of Object.keys(endpoints)) {\n const authMode = getAuthMode(apiClass, methodName);\n if (!authMode) {\n throw new Error(`Endpoint '${methodName}' on ${this.apiName} has no auth mode`);\n }\n const plan = new EndpointPlan(\n basePath + endpoints[methodName],\n getQueueName(apiClass, methodName),\n authMode,\n );\n plans.set(methodName, plan);\n }\n return plans;\n }\n\n /** Transferred context keys (txId/requestId/tenant…) MINUS the caller's auth credentials. */\n private buildContextHeaders(): Map<string, string> {\n const headers = new Map<string, string>();\n for (const entry of this.contextMgr.buildOutboundHeaders().entries()) {\n if (!AUTH_HEADER_NAMES.has(entry[0].toLowerCase())) {\n headers.set(entry[0], entry[1]);\n }\n }\n return headers;\n }\n}\n"]}
package/src/index.d.ts CHANGED
@@ -7,8 +7,10 @@
7
7
  * delivered to the same endpoint through the full server filter chain.
8
8
  */
9
9
  export { ScheduleInfo, JobReference, TaskRequest, TaskInvoker, } from './TaskTypes';
10
- export { createTaskClient, TaskClientConfig } from './TaskClientFactory';
11
- export { TaskClientCreator } from './TaskClientCreator';
10
+ export { ClientCloudTasksFactory } from './ClientCloudTasksFactory';
11
+ export { TaskProxyClient } from './TaskProxyClient';
12
+ export { TaskClientConfig } from './TaskClientConfig';
13
+ export type { ApiPrototype } from './TaskClientConfig';
12
14
  export { CloudTaskScheduler, ScheduleOptions } from './CloudTaskScheduler';
13
15
  export { InMemoryTaskInvoker } from './InMemoryTaskInvoker';
14
16
  export { GcpTaskInvoker } from './GcpTaskInvoker';
package/src/index.js CHANGED
@@ -8,17 +8,18 @@
8
8
  * delivered to the same endpoint through the full server filter chain.
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.clearScheduleFrame = exports.currentScheduleFrame = exports.setScheduleFrame = exports.ScheduleFrame = exports.GcpTaskInvoker = exports.InMemoryTaskInvoker = exports.ScheduleOptions = exports.CloudTaskScheduler = exports.TaskClientCreator = exports.TaskClientConfig = exports.createTaskClient = exports.TaskInvoker = exports.TaskRequest = exports.JobReference = exports.ScheduleInfo = void 0;
11
+ exports.clearScheduleFrame = exports.currentScheduleFrame = exports.setScheduleFrame = exports.ScheduleFrame = exports.GcpTaskInvoker = exports.InMemoryTaskInvoker = exports.ScheduleOptions = exports.CloudTaskScheduler = exports.TaskClientConfig = exports.TaskProxyClient = exports.ClientCloudTasksFactory = exports.TaskInvoker = exports.TaskRequest = exports.JobReference = exports.ScheduleInfo = void 0;
12
12
  var TaskTypes_1 = require("./TaskTypes");
13
13
  Object.defineProperty(exports, "ScheduleInfo", { enumerable: true, get: function () { return TaskTypes_1.ScheduleInfo; } });
14
14
  Object.defineProperty(exports, "JobReference", { enumerable: true, get: function () { return TaskTypes_1.JobReference; } });
15
15
  Object.defineProperty(exports, "TaskRequest", { enumerable: true, get: function () { return TaskTypes_1.TaskRequest; } });
16
16
  Object.defineProperty(exports, "TaskInvoker", { enumerable: true, get: function () { return TaskTypes_1.TaskInvoker; } });
17
- var TaskClientFactory_1 = require("./TaskClientFactory");
18
- Object.defineProperty(exports, "createTaskClient", { enumerable: true, get: function () { return TaskClientFactory_1.createTaskClient; } });
19
- Object.defineProperty(exports, "TaskClientConfig", { enumerable: true, get: function () { return TaskClientFactory_1.TaskClientConfig; } });
20
- var TaskClientCreator_1 = require("./TaskClientCreator");
21
- Object.defineProperty(exports, "TaskClientCreator", { enumerable: true, get: function () { return TaskClientCreator_1.TaskClientCreator; } });
17
+ var ClientCloudTasksFactory_1 = require("./ClientCloudTasksFactory");
18
+ Object.defineProperty(exports, "ClientCloudTasksFactory", { enumerable: true, get: function () { return ClientCloudTasksFactory_1.ClientCloudTasksFactory; } });
19
+ var TaskProxyClient_1 = require("./TaskProxyClient");
20
+ Object.defineProperty(exports, "TaskProxyClient", { enumerable: true, get: function () { return TaskProxyClient_1.TaskProxyClient; } });
21
+ var TaskClientConfig_1 = require("./TaskClientConfig");
22
+ Object.defineProperty(exports, "TaskClientConfig", { enumerable: true, get: function () { return TaskClientConfig_1.TaskClientConfig; } });
22
23
  var CloudTaskScheduler_1 = require("./CloudTaskScheduler");
23
24
  Object.defineProperty(exports, "CloudTaskScheduler", { enumerable: true, get: function () { return CloudTaskScheduler_1.CloudTaskScheduler; } });
24
25
  Object.defineProperty(exports, "ScheduleOptions", { enumerable: true, get: function () { return CloudTaskScheduler_1.ScheduleOptions; } });
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/cloud/cloudtasks-client/src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAEH,yCAKqB;AAJjB,yGAAA,YAAY,OAAA;AACZ,yGAAA,YAAY,OAAA;AACZ,wGAAA,WAAW,OAAA;AACX,wGAAA,WAAW,OAAA;AAEf,yDAAyE;AAAhE,qHAAA,gBAAgB,OAAA;AAAE,qHAAA,gBAAgB,OAAA;AAC3C,yDAAwD;AAA/C,sHAAA,iBAAiB,OAAA;AAC1B,2DAA2E;AAAlE,wHAAA,kBAAkB,OAAA;AAAE,qHAAA,eAAe,OAAA;AAC5C,2FAA2F;AAC3F,6DAA4D;AAAnD,0HAAA,mBAAmB,OAAA;AAC5B,mDAAkD;AAAzC,gHAAA,cAAc,OAAA;AACvB,4FAA4F;AAC5F,2FAA2F;AAC3F,qDAK2B;AAJvB,gHAAA,aAAa,OAAA;AACb,mHAAA,gBAAgB,OAAA;AAChB,uHAAA,oBAAoB,OAAA;AACpB,qHAAA,kBAAkB,OAAA","sourcesContent":["/**\n * @webpieces/cloudtasks-client\n *\n * Cloud Tasks enqueue client generated from a shared @PubSub API contract — the\n * fire-and-forget twin of @webpieces/http-client. The client and the controller\n * share ONE abstract API class; calling a method enqueues a task that is later\n * delivered to the same endpoint through the full server filter chain.\n */\n\nexport {\n ScheduleInfo,\n JobReference,\n TaskRequest,\n TaskInvoker,\n} from './TaskTypes';\nexport { createTaskClient, TaskClientConfig } from './TaskClientFactory';\nexport { TaskClientCreator } from './TaskClientCreator';\nexport { CloudTaskScheduler, ScheduleOptions } from './CloudTaskScheduler';\n// The two task transports (local HTTP-queue + remote GCP), both delivering over real HTTP.\nexport { InMemoryTaskInvoker } from './InMemoryTaskInvoker';\nexport { GcpTaskInvoker } from './GcpTaskInvoker';\n// NOTE: server-side delivery-auth is enforced by the framework AuthFilter (AuthMode-driven)\n// in @webpieces/http-routing — a client library has no server filters / routing machinery.\nexport {\n ScheduleFrame,\n setScheduleFrame,\n currentScheduleFrame,\n clearScheduleFrame,\n} from './ScheduleContext';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/cloud/cloudtasks-client/src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAEH,yCAKqB;AAJjB,yGAAA,YAAY,OAAA;AACZ,yGAAA,YAAY,OAAA;AACZ,wGAAA,WAAW,OAAA;AACX,wGAAA,WAAW,OAAA;AAEf,qEAAoE;AAA3D,kIAAA,uBAAuB,OAAA;AAChC,qDAAoD;AAA3C,kHAAA,eAAe,OAAA;AACxB,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AAEzB,2DAA2E;AAAlE,wHAAA,kBAAkB,OAAA;AAAE,qHAAA,eAAe,OAAA;AAC5C,2FAA2F;AAC3F,6DAA4D;AAAnD,0HAAA,mBAAmB,OAAA;AAC5B,mDAAkD;AAAzC,gHAAA,cAAc,OAAA;AACvB,4FAA4F;AAC5F,2FAA2F;AAC3F,qDAK2B;AAJvB,gHAAA,aAAa,OAAA;AACb,mHAAA,gBAAgB,OAAA;AAChB,uHAAA,oBAAoB,OAAA;AACpB,qHAAA,kBAAkB,OAAA","sourcesContent":["/**\n * @webpieces/cloudtasks-client\n *\n * Cloud Tasks enqueue client generated from a shared @PubSub API contract — the\n * fire-and-forget twin of @webpieces/http-client. The client and the controller\n * share ONE abstract API class; calling a method enqueues a task that is later\n * delivered to the same endpoint through the full server filter chain.\n */\n\nexport {\n ScheduleInfo,\n JobReference,\n TaskRequest,\n TaskInvoker,\n} from './TaskTypes';\nexport { ClientCloudTasksFactory } from './ClientCloudTasksFactory';\nexport { TaskProxyClient } from './TaskProxyClient';\nexport { TaskClientConfig } from './TaskClientConfig';\nexport type { ApiPrototype } from './TaskClientConfig';\nexport { CloudTaskScheduler, ScheduleOptions } from './CloudTaskScheduler';\n// The two task transports (local HTTP-queue + remote GCP), both delivering over real HTTP.\nexport { InMemoryTaskInvoker } from './InMemoryTaskInvoker';\nexport { GcpTaskInvoker } from './GcpTaskInvoker';\n// NOTE: server-side delivery-auth is enforced by the framework AuthFilter (AuthMode-driven)\n// in @webpieces/http-routing — a client library has no server filters / routing machinery.\nexport {\n ScheduleFrame,\n setScheduleFrame,\n currentScheduleFrame,\n clearScheduleFrame,\n} from './ScheduleContext';\n"]}
@@ -1,22 +0,0 @@
1
- import { TaskInvoker } from './TaskTypes';
2
- /** Constructor whose prototype is T (the abstract @PubSub API class). */
3
- type ApiPrototype<T> = Function & {
4
- prototype: T;
5
- };
6
- /**
7
- * Injectable factory for Cloud Tasks enqueue clients — the twin of http-client's
8
- * RpcClientCreator. Resolves the bound TaskInvoker + a context-propagating ContextMgr
9
- * so a service just asks for a typed client:
10
- *
11
- * const emailTasks = creator.createClientOnService(EmailApi, 'email-svc'); // self/other svc
12
- */
13
- export declare class TaskClientCreator {
14
- private readonly invoker;
15
- constructor(invoker: TaskInvoker);
16
- /** Enqueue client whose delivery URL is another Cloud Run service (by name). */
17
- createClientOnService<T extends object>(apiClass: ApiPrototype<T>, serviceName: string): T;
18
- /** Enqueue client whose delivery URL is a fixed base URL. */
19
- createClientOnUrl<T extends object>(apiClass: ApiPrototype<T>, url: string): T;
20
- private buildContextMgr;
21
- }
22
- export {};
@@ -1,45 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TaskClientCreator = void 0;
4
- const tslib_1 = require("tslib");
5
- const inversify_1 = require("inversify");
6
- const core_context_1 = require("@webpieces/core-context");
7
- const core_util_1 = require("@webpieces/core-util");
8
- const gcp_identity_1 = require("@webpieces/gcp-identity");
9
- const TaskTypes_1 = require("./TaskTypes");
10
- const TaskClientFactory_1 = require("./TaskClientFactory");
11
- /**
12
- * Injectable factory for Cloud Tasks enqueue clients — the twin of http-client's
13
- * RpcClientCreator. Resolves the bound TaskInvoker + a context-propagating ContextMgr
14
- * so a service just asks for a typed client:
15
- *
16
- * const emailTasks = creator.createClientOnService(EmailApi, 'email-svc'); // self/other svc
17
- */
18
- let TaskClientCreator = class TaskClientCreator {
19
- invoker;
20
- constructor(invoker) {
21
- this.invoker = invoker;
22
- }
23
- /** Enqueue client whose delivery URL is another Cloud Run service (by name). */
24
- createClientOnService(apiClass, serviceName) {
25
- const config = new TaskClientFactory_1.TaskClientConfig(() => (0, gcp_identity_1.getCloudRunUrl)(serviceName), this.invoker, this.buildContextMgr());
26
- return (0, TaskClientFactory_1.createTaskClient)(apiClass, config);
27
- }
28
- /** Enqueue client whose delivery URL is a fixed base URL. */
29
- createClientOnUrl(apiClass, url) {
30
- const config = new TaskClientFactory_1.TaskClientConfig(url, this.invoker, this.buildContextMgr());
31
- return (0, TaskClientFactory_1.createTaskClient)(apiClass, config);
32
- }
33
- buildContextMgr() {
34
- return new core_context_1.ContextMgr(new core_context_1.RequestContextReader());
35
- }
36
- };
37
- exports.TaskClientCreator = TaskClientCreator;
38
- exports.TaskClientCreator = TaskClientCreator = tslib_1.__decorate([
39
- (0, core_util_1.DocumentDesign)(),
40
- (0, core_context_1.provideFrameworkSingleton)(),
41
- (0, inversify_1.injectable)(),
42
- tslib_1.__param(0, (0, inversify_1.inject)(TaskTypes_1.TaskInvoker)),
43
- tslib_1.__metadata("design:paramtypes", [TaskTypes_1.TaskInvoker])
44
- ], TaskClientCreator);
45
- //# sourceMappingURL=TaskClientCreator.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"TaskClientCreator.js","sourceRoot":"","sources":["../../../../../packages/cloud/cloudtasks-client/src/TaskClientCreator.ts"],"names":[],"mappings":";;;;AAAA,yCAA+C;AAC/C,0DAAsG;AACtG,oDAAsD;AACtD,0DAAyD;AACzD,2CAA0C;AAC1C,2DAAyE;AAKzE;;;;;;GAMG;AAII,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAEgB;IAD1C,YAC0C,OAAoB;QAApB,YAAO,GAAP,OAAO,CAAa;IAC3D,CAAC;IAEJ,gFAAgF;IAChF,qBAAqB,CAAmB,QAAyB,EAAE,WAAmB;QAClF,MAAM,MAAM,GAAG,IAAI,oCAAgB,CAC/B,GAAG,EAAE,CAAC,IAAA,6BAAc,EAAC,WAAW,CAAC,EACjC,IAAI,CAAC,OAAO,EACZ,IAAI,CAAC,eAAe,EAAE,CACzB,CAAC;QACF,OAAO,IAAA,oCAAgB,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC9C,CAAC;IAED,6DAA6D;IAC7D,iBAAiB,CAAmB,QAAyB,EAAE,GAAW;QACtE,MAAM,MAAM,GAAG,IAAI,oCAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC;QAC/E,OAAO,IAAA,oCAAgB,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC9C,CAAC;IAEO,eAAe;QACnB,OAAO,IAAI,yBAAU,CAAC,IAAI,mCAAoB,EAAE,CAAC,CAAC;IACtD,CAAC;CACJ,CAAA;AAxBY,8CAAiB;4BAAjB,iBAAiB;IAH7B,IAAA,0BAAc,GAAE;IAChB,IAAA,wCAAyB,GAAE;IAC3B,IAAA,sBAAU,GAAE;IAGJ,mBAAA,IAAA,kBAAM,EAAC,uBAAW,CAAC,CAAA;6CAA2B,uBAAW;GAFrD,iBAAiB,CAwB7B","sourcesContent":["import { inject, injectable } from 'inversify';\nimport { provideFrameworkSingleton, RequestContextReader, ContextMgr } from '@webpieces/core-context';\nimport { DocumentDesign } from '@webpieces/core-util';\nimport { getCloudRunUrl } from '@webpieces/gcp-identity';\nimport { TaskInvoker } from './TaskTypes';\nimport { createTaskClient, TaskClientConfig } from './TaskClientFactory';\n\n/** Constructor whose prototype is T (the abstract @PubSub API class). */\ntype ApiPrototype<T> = Function & { prototype: T };\n\n/**\n * Injectable factory for Cloud Tasks enqueue clients — the twin of http-client's\n * RpcClientCreator. Resolves the bound TaskInvoker + a context-propagating ContextMgr\n * so a service just asks for a typed client:\n *\n * const emailTasks = creator.createClientOnService(EmailApi, 'email-svc'); // self/other svc\n */\n@DocumentDesign()\n@provideFrameworkSingleton()\n@injectable()\nexport class TaskClientCreator {\n constructor(\n @inject(TaskInvoker) private readonly invoker: TaskInvoker,\n ) {}\n\n /** Enqueue client whose delivery URL is another Cloud Run service (by name). */\n createClientOnService<T extends object>(apiClass: ApiPrototype<T>, serviceName: string): T {\n const config = new TaskClientConfig(\n () => getCloudRunUrl(serviceName),\n this.invoker,\n this.buildContextMgr(),\n );\n return createTaskClient(apiClass, config);\n }\n\n /** Enqueue client whose delivery URL is a fixed base URL. */\n createClientOnUrl<T extends object>(apiClass: ApiPrototype<T>, url: string): T {\n const config = new TaskClientConfig(url, this.invoker, this.buildContextMgr());\n return createTaskClient(apiClass, config);\n }\n\n private buildContextMgr(): ContextMgr {\n return new ContextMgr(new RequestContextReader());\n }\n}\n"]}
@@ -1,27 +0,0 @@
1
- import { ContextMgr } from '@webpieces/core-context';
2
- import { TaskInvoker } from './TaskTypes';
3
- /** Constructor whose prototype is T (the abstract API class). */
4
- type ApiPrototype<T> = Function & {
5
- prototype: T;
6
- };
7
- /** Configuration for an enqueue client. */
8
- export declare class TaskClientConfig {
9
- /** Callee base URL, or an async resolver (e.g. getCloudRunUrl(serviceName)). */
10
- targetUrl: string | (() => Promise<string>);
11
- /** The transport that enqueues the task (GcpTaskInvoker / InMemoryTaskInvoker). */
12
- invoker: TaskInvoker;
13
- /** Optional context propagation (txId/requestId/tenant…) onto the task headers. */
14
- contextMgr?: ContextMgr;
15
- constructor(targetUrl: string | (() => Promise<string>), invoker: TaskInvoker, contextMgr?: ContextMgr);
16
- }
17
- /**
18
- * Create a Cloud Tasks enqueue client from a shared @PubSub API contract. Calling a
19
- * method ENQUEUES a task (it does not call remotely); the task is later delivered to
20
- * the same endpoint's controller through the full server filter chain.
21
- *
22
- * Must be called inside a CloudTaskScheduler lambda (which supplies the ScheduleInfo)
23
- * within an active RequestContext, e.g.:
24
- * scheduler.addToQueue(() => taskClient.foo(req), { dedupName });
25
- */
26
- export declare function createTaskClient<T extends object>(apiClass: ApiPrototype<T>, config: TaskClientConfig): T;
27
- export {};
@@ -1,124 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TaskClientConfig = void 0;
4
- exports.createTaskClient = createTaskClient;
5
- const core_util_1 = require("@webpieces/core-util");
6
- const core_util_2 = require("@webpieces/core-util");
7
- const TaskTypes_1 = require("./TaskTypes");
8
- const ScheduleContext_1 = require("./ScheduleContext");
9
- const log = core_util_2.LogManager.getLogger('TaskClientFactory');
10
- /**
11
- * Auth headers are NEVER propagated from the caller's context onto an enqueued task:
12
- * the caller's inbound user JWT / secret must not leak to an internal service, and
13
- * the invoker mints fresh delivery auth (OIDC / shared-secret) per the endpoint's mode.
14
- */
15
- const AUTH_HEADER_NAMES = new Set(['authorization', 'x-webpieces-shared-secret']);
16
- /**
17
- * Properties DI frameworks / Promise checks / serializers probe on the proxy; return
18
- * undefined instead of treating them as endpoints. Mirrors http-client's ClientFactory.
19
- */
20
- const FRAMEWORK_INSPECTION_PROPERTIES = new Set([
21
- 'constructor', 'prototype', '__proto__', 'name', 'then', 'catch', 'finally',
22
- 'toJSON', 'valueOf', 'toString', 'nodeType', 'tagName', '$$typeof',
23
- ]);
24
- /** Configuration for an enqueue client. */
25
- class TaskClientConfig {
26
- /** Callee base URL, or an async resolver (e.g. getCloudRunUrl(serviceName)). */
27
- targetUrl;
28
- /** The transport that enqueues the task (GcpTaskInvoker / InMemoryTaskInvoker). */
29
- invoker;
30
- /** Optional context propagation (txId/requestId/tenant…) onto the task headers. */
31
- contextMgr;
32
- constructor(targetUrl, invoker, contextMgr) {
33
- this.targetUrl = targetUrl;
34
- this.invoker = invoker;
35
- this.contextMgr = contextMgr;
36
- }
37
- }
38
- exports.TaskClientConfig = TaskClientConfig;
39
- /** Per-endpoint routing plan resolved once from the contract's decorators. */
40
- class EndpointPlan {
41
- path;
42
- queueName;
43
- authMode;
44
- constructor(path, queueName, authMode) {
45
- this.path = path;
46
- this.queueName = queueName;
47
- this.authMode = authMode;
48
- }
49
- }
50
- /**
51
- * Create a Cloud Tasks enqueue client from a shared @PubSub API contract. Calling a
52
- * method ENQUEUES a task (it does not call remotely); the task is later delivered to
53
- * the same endpoint's controller through the full server filter chain.
54
- *
55
- * Must be called inside a CloudTaskScheduler lambda (which supplies the ScheduleInfo)
56
- * within an active RequestContext, e.g.:
57
- * scheduler.addToQueue(() => taskClient.foo(req), { dedupName });
58
- */
59
- function createTaskClient(apiClass, config) {
60
- if (!(0, core_util_1.isApiPath)(apiClass)) {
61
- throw new Error(`Class ${apiClass.name || 'Unknown'} must be decorated with @ApiPath()`);
62
- }
63
- (0, core_util_1.assertPubSubConventions)(apiClass);
64
- (0, core_util_1.assertEveryEndpointHasAuthMode)(apiClass);
65
- const basePath = (0, core_util_1.getApiPath)(apiClass) ?? '';
66
- const endpoints = (0, core_util_1.getEndpoints)(apiClass) ?? {};
67
- const plans = buildPlans(apiClass, basePath, endpoints);
68
- return new Proxy({}, {
69
- // webpieces-disable no-any-unknown -- proxy get trap returns either a method or undefined
70
- get(_target, prop) {
71
- if (typeof prop !== 'string' || FRAMEWORK_INSPECTION_PROPERTIES.has(prop)) {
72
- return undefined;
73
- }
74
- const plan = plans.get(prop);
75
- if (!plan) {
76
- throw new Error(`No @PubSub endpoint '${prop}' on ${apiClass.name || 'Unknown'}. ` +
77
- `Check for typos or a missing @Endpoint() decorator.`);
78
- }
79
- // webpieces-disable no-any-unknown -- request DTO type is erased at the proxy layer
80
- return (requestDto) => enqueue(config, plan, requestDto);
81
- },
82
- });
83
- }
84
- function buildPlans(apiClass, basePath, endpoints) {
85
- const plans = new Map();
86
- for (const methodName of Object.keys(endpoints)) {
87
- const authMode = (0, core_util_1.getAuthMode)(apiClass, methodName);
88
- if (!authMode) {
89
- throw new Error(`Endpoint '${methodName}' on ${apiClass.name} has no auth mode`);
90
- }
91
- const plan = new EndpointPlan(basePath + endpoints[methodName], (0, core_util_1.getQueueName)(apiClass, methodName), authMode);
92
- plans.set(methodName, plan);
93
- }
94
- return plans;
95
- }
96
- async function enqueue(config, plan,
97
- // webpieces-disable no-any-unknown -- request DTO type is erased at the proxy layer
98
- requestDto) {
99
- const frame = (0, ScheduleContext_1.currentScheduleFrame)();
100
- if (!frame) {
101
- throw new Error('Cloud task enqueue must run inside a CloudTaskScheduler lambda, e.g. ' +
102
- 'scheduler.addToQueue(() => taskClient.method(req), { dedupName }).');
103
- }
104
- const targetUrl = typeof config.targetUrl === 'string'
105
- ? config.targetUrl
106
- : await config.targetUrl();
107
- const contextHeaders = buildContextHeaders(config.contextMgr);
108
- const request = new TaskTypes_1.TaskRequest(targetUrl, plan.path, plan.queueName, requestDto, contextHeaders, plan.authMode, frame.info ?? new TaskTypes_1.ScheduleInfo());
109
- log.debug(`enqueue task ${plan.queueName} -> ${targetUrl}${plan.path}`);
110
- frame.jobRef = await config.invoker.enqueue(request);
111
- }
112
- function buildContextHeaders(contextMgr) {
113
- const headers = new Map();
114
- if (!contextMgr) {
115
- return headers;
116
- }
117
- for (const entry of contextMgr.buildOutboundHeaders().entries()) {
118
- if (!AUTH_HEADER_NAMES.has(entry[0].toLowerCase())) {
119
- headers.set(entry[0], entry[1]);
120
- }
121
- }
122
- return headers;
123
- }
124
- //# sourceMappingURL=TaskClientFactory.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"TaskClientFactory.js","sourceRoot":"","sources":["../../../../../packages/cloud/cloudtasks-client/src/TaskClientFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n isApiPath,\n getApiPath,\n getEndpoints,\n getAuthMode,\n getQueueName,\n assertPubSubConventions,\n assertEveryEndpointHasAuthMode,\n AuthMode,\n} from '@webpieces/core-util';\nimport { ContextMgr } from '@webpieces/core-context';\nimport { LogManager } from '@webpieces/core-util';\nimport { TaskInvoker, TaskRequest, ScheduleInfo } from './TaskTypes';\nimport { currentScheduleFrame } from './ScheduleContext';\n\nconst log = LogManager.getLogger('TaskClientFactory');\n\n/** Constructor whose prototype is T (the abstract API class). */\ntype ApiPrototype<T> = Function & { prototype: T };\n\n/**\n * Auth headers are NEVER propagated from the caller's context onto an enqueued task:\n * the caller's inbound user JWT / secret must not leak to an internal service, and\n * the invoker mints fresh delivery auth (OIDC / shared-secret) per the endpoint's mode.\n */\nconst AUTH_HEADER_NAMES = new Set<string>(['authorization', 'x-webpieces-shared-secret']);\n\n/**\n * Properties DI frameworks / Promise checks / serializers probe on the proxy; return\n * undefined instead of treating them as endpoints. Mirrors http-client's ClientFactory.\n */\nconst FRAMEWORK_INSPECTION_PROPERTIES = new Set<string>([\n 'constructor', 'prototype', '__proto__', 'name', 'then', 'catch', 'finally',\n 'toJSON', 'valueOf', 'toString', 'nodeType', 'tagName', '$$typeof',\n]);\n\n/** Configuration for an enqueue client. */\nexport class TaskClientConfig {\n /** Callee base URL, or an async resolver (e.g. getCloudRunUrl(serviceName)). */\n targetUrl: string | (() => Promise<string>);\n /** The transport that enqueues the task (GcpTaskInvoker / InMemoryTaskInvoker). */\n invoker: TaskInvoker;\n /** Optional context propagation (txId/requestId/tenant…) onto the task headers. */\n contextMgr?: ContextMgr;\n\n constructor(\n targetUrl: string | (() => Promise<string>),\n invoker: TaskInvoker,\n contextMgr?: ContextMgr,\n ) {\n this.targetUrl = targetUrl;\n this.invoker = invoker;\n this.contextMgr = contextMgr;\n }\n}\n\n/** Per-endpoint routing plan resolved once from the contract's decorators. */\nclass EndpointPlan {\n path: string;\n queueName: string;\n authMode: AuthMode;\n\n constructor(path: string, queueName: string, authMode: AuthMode) {\n this.path = path;\n this.queueName = queueName;\n this.authMode = authMode;\n }\n}\n\n/**\n * Create a Cloud Tasks enqueue client from a shared @PubSub API contract. Calling a\n * method ENQUEUES a task (it does not call remotely); the task is later delivered to\n * the same endpoint's controller through the full server filter chain.\n *\n * Must be called inside a CloudTaskScheduler lambda (which supplies the ScheduleInfo)\n * within an active RequestContext, e.g.:\n * scheduler.addToQueue(() => taskClient.foo(req), { dedupName });\n */\nexport function createTaskClient<T extends object>(\n apiClass: ApiPrototype<T>,\n config: TaskClientConfig,\n): T {\n if (!isApiPath(apiClass)) {\n throw new Error(`Class ${apiClass.name || 'Unknown'} must be decorated with @ApiPath()`);\n }\n assertPubSubConventions(apiClass);\n assertEveryEndpointHasAuthMode(apiClass);\n\n const basePath = getApiPath(apiClass) ?? '';\n const endpoints = getEndpoints(apiClass) ?? {};\n const plans = buildPlans(apiClass, basePath, endpoints);\n\n return new Proxy({} as T, {\n // webpieces-disable no-any-unknown -- proxy get trap returns either a method or undefined\n get(_target: T, prop: string | symbol): unknown {\n if (typeof prop !== 'string' || FRAMEWORK_INSPECTION_PROPERTIES.has(prop)) {\n return undefined;\n }\n const plan = plans.get(prop);\n if (!plan) {\n throw new Error(\n `No @PubSub endpoint '${prop}' on ${apiClass.name || 'Unknown'}. ` +\n `Check for typos or a missing @Endpoint() decorator.`,\n );\n }\n // webpieces-disable no-any-unknown -- request DTO type is erased at the proxy layer\n return (requestDto: unknown): Promise<void> => enqueue(config, plan, requestDto);\n },\n });\n}\n\nfunction buildPlans(\n apiClass: Function,\n basePath: string,\n endpoints: Record<string, string>,\n): Map<string, EndpointPlan> {\n const plans = new Map<string, EndpointPlan>();\n for (const methodName of Object.keys(endpoints)) {\n const authMode = getAuthMode(apiClass, methodName);\n if (!authMode) {\n throw new Error(`Endpoint '${methodName}' on ${apiClass.name} has no auth mode`);\n }\n const plan = new EndpointPlan(\n basePath + endpoints[methodName],\n getQueueName(apiClass, methodName),\n authMode,\n );\n plans.set(methodName, plan);\n }\n return plans;\n}\n\nasync function enqueue(\n config: TaskClientConfig,\n plan: EndpointPlan,\n // webpieces-disable no-any-unknown -- request DTO type is erased at the proxy layer\n requestDto: unknown,\n): Promise<void> {\n const frame = currentScheduleFrame();\n if (!frame) {\n throw new Error(\n 'Cloud task enqueue must run inside a CloudTaskScheduler lambda, e.g. ' +\n 'scheduler.addToQueue(() => taskClient.method(req), { dedupName }).',\n );\n }\n\n const targetUrl = typeof config.targetUrl === 'string'\n ? config.targetUrl\n : await config.targetUrl();\n\n const contextHeaders = buildContextHeaders(config.contextMgr);\n\n const request = new TaskRequest(\n targetUrl,\n plan.path,\n plan.queueName,\n requestDto,\n contextHeaders,\n plan.authMode,\n frame.info ?? new ScheduleInfo(),\n );\n\n log.debug(`enqueue task ${plan.queueName} -> ${targetUrl}${plan.path}`);\n frame.jobRef = await config.invoker.enqueue(request);\n}\n\nfunction buildContextHeaders(contextMgr?: ContextMgr): Map<string, string> {\n const headers = new Map<string, string>();\n if (!contextMgr) {\n return headers;\n }\n for (const entry of contextMgr.buildOutboundHeaders().entries()) {\n if (!AUTH_HEADER_NAMES.has(entry[0].toLowerCase())) {\n headers.set(entry[0], entry[1]);\n }\n }\n return headers;\n}\n"]}