@webpieces/cloudtasks-client 0.3.372 → 0.3.374

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
@@ -21,10 +21,11 @@ await scheduler.addToQueue(() => emailTasks.sendEmail(req), { dedupName: req.id
21
21
 
22
22
  - `ClientCloudTasksFactory.createPubSubClient(Api, TaskClientConfig)` — builds the enqueue proxy. It
23
23
  injects a `Provider<TaskProxyClient>` and calls `get()` per contract; `TaskProxyClient` is bound
24
- TRANSIENT, so each client gets its own. The delivery URL is resolved at enqueue time from
25
- `svcName` (same project + region as this container, so you maintain no URL table) unless you pass
26
- an explicit `targetUrl`. `getCloudRunUrl` also honours a `CLOUD_RUN_URL_<UPPER_SNAKE_NAME>` env
27
- override for local multi-service runs and integration tests
24
+ TRANSIENT, so each client gets its own. The delivery URL is resolved at enqueue time from `svcName`
25
+ via `resolveServiceUrl`: a `ClientRegistry` override wins, else on GCP it is derived from the Cloud
26
+ Run service name (same project + region, so you maintain no URL table), else (off-GCP, unregistered)
27
+ it throws. Register non-derivable URLs (localhost, cross-region, non-Cloud-Run) once at startup with
28
+ `ClientRegistry.addMapping(svcName, port)` / `addUrlMapping(svcName, url)`
28
29
  - An enqueue outside `RequestContext.run(...)` **throws**: a task with no caller trace is a bug
29
30
  - `CloudTaskScheduler` — `addToQueue` / `schedule` / `cancelJob`; carries scheduling
30
31
  options out-of-band so the contract signature stays identical on both sides
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/cloudtasks-client",
3
- "version": "0.3.372",
3
+ "version": "0.3.374",
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.372",
27
- "@webpieces/core-util": "0.3.372",
28
- "@webpieces/gcp-identity": "0.3.372",
26
+ "@webpieces/core-context": "0.3.374",
27
+ "@webpieces/core-util": "0.3.374",
28
+ "@webpieces/gcp-identity": "0.3.374",
29
29
  "@google-cloud/tasks": "5.5.2",
30
30
  "inversify": "7.10.4",
31
31
  "reflect-metadata": "0.2.2"
@@ -7,46 +7,24 @@ export type ApiPrototype<T> = Function & {
7
7
  *
8
8
  * Collaborators (TaskInvoker, RequestContextHeaders) are NOT config: they are dependencies of
9
9
  * {@link ClientCloudTasksFactory} and shared by every client it builds. This is the fire-and-forget
10
- * twin of http-client-node's ClientConfig, and takes the same two fields.
10
+ * twin of http-client-node's ClientConfig, and takes the same single field.
11
11
  */
12
12
  export declare class TaskClientConfig {
13
13
  /**
14
- * TYPICALLY the GCP Cloud Run service name and it MUST be the Cloud Run service name
15
- * when you do not supply a `targetUrl`, because we derive the URL from it.
16
- *
17
- * We lookup your service in the same project, same region, and form the url from the
18
- * container information unless you pass in a targetUrl, so you do not have to maintain
19
- * targetUrls. This works across your demo, qa, prod environments as long as each
20
- * environment is in its own projectId, which is typical.
21
- *
22
- * When you DO supply a `targetUrl`, svcName is used only for logging, so any readable
23
- * name works.
14
+ * The service name. On GCP the URL is DERIVED from it (same project, same region the Cloud
15
+ * Run service name, so you maintain no URL table), which works across demo/qa/prod. Anything
16
+ * the derivation cannot describe — a localhost port, another region/project, a non-Cloud-Run
17
+ * host is a `ClientRegistry` mapping registered at startup, NOT a per-client URL.
24
18
  */
25
19
  readonly svcName: string;
26
- /**
27
- * Optional explicit base URL, for the cases lookup cannot describe: another region,
28
- * another project, or a host that is not Cloud Run at all. It wins over `svcName`.
29
- */
30
- readonly targetUrl?: string | undefined;
31
20
  constructor(
32
21
  /**
33
- * TYPICALLY the GCP Cloud Run service name and it MUST be the Cloud Run service name
34
- * when you do not supply a `targetUrl`, because we derive the URL from it.
35
- *
36
- * We lookup your service in the same project, same region, and form the url from the
37
- * container information unless you pass in a targetUrl, so you do not have to maintain
38
- * targetUrls. This works across your demo, qa, prod environments as long as each
39
- * environment is in its own projectId, which is typical.
40
- *
41
- * When you DO supply a `targetUrl`, svcName is used only for logging, so any readable
42
- * name works.
43
- */
44
- svcName: string,
45
- /**
46
- * Optional explicit base URL, for the cases lookup cannot describe: another region,
47
- * another project, or a host that is not Cloud Run at all. It wins over `svcName`.
22
+ * The service name. On GCP the URL is DERIVED from it (same project, same region the Cloud
23
+ * Run service name, so you maintain no URL table), which works across demo/qa/prod. Anything
24
+ * the derivation cannot describe — a localhost port, another region/project, a non-Cloud-Run
25
+ * host is a `ClientRegistry` mapping registered at startup, NOT a per-client URL.
48
26
  */
49
- targetUrl?: string | undefined);
27
+ svcName: string);
50
28
  /** Resolved per enqueue, not at construction — so building a client stays synchronous. */
51
- resolveTargetUrl(): Promise<string>;
29
+ resolveUrl(): Promise<string>;
52
30
  }
@@ -7,36 +7,23 @@ const gcp_identity_1 = require("@webpieces/gcp-identity");
7
7
  *
8
8
  * Collaborators (TaskInvoker, RequestContextHeaders) are NOT config: they are dependencies of
9
9
  * {@link ClientCloudTasksFactory} and shared by every client it builds. This is the fire-and-forget
10
- * twin of http-client-node's ClientConfig, and takes the same two fields.
10
+ * twin of http-client-node's ClientConfig, and takes the same single field.
11
11
  */
12
12
  class TaskClientConfig {
13
13
  svcName;
14
- targetUrl;
15
14
  constructor(
16
15
  /**
17
- * TYPICALLY the GCP Cloud Run service name and it MUST be the Cloud Run service name
18
- * when you do not supply a `targetUrl`, because we derive the URL from it.
19
- *
20
- * We lookup your service in the same project, same region, and form the url from the
21
- * container information unless you pass in a targetUrl, so you do not have to maintain
22
- * targetUrls. This works across your demo, qa, prod environments as long as each
23
- * environment is in its own projectId, which is typical.
24
- *
25
- * When you DO supply a `targetUrl`, svcName is used only for logging, so any readable
26
- * name works.
16
+ * The service name. On GCP the URL is DERIVED from it (same project, same region the Cloud
17
+ * Run service name, so you maintain no URL table), which works across demo/qa/prod. Anything
18
+ * the derivation cannot describe — a localhost port, another region/project, a non-Cloud-Run
19
+ * host is a `ClientRegistry` mapping registered at startup, NOT a per-client URL.
27
20
  */
28
- svcName,
29
- /**
30
- * Optional explicit base URL, for the cases lookup cannot describe: another region,
31
- * another project, or a host that is not Cloud Run at all. It wins over `svcName`.
32
- */
33
- targetUrl) {
21
+ svcName) {
34
22
  this.svcName = svcName;
35
- this.targetUrl = targetUrl;
36
23
  }
37
24
  /** Resolved per enqueue, not at construction — so building a client stays synchronous. */
38
- resolveTargetUrl() {
39
- return (0, gcp_identity_1.resolveTargetUrl)(this.svcName, this.targetUrl);
25
+ resolveUrl() {
26
+ return (0, gcp_identity_1.resolveServiceUrl)(this.svcName);
40
27
  }
41
28
  }
42
29
  exports.TaskClientConfig = TaskClientConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"TaskClientConfig.js","sourceRoot":"","sources":["../../../../../packages/cloud/cloudtasks-client/src/TaskClientConfig.ts"],"names":[],"mappings":";;;AAAA,0DAA2D;AAK3D;;;;;;GAMG;AACH,MAAa,gBAAgB;IAcL;IAMA;IAnBpB;IACI;;;;;;;;;;;OAWG;IACa,OAAe;IAE/B;;;OAGG;IACa,SAAkB;QANlB,YAAO,GAAP,OAAO,CAAQ;QAMf,cAAS,GAAT,SAAS,CAAS;IACnC,CAAC;IAEJ,0FAA0F;IAC1F,gBAAgB;QACZ,OAAO,IAAA,+BAAgB,EAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC1D,CAAC;CACJ;AA3BD,4CA2BC","sourcesContent":["import { resolveTargetUrl } from '@webpieces/gcp-identity';\n\n/** 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 * Collaborators (TaskInvoker, RequestContextHeaders) are NOT config: they are dependencies of\n * {@link ClientCloudTasksFactory} and shared by every client it builds. This is the fire-and-forget\n * twin of http-client-node's ClientConfig, and takes the same two fields.\n */\nexport class TaskClientConfig {\n constructor(\n /**\n * TYPICALLY the GCP Cloud Run service name and it MUST be the Cloud Run service name\n * when you do not supply a `targetUrl`, because we derive the URL from it.\n *\n * We lookup your service in the same project, same region, and form the url from the\n * container information unless you pass in a targetUrl, so you do not have to maintain\n * targetUrls. This works across your demo, qa, prod environments as long as each\n * environment is in its own projectId, which is typical.\n *\n * When you DO supply a `targetUrl`, svcName is used only for logging, so any readable\n * name works.\n */\n public readonly svcName: string,\n\n /**\n * Optional explicit base URL, for the cases lookup cannot describe: another region,\n * another project, or a host that is not Cloud Run at all. It wins over `svcName`.\n */\n public readonly targetUrl?: string,\n ) {}\n\n /** Resolved per enqueue, not at construction — so building a client stays synchronous. */\n resolveTargetUrl(): Promise<string> {\n return resolveTargetUrl(this.svcName, this.targetUrl);\n }\n}\n"]}
1
+ {"version":3,"file":"TaskClientConfig.js","sourceRoot":"","sources":["../../../../../packages/cloud/cloudtasks-client/src/TaskClientConfig.ts"],"names":[],"mappings":";;;AAAA,0DAA4D;AAK5D;;;;;;GAMG;AACH,MAAa,gBAAgB;IAQL;IAPpB;IACI;;;;;OAKG;IACa,OAAe;QAAf,YAAO,GAAP,OAAO,CAAQ;IAChC,CAAC;IAEJ,0FAA0F;IAC1F,UAAU;QACN,OAAO,IAAA,gCAAiB,EAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;CACJ;AAfD,4CAeC","sourcesContent":["import { resolveServiceUrl } from '@webpieces/gcp-identity';\n\n/** 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 * Collaborators (TaskInvoker, RequestContextHeaders) are NOT config: they are dependencies of\n * {@link ClientCloudTasksFactory} and shared by every client it builds. This is the fire-and-forget\n * twin of http-client-node's ClientConfig, and takes the same single field.\n */\nexport class TaskClientConfig {\n constructor(\n /**\n * The service name. On GCP the URL is DERIVED from it (same project, same region the Cloud\n * Run service name, so you maintain no URL table), which works across demo/qa/prod. Anything\n * the derivation cannot describe a localhost port, another region/project, a non-Cloud-Run\n * host is a `ClientRegistry` mapping registered at startup, NOT a per-client URL.\n */\n public readonly svcName: string,\n ) {}\n\n /** Resolved per enqueue, not at construction — so building a client stays synchronous. */\n resolveUrl(): Promise<string> {\n return resolveServiceUrl(this.svcName);\n }\n}\n"]}
@@ -15,7 +15,7 @@ import { ApiPrototype, TaskClientConfig } from './TaskClientConfig';
15
15
  *
16
16
  * It owns:
17
17
  * - @ApiPath / @PubSub convention validation + the endpoint plans from the contract's decorators
18
- * - Resolving the callee's base URL (from svcName, or the explicit targetUrl)
18
+ * - Resolving the callee's base URL from svcName (ClientRegistry override, else GCP derivation)
19
19
  * - Context propagation onto the task headers (a credential is never a context key, so none can ride along)
20
20
  * - Handing a fully-built TaskRequest to the bound {@link TaskInvoker}
21
21
  */
@@ -33,7 +33,7 @@ class EndpointPlan {
33
33
  *
34
34
  * It owns:
35
35
  * - @ApiPath / @PubSub convention validation + the endpoint plans from the contract's decorators
36
- * - Resolving the callee's base URL (from svcName, or the explicit targetUrl)
36
+ * - Resolving the callee's base URL from svcName (ClientRegistry override, else GCP derivation)
37
37
  * - Context propagation onto the task headers (a credential is never a context key, so none can ride along)
38
38
  * - Handing a fully-built TaskRequest to the bound {@link TaskInvoker}
39
39
  */
@@ -80,9 +80,9 @@ let TaskProxyClient = class TaskProxyClient {
80
80
  'scheduler.addToQueue(() => taskClient.method(req), { dedupName }).');
81
81
  }
82
82
  // Resolved lazily (not at client construction) so building a client stays synchronous.
83
- // Every metadata read beneath resolveTargetUrl is memoized process-wide, so only the
83
+ // Every metadata read beneath resolveServiceUrl is memoized process-wide, so only the
84
84
  // first enqueue in the process pays a lookup.
85
- const targetUrl = await this.config.resolveTargetUrl();
85
+ const targetUrl = await this.config.resolveUrl();
86
86
  const request = new TaskTypes_1.TaskRequest(targetUrl, plan.path, plan.queueName, requestDto, this.buildContextHeaders(), plan.authMode, frame.info ?? new TaskTypes_1.ScheduleInfo());
87
87
  // svcName, not the URL, is the stable name across demo/qa/prod.
88
88
  log.debug(`enqueue task ${plan.queueName} -> ${this.config.svcName}${plan.path}`);
@@ -1 +1 @@
1
- {"version":3,"file":"TaskProxyClient.js","sourceRoot":"","sources":["../../../../../packages/cloud/cloudtasks-client/src/TaskProxyClient.ts"],"names":[],"mappings":"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","sourcesContent":["import { inject, injectable } from 'inversify';\nimport {\n isApiPath,\n getApiPath,\n getEndpoints,\n getAuthMode,\n getQueueName,\n assertPubSubConventions,\n assertEveryEndpointHasAuthMode,\n AuthMode,\n LogManager,\n} from '@webpieces/core-util';\nimport {\n RequestContextHeaders,\n provideFrameworkTransient,\n} from '@webpieces/core-context';\nimport { TaskInvoker, TaskRequest, ScheduleInfo } from './TaskTypes';\nimport { currentScheduleFrame } from './ScheduleContext';\nimport { ApiPrototype, TaskClientConfig } from './TaskClientConfig';\n\nconst log = LogManager.getLogger('TaskProxyClient');\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-core's ProxyClient, and TWO-PHASE for the same reason:\n * its COLLABORATORS (invoker, headers) come from the container, while the PER-CLIENT state (which\n * contract, which target) arrives on {@link init}. That is what lets {@link ClientCloudTasksFactory}\n * hold a `Provider<TaskProxyClient>` and hand out a fresh, independently-configured client per\n * contract.\n *\n * Calling an endpoint 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 * It owns:\n * - @ApiPath / @PubSub convention validation + the endpoint plans from the contract's decorators\n * - Resolving the callee's base URL (from svcName, or the explicit targetUrl)\n * - Context propagation onto the task headers (a credential is never a context key, so none can ride along)\n * - Handing a fully-built TaskRequest to the bound {@link TaskInvoker}\n */\n@provideFrameworkTransient()\n@injectable()\nexport class TaskProxyClient {\n // Assigned by init(), which the factory calls immediately after construction.\n private plans!: Map<string, EndpointPlan>;\n private apiName!: string;\n private config!: TaskClientConfig;\n\n constructor(\n @inject(TaskInvoker) private readonly invoker: TaskInvoker,\n @inject(RequestContextHeaders) private readonly headers: RequestContextHeaders,\n ) {}\n\n /** Bind this client to one @PubSub contract + target. */\n init(apiClass: ApiPrototype<object>, config: TaskClientConfig): void {\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.config = config;\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 resolveTargetUrl is memoized process-wide, so only the\n // first enqueue in the process pays a lookup.\n const targetUrl = await this.config.resolveTargetUrl();\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 // svcName, not the URL, is the stable name across demo/qa/prod.\n log.debug(`enqueue task ${plan.queueName} -> ${this.config.svcName}${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 /**\n * Every transferred context key (txId/requestId/tenant…), request-id chained.\n * Throws if there is no active RequestContext — an enqueue with no trace is a bug.\n *\n * No credential can appear here: `authorization` is read off the inbound HttpRequest and is not\n * a ContextKey, so it never enters the RequestContext to be transferred. The invoker mints the\n * task's own delivery auth per the endpoint's @AuthOidc / @AuthSharedSecret mode.\n */\n private buildContextHeaders(): Map<string, string> {\n return this.headers.buildOutboundHeaders();\n }\n}\n\n/**\n * DI token for the `Provider<TaskProxyClient>` that hands out enqueue clients — one per @PubSub\n * contract. `Provider<T>` is erased at runtime, so it cannot be its own token; this Symbol names T.\n *\n * Because TaskProxyClient is bound TRANSIENT, every `get()` constructs a new one. (Were it bound\n * `@provideFrameworkSingleton`, the very same Provider would hand back one lazily-created instance\n * instead — the provider caches nothing, so the target's scope decides.)\n */\n// webpieces-disable no-symbol-di-tokens -- Provider<T> is erased at runtime; the Symbol names T\nexport const TASK_PROXY_CLIENT_PROVIDER = Symbol.for('Provider<TaskProxyClient>');\n"]}
1
+ {"version":3,"file":"TaskProxyClient.js","sourceRoot":"","sources":["../../../../../packages/cloud/cloudtasks-client/src/TaskProxyClient.ts"],"names":[],"mappings":"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","sourcesContent":["import { inject, injectable } from 'inversify';\nimport {\n isApiPath,\n getApiPath,\n getEndpoints,\n getAuthMode,\n getQueueName,\n assertPubSubConventions,\n assertEveryEndpointHasAuthMode,\n AuthMode,\n LogManager,\n} from '@webpieces/core-util';\nimport {\n RequestContextHeaders,\n provideFrameworkTransient,\n} from '@webpieces/core-context';\nimport { TaskInvoker, TaskRequest, ScheduleInfo } from './TaskTypes';\nimport { currentScheduleFrame } from './ScheduleContext';\nimport { ApiPrototype, TaskClientConfig } from './TaskClientConfig';\n\nconst log = LogManager.getLogger('TaskProxyClient');\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-core's ProxyClient, and TWO-PHASE for the same reason:\n * its COLLABORATORS (invoker, headers) come from the container, while the PER-CLIENT state (which\n * contract, which target) arrives on {@link init}. That is what lets {@link ClientCloudTasksFactory}\n * hold a `Provider<TaskProxyClient>` and hand out a fresh, independently-configured client per\n * contract.\n *\n * Calling an endpoint 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 * It owns:\n * - @ApiPath / @PubSub convention validation + the endpoint plans from the contract's decorators\n * - Resolving the callee's base URL from svcName (ClientRegistry override, else GCP derivation)\n * - Context propagation onto the task headers (a credential is never a context key, so none can ride along)\n * - Handing a fully-built TaskRequest to the bound {@link TaskInvoker}\n */\n@provideFrameworkTransient()\n@injectable()\nexport class TaskProxyClient {\n // Assigned by init(), which the factory calls immediately after construction.\n private plans!: Map<string, EndpointPlan>;\n private apiName!: string;\n private config!: TaskClientConfig;\n\n constructor(\n @inject(TaskInvoker) private readonly invoker: TaskInvoker,\n @inject(RequestContextHeaders) private readonly headers: RequestContextHeaders,\n ) {}\n\n /** Bind this client to one @PubSub contract + target. */\n init(apiClass: ApiPrototype<object>, config: TaskClientConfig): void {\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.config = config;\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 resolveServiceUrl is memoized process-wide, so only the\n // first enqueue in the process pays a lookup.\n const targetUrl = await this.config.resolveUrl();\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 // svcName, not the URL, is the stable name across demo/qa/prod.\n log.debug(`enqueue task ${plan.queueName} -> ${this.config.svcName}${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 /**\n * Every transferred context key (txId/requestId/tenant…), request-id chained.\n * Throws if there is no active RequestContext — an enqueue with no trace is a bug.\n *\n * No credential can appear here: `authorization` is read off the inbound HttpRequest and is not\n * a ContextKey, so it never enters the RequestContext to be transferred. The invoker mints the\n * task's own delivery auth per the endpoint's @AuthOidc / @AuthSharedSecret mode.\n */\n private buildContextHeaders(): Map<string, string> {\n return this.headers.buildOutboundHeaders();\n }\n}\n\n/**\n * DI token for the `Provider<TaskProxyClient>` that hands out enqueue clients — one per @PubSub\n * contract. `Provider<T>` is erased at runtime, so it cannot be its own token; this Symbol names T.\n *\n * Because TaskProxyClient is bound TRANSIENT, every `get()` constructs a new one. (Were it bound\n * `@provideFrameworkSingleton`, the very same Provider would hand back one lazily-created instance\n * instead — the provider caches nothing, so the target's scope decides.)\n */\n// webpieces-disable no-symbol-di-tokens -- Provider<T> is erased at runtime; the Symbol names T\nexport const TASK_PROXY_CLIENT_PROVIDER = Symbol.for('Provider<TaskProxyClient>');\n"]}