@webpieces/cloudtasks-client 0.3.316 → 0.3.322

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
@@ -12,15 +12,20 @@ abstract class EmailApi { @Endpoint('/send') sendEmail(r: SendEmailRequest): Pro
12
12
 
13
13
  // build the client once (sync); 'email-svc' is the callee's Cloud Run service name
14
14
  const emailTasks = factory.createClient(EmailApi, new TaskClientConfig('email-svc'));
15
+ // ...or pin a URL lookup cannot describe (other region/project); svcName stays the log name
16
+ const other = factory.createClient(EmailApi, new TaskClientConfig('email-svc', 'https://email.eu.example'));
15
17
 
16
18
  // producer (inside a request → RequestContext active)
17
19
  await scheduler.addToQueue(() => emailTasks.sendEmail(req), { dedupName: req.id });
18
20
  ```
19
21
 
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
22
+ - `ClientCloudTasksFactory.createClient(Api, TaskClientConfig)` — builds the enqueue proxy. It
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
28
+ - An enqueue outside `RequestContext.run(...)` **throws**: a task with no caller trace is a bug
24
29
  - `CloudTaskScheduler` — `addToQueue` / `schedule` / `cancelJob`; carries scheduling
25
30
  options out-of-band so the contract signature stays identical on both sides
26
31
  - `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.316",
3
+ "version": "0.3.322",
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.316",
27
- "@webpieces/core-util": "0.3.316",
28
- "@webpieces/gcp-identity": "0.3.316",
26
+ "@webpieces/core-context": "0.3.322",
27
+ "@webpieces/core-util": "0.3.322",
28
+ "@webpieces/gcp-identity": "0.3.322",
29
29
  "@google-cloud/tasks": "5.5.2",
30
30
  "inversify": "7.10.4",
31
31
  "reflect-metadata": "0.2.2"
@@ -1,30 +1,29 @@
1
- import { TaskInvoker } from './TaskTypes';
1
+ import { Provider } from '@webpieces/core-context';
2
+ import { TaskProxyClient } from './TaskProxyClient';
2
3
  import { ApiPrototype, TaskClientConfig } from './TaskClientConfig';
3
4
  /**
4
5
  * ClientCloudTasksFactory - builds Cloud Tasks enqueue clients from a shared @PubSub API
5
- * contract. The fire-and-forget twin of http-client's ClientHttpFactory.
6
+ * contract. The fire-and-forget twin of http-client-node's ClientHttpFactory.
6
7
  *
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:
8
+ * Calling a method on the returned client ENQUEUES a task (it does not call remotely); the task
9
+ * is later delivered to the same endpoint's controller through the full server filter chain.
10
10
  *
11
11
  * ```typescript
12
+ * // same project + region as this container; the URL is derived, you maintain nothing
12
13
  * const emailTasks = factory.createClient(EmailApi, new TaskClientConfig('email-svc'));
13
14
  * await scheduler.addToQueue(() => emailTasks.sendEmail(req), { dedupName });
14
15
  * ```
15
16
  *
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).
17
+ * Every client it builds gets its OWN {@link TaskProxyClient} from the injected
18
+ * `Provider<TaskProxyClient>` (bound transient), which `createClient` then `init`s for one
19
+ * contract. Their collaborators (TaskInvoker, RequestContextHeaders) come from the container.
19
20
  *
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.
21
+ * Node-only, so the factory IS the inversify entry point. An enqueue outside
22
+ * `RequestContext.run(...)` throws rather than silently dropping the caller's trace.
23
23
  */
24
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`. */
25
+ private readonly taskProxyClientProvider;
26
+ constructor(taskProxyClientProvider: Provider<TaskProxyClient>);
27
+ /** Typed enqueue client for a @PubSub contract, delivered to `config.svcName`. */
29
28
  createClient<T extends object>(apiClass: ApiPrototype<T>, config: TaskClientConfig): T;
30
29
  }
@@ -3,12 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ClientCloudTasksFactory = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const inversify_1 = require("inversify");
6
+ const core_util_1 = require("@webpieces/core-util");
6
7
  const core_context_1 = require("@webpieces/core-context");
7
- const TaskTypes_1 = require("./TaskTypes");
8
8
  const TaskProxyClient_1 = require("./TaskProxyClient");
9
+ // Teach the container how to hand out fresh TaskProxyClients. TaskProxyClient is bound TRANSIENT
10
+ // (@provideFrameworkTransient), so each provider.get() constructs a new one.
11
+ (0, core_context_1.bindFrameworkProvider)(TaskProxyClient_1.TASK_PROXY_CLIENT_PROVIDER, TaskProxyClient_1.TaskProxyClient);
9
12
  /**
10
13
  * Properties DI frameworks / Promise checks / serializers probe on the proxy; return
11
- * undefined instead of treating them as endpoints. Mirrors http-client's ClientHttpFactory.
14
+ * undefined instead of treating them as endpoints. Mirrors http-client-core's buildClientProxy.
12
15
  */
13
16
  const FRAMEWORK_INSPECTION_PROPERTIES = new Set([
14
17
  'constructor', 'prototype', '__proto__', 'name', 'then', 'catch', 'finally',
@@ -16,36 +19,34 @@ const FRAMEWORK_INSPECTION_PROPERTIES = new Set([
16
19
  ]);
17
20
  /**
18
21
  * ClientCloudTasksFactory - builds Cloud Tasks enqueue clients from a shared @PubSub API
19
- * contract. The fire-and-forget twin of http-client's ClientHttpFactory.
22
+ * contract. The fire-and-forget twin of http-client-node's ClientHttpFactory.
20
23
  *
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
+ * Calling a method on the returned client ENQUEUES a task (it does not call remotely); the task
25
+ * is later delivered to the same endpoint's controller through the full server filter chain.
24
26
  *
25
27
  * ```typescript
28
+ * // same project + region as this container; the URL is derived, you maintain nothing
26
29
  * const emailTasks = factory.createClient(EmailApi, new TaskClientConfig('email-svc'));
27
30
  * await scheduler.addToQueue(() => emailTasks.sendEmail(req), { dedupName });
28
31
  * ```
29
32
  *
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
+ * Every client it builds gets its OWN {@link TaskProxyClient} from the injected
34
+ * `Provider<TaskProxyClient>` (bound transient), which `createClient` then `init`s for one
35
+ * contract. Their collaborators (TaskInvoker, RequestContextHeaders) come from the container.
33
36
  *
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
+ * Node-only, so the factory IS the inversify entry point. An enqueue outside
38
+ * `RequestContext.run(...)` throws rather than silently dropping the caller's trace.
37
39
  */
38
40
  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;
41
+ taskProxyClientProvider;
42
+ constructor(taskProxyClientProvider) {
43
+ this.taskProxyClientProvider = taskProxyClientProvider;
43
44
  }
44
- /** Typed enqueue client for a @PubSub contract, delivered to `config.gcpCloudRunSvcName`. */
45
+ /** Typed enqueue client for a @PubSub contract, delivered to `config.svcName`. */
45
46
  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);
47
+ // Fresh instance per contract TaskProxyClient is transient.
48
+ const proxyClient = this.taskProxyClientProvider.get();
49
+ proxyClient.init(apiClass, config);
49
50
  return new Proxy({}, {
50
51
  // webpieces-disable no-any-unknown -- proxy get trap returns either an endpoint method or undefined
51
52
  get(target, prop) {
@@ -64,9 +65,10 @@ let ClientCloudTasksFactory = class ClientCloudTasksFactory {
64
65
  };
65
66
  exports.ClientCloudTasksFactory = ClientCloudTasksFactory;
66
67
  exports.ClientCloudTasksFactory = ClientCloudTasksFactory = tslib_1.__decorate([
68
+ (0, core_util_1.DocumentDesign)(),
67
69
  (0, core_context_1.provideFrameworkSingleton)(),
68
70
  (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
+ tslib_1.__param(0, (0, inversify_1.inject)(TaskProxyClient_1.TASK_PROXY_CLIENT_PROVIDER)),
72
+ tslib_1.__metadata("design:paramtypes", [core_context_1.Provider])
71
73
  ], ClientCloudTasksFactory);
72
74
  //# sourceMappingURL=ClientCloudTasksFactory.js.map
@@ -1 +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"]}
1
+ {"version":3,"file":"ClientCloudTasksFactory.js","sourceRoot":"","sources":["../../../../../packages/cloud/cloudtasks-client/src/ClientCloudTasksFactory.ts"],"names":[],"mappings":";;;;AAAA,yCAA+C;AAC/C,oDAAsD;AACtD,0DAAqG;AACrG,uDAAgF;AAGhF,iGAAiG;AACjG,6EAA6E;AAC7E,IAAA,oCAAqB,EAAC,4CAA0B,EAAE,iCAAe,CAAC,CAAC;AAEnE;;;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;;;;;;;;;;;;;;;;;;;GAmBG;AAII,IAAM,uBAAuB,GAA7B,MAAM,uBAAuB;IAEyB;IADzD,YACyD,uBAAkD;QAAlD,4BAAuB,GAAvB,uBAAuB,CAA2B;IACxG,CAAC;IAEJ,kFAAkF;IAClF,YAAY,CAAmB,QAAyB,EAAE,MAAwB;QAC9E,8DAA8D;QAC9D,MAAM,WAAW,GAAG,IAAI,CAAC,uBAAuB,CAAC,GAAG,EAAE,CAAC;QACvD,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAEnC,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;AA5BY,0DAAuB;kCAAvB,uBAAuB;IAHnC,IAAA,0BAAc,GAAE;IAChB,IAAA,wCAAyB,GAAE;IAC3B,IAAA,sBAAU,GAAE;IAGJ,mBAAA,IAAA,kBAAM,EAAC,4CAA0B,CAAC,CAAA;6CAA2C,uBAAQ;GAFjF,uBAAuB,CA4BnC","sourcesContent":["import { inject, injectable } from 'inversify';\nimport { DocumentDesign } from '@webpieces/core-util';\nimport { Provider, bindFrameworkProvider, provideFrameworkSingleton } from '@webpieces/core-context';\nimport { TASK_PROXY_CLIENT_PROVIDER, TaskProxyClient } from './TaskProxyClient';\nimport { ApiPrototype, TaskClientConfig } from './TaskClientConfig';\n\n// Teach the container how to hand out fresh TaskProxyClients. TaskProxyClient is bound TRANSIENT\n// (@provideFrameworkTransient), so each provider.get() constructs a new one.\nbindFrameworkProvider(TASK_PROXY_CLIENT_PROVIDER, TaskProxyClient);\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-core's buildClientProxy.\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-node's ClientHttpFactory.\n *\n * Calling a method on the returned client ENQUEUES a task (it does not call remotely); the task\n * is later delivered to the same endpoint's controller through the full server filter chain.\n *\n * ```typescript\n * // same project + region as this container; the URL is derived, you maintain nothing\n * const emailTasks = factory.createClient(EmailApi, new TaskClientConfig('email-svc'));\n * await scheduler.addToQueue(() => emailTasks.sendEmail(req), { dedupName });\n * ```\n *\n * Every client it builds gets its OWN {@link TaskProxyClient} from the injected\n * `Provider<TaskProxyClient>` (bound transient), which `createClient` then `init`s for one\n * contract. Their collaborators (TaskInvoker, RequestContextHeaders) come from the container.\n *\n * Node-only, so the factory IS the inversify entry point. An enqueue outside\n * `RequestContext.run(...)` throws rather than silently dropping the caller's trace.\n */\n@DocumentDesign()\n@provideFrameworkSingleton()\n@injectable()\nexport class ClientCloudTasksFactory {\n constructor(\n @inject(TASK_PROXY_CLIENT_PROVIDER) private readonly taskProxyClientProvider: Provider<TaskProxyClient>,\n ) {}\n\n /** Typed enqueue client for a @PubSub contract, delivered to `config.svcName`. */\n createClient<T extends object>(apiClass: ApiPrototype<T>, config: TaskClientConfig): T {\n // Fresh instance per contract TaskProxyClient is transient.\n const proxyClient = this.taskProxyClientProvider.get();\n proxyClient.init(apiClass, config);\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"]}
@@ -72,7 +72,8 @@ let GcpTaskInvoker = class GcpTaskInvoker extends TaskTypes_1.TaskInvoker {
72
72
  if (mode.kind === 'shared-secret') {
73
73
  const secret = this.secrets?.get(mode.secretKey);
74
74
  if (secret) {
75
- headers.set('x-webpieces-shared-secret', secret);
75
+ // One credential header; the 'Webpieces' scheme says a shared secret follows, not a token.
76
+ headers.set('authorization', `Webpieces ${secret}`);
76
77
  httpRequest.headers = Object.fromEntries(headers);
77
78
  }
78
79
  }
@@ -1 +1 @@
1
- {"version":3,"file":"GcpTaskInvoker.js","sourceRoot":"","sources":["../../../../../packages/cloud/cloudtasks-client/src/GcpTaskInvoker.ts"],"names":[],"mappings":"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","sourcesContent":["import { injectable, inject, optional } from 'inversify';\nimport { CloudTasksClient, protos } from '@google-cloud/tasks';\nimport { provideFrameworkSingleton } from '@webpieces/core-context';\nimport {\n getProjectId,\n getRegion,\n getRuntimeServiceAccountEmail,\n} from '@webpieces/gcp-identity';\nimport { LogManager, Secrets } from '@webpieces/core-util';\nimport { TaskInvoker, TaskRequest, JobReference } from './TaskTypes';\n\nconst log = LogManager.getLogger('GcpTaskInvoker');\n\ntype ITask = protos.google.cloud.tasks.v2.ITask;\n\n/**\n * TaskInvoker that enqueues to Google Cloud Tasks (`@google-cloud/tasks`). Builds an\n * HTTP-target task delivered as POST to `targetUrl + path`, authenticated per the\n * endpoint's auth mode (OIDC token minted as this service's runtime SA, or a\n * shared-secret header). The task name is derived from the dedup name for idempotency\n * (a duplicate enqueue is rejected ALREADY_EXISTS = idempotent success upstream).\n */\n@provideFrameworkSingleton()\n@injectable()\nexport class GcpTaskInvoker extends TaskInvoker {\n private readonly client = new CloudTasksClient();\n\n // @optional: only @AuthSharedSecret task endpoints need it; the client sends its bound value.\n constructor(@optional() @inject(Secrets) private readonly secrets?: Secrets) {\n super();\n }\n\n override async enqueue(request: TaskRequest): Promise<JobReference> {\n const projectId = await getProjectId();\n const region = await getRegion();\n const parent = this.client.queuePath(projectId, region, request.queueName);\n\n const task = await this.buildTask(request, parent);\n const result = await this.client.createTask({ parent: parent, task: task });\n const created = result[0];\n const name = created.name ?? request.scheduleInfo.dedupName ?? 'unknown';\n log.info(`enqueued cloud task ${name}`);\n return new JobReference(name);\n }\n\n override async delete(ref: JobReference): Promise<void> {\n await this.client.deleteTask({ name: ref.taskId });\n }\n\n private async buildTask(request: TaskRequest, parent: string): Promise<ITask> {\n const headers = new Map<string, string>(request.contextHeaders);\n headers.set('content-type', 'application/json');\n\n const httpRequest: protos.google.cloud.tasks.v2.IHttpRequest = {\n httpMethod: 'POST',\n url: request.targetUrl + request.path,\n headers: Object.fromEntries(headers),\n body: Buffer.from(JSON.stringify(request.body ?? {}), 'utf8').toString('base64'),\n };\n await this.applyAuth(request, httpRequest, headers);\n\n const task: ITask = { httpRequest: httpRequest };\n if (request.scheduleInfo.dedupName) {\n task.name = `${parent}/tasks/${request.scheduleInfo.dedupName}`;\n }\n if (request.scheduleInfo.epochMsToRunAt) {\n task.scheduleTime = { seconds: Math.floor(request.scheduleInfo.epochMsToRunAt / 1000) };\n }\n if (request.scheduleInfo.taskTimeoutSeconds) {\n task.dispatchDeadline = { seconds: request.scheduleInfo.taskTimeoutSeconds };\n }\n return task;\n }\n\n private async applyAuth(\n request: TaskRequest,\n httpRequest: protos.google.cloud.tasks.v2.IHttpRequest,\n headers: Map<string, string>,\n ): Promise<void> {\n const mode = request.authMode;\n if (mode.kind === 'oidc') {\n httpRequest.oidcToken = {\n serviceAccountEmail: await getRuntimeServiceAccountEmail(),\n audience: request.targetUrl,\n };\n return;\n }\n if (mode.kind === 'shared-secret') {\n const secret = this.secrets?.get(mode.secretKey);\n if (secret) {\n headers.set('x-webpieces-shared-secret', secret);\n httpRequest.headers = Object.fromEntries(headers);\n }\n }\n }\n}\n"]}
1
+ {"version":3,"file":"GcpTaskInvoker.js","sourceRoot":"","sources":["../../../../../packages/cloud/cloudtasks-client/src/GcpTaskInvoker.ts"],"names":[],"mappings":"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","sourcesContent":["import { injectable, inject, optional } from 'inversify';\nimport { CloudTasksClient, protos } from '@google-cloud/tasks';\nimport { provideFrameworkSingleton } from '@webpieces/core-context';\nimport {\n getProjectId,\n getRegion,\n getRuntimeServiceAccountEmail,\n} from '@webpieces/gcp-identity';\nimport { LogManager, Secrets } from '@webpieces/core-util';\nimport { TaskInvoker, TaskRequest, JobReference } from './TaskTypes';\n\nconst log = LogManager.getLogger('GcpTaskInvoker');\n\ntype ITask = protos.google.cloud.tasks.v2.ITask;\n\n/**\n * TaskInvoker that enqueues to Google Cloud Tasks (`@google-cloud/tasks`). Builds an\n * HTTP-target task delivered as POST to `targetUrl + path`, authenticated per the\n * endpoint's auth mode (OIDC token minted as this service's runtime SA, or a\n * shared-secret header). The task name is derived from the dedup name for idempotency\n * (a duplicate enqueue is rejected ALREADY_EXISTS = idempotent success upstream).\n */\n@provideFrameworkSingleton()\n@injectable()\nexport class GcpTaskInvoker extends TaskInvoker {\n private readonly client = new CloudTasksClient();\n\n // @optional: only @AuthSharedSecret task endpoints need it; the client sends its bound value.\n constructor(@optional() @inject(Secrets) private readonly secrets?: Secrets) {\n super();\n }\n\n override async enqueue(request: TaskRequest): Promise<JobReference> {\n const projectId = await getProjectId();\n const region = await getRegion();\n const parent = this.client.queuePath(projectId, region, request.queueName);\n\n const task = await this.buildTask(request, parent);\n const result = await this.client.createTask({ parent: parent, task: task });\n const created = result[0];\n const name = created.name ?? request.scheduleInfo.dedupName ?? 'unknown';\n log.info(`enqueued cloud task ${name}`);\n return new JobReference(name);\n }\n\n override async delete(ref: JobReference): Promise<void> {\n await this.client.deleteTask({ name: ref.taskId });\n }\n\n private async buildTask(request: TaskRequest, parent: string): Promise<ITask> {\n const headers = new Map<string, string>(request.contextHeaders);\n headers.set('content-type', 'application/json');\n\n const httpRequest: protos.google.cloud.tasks.v2.IHttpRequest = {\n httpMethod: 'POST',\n url: request.targetUrl + request.path,\n headers: Object.fromEntries(headers),\n body: Buffer.from(JSON.stringify(request.body ?? {}), 'utf8').toString('base64'),\n };\n await this.applyAuth(request, httpRequest, headers);\n\n const task: ITask = { httpRequest: httpRequest };\n if (request.scheduleInfo.dedupName) {\n task.name = `${parent}/tasks/${request.scheduleInfo.dedupName}`;\n }\n if (request.scheduleInfo.epochMsToRunAt) {\n task.scheduleTime = { seconds: Math.floor(request.scheduleInfo.epochMsToRunAt / 1000) };\n }\n if (request.scheduleInfo.taskTimeoutSeconds) {\n task.dispatchDeadline = { seconds: request.scheduleInfo.taskTimeoutSeconds };\n }\n return task;\n }\n\n private async applyAuth(\n request: TaskRequest,\n httpRequest: protos.google.cloud.tasks.v2.IHttpRequest,\n headers: Map<string, string>,\n ): Promise<void> {\n const mode = request.authMode;\n if (mode.kind === 'oidc') {\n httpRequest.oidcToken = {\n serviceAccountEmail: await getRuntimeServiceAccountEmail(),\n audience: request.targetUrl,\n };\n return;\n }\n if (mode.kind === 'shared-secret') {\n const secret = this.secrets?.get(mode.secretKey);\n if (secret) {\n // One credential header; the 'Webpieces' scheme says a shared secret follows, not a token.\n headers.set('authorization', `Webpieces ${secret}`);\n httpRequest.headers = Object.fromEntries(headers);\n }\n }\n }\n}\n"]}
@@ -107,7 +107,8 @@ let InMemoryTaskInvoker = class InMemoryTaskInvoker extends TaskTypes_1.TaskInvo
107
107
  if (mode.kind === 'shared-secret') {
108
108
  const secret = this.secrets?.get(mode.secretKey);
109
109
  if (secret) {
110
- headers['x-webpieces-shared-secret'] = secret;
110
+ // One credential header; the 'Webpieces' scheme says a shared secret follows, not a token.
111
+ headers['authorization'] = `Webpieces ${secret}`;
111
112
  }
112
113
  }
113
114
  // public / jwt → no service credential is synthesized.
@@ -1 +1 @@
1
- {"version":3,"file":"InMemoryTaskInvoker.js","sourceRoot":"","sources":["../../../../../packages/cloud/cloudtasks-client/src/InMemoryTaskInvoker.ts"],"names":[],"mappings":";;;;AAAA,yCAAyD;AACzD,0DAAoE;AACpE,0DAAsD;AACtD,oDAAoE;AACpE,2CAAqE;AAErE,MAAM,GAAG,GAAG,sBAAU,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;AAExD;;;;;;;;;;;;;;;GAeG;AAGI,IAAM,mBAAmB,GAAzB,MAAM,mBAAoB,SAAQ,uBAAW;IAMU;IALlD,OAAO,GAAG,CAAC,CAAC;IACpB,uEAAuE;IACtD,OAAO,GAAG,IAAI,GAAG,EAAyC,CAAC;IAE5E,8FAA8F;IAC9F,YAA0D,OAAiB;QACvE,KAAK,EAAE,CAAC;QAD8C,YAAO,GAAP,OAAO,CAAU;IAE3E,CAAC;IAEQ,KAAK,CAAC,OAAO,CAAC,OAAoB;QACvC,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC;QAClB,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,SAAS,IAAI,SAAS,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QAC9F,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC;QAEzE,+EAA+E;QAC/E,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC1B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC5B,KAAK,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACvC,CAAC,EAAE,OAAO,CAAC,CAAC;QACZ,4DAA4D;QAC5D,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;QAChB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAEhC,GAAG,CAAC,KAAK,CAAC,qBAAqB,MAAM,OAAO,OAAO,CAAC,SAAS,GAAG,OAAO,CAAC,IAAI,WAAW,OAAO,KAAK,CAAC,CAAC;QACrG,OAAO,IAAI,wBAAY,CAAC,MAAM,CAAC,CAAC;IACpC,CAAC;IAEQ,KAAK,CAAC,MAAM,CAAC,GAAiB;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC3C,IAAI,KAAK,EAAE,CAAC;YACR,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpC,CAAC;IACL,CAAC;IAED,+EAA+E;IACvE,cAAc,CAAC,cAAuB;QAC1C,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;YAC/B,OAAO,CAAC,CAAC;QACb,CAAC;QACD,MAAM,KAAK,GAAG,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC1C,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACjC,CAAC;IAED,oFAAoF;IAC5E,KAAK,CAAC,OAAO,CAAC,OAAoB,EAAE,MAAc;QACtD,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,SAAS,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAClD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QACjD,GAAG,CAAC,IAAI,CAAC,yBAAyB,MAAM,UAAU,GAAG,EAAE,CAAC,CAAC;QACzD,iIAAiI;QACjI,IAAI,CAAC;YACD,0HAA0H;YAC1H,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAC9B,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,OAAO;gBAChB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC;aAC3C,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACf,GAAG,CAAC,KAAK,CAAC,cAAc,MAAM,gBAAgB,GAAG,iBAAiB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;gBACrF,OAAO;YACX,CAAC;YACD,GAAG,CAAC,KAAK,CAAC,cAAc,MAAM,oBAAoB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QACzE,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,mBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,GAAG,CAAC,KAAK,CAAC,cAAc,MAAM,gBAAgB,GAAG,WAAW,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACjF,CAAC;IACL,CAAC;IAED,mFAAmF;IAC3E,KAAK,CAAC,YAAY,CAAC,OAAoB;QAC3C,MAAM,OAAO,GAA2B,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC;QAC/E,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,CAAC;YACnD,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACjC,CAAC;QACD,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxC,OAAO,OAAO,CAAC;IACnB,CAAC;IAED,sFAAsF;IAC9E,KAAK,CAAC,UAAU,CAAC,OAAoB,EAAE,OAA+B;QAC1E,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC;QAC9B,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACvB,OAAO,CAAC,eAAe,CAAC,GAAG,UAAU,MAAM,IAAA,0BAAW,EAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;YAC5E,OAAO;QACX,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;YAChC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACjD,IAAI,MAAM,EAAE,CAAC;gBACT,OAAO,CAAC,2BAA2B,CAAC,GAAG,MAAM,CAAC;YAClD,CAAC;QACL,CAAC;QACD,uDAAuD;IAC3D,CAAC;CACJ,CAAA;AA9FY,kDAAmB;8BAAnB,mBAAmB;IAF/B,IAAA,wCAAyB,GAAE;IAC3B,IAAA,sBAAU,GAAE;IAOI,mBAAA,IAAA,oBAAQ,GAAE,CAAA;IAAE,mBAAA,IAAA,kBAAM,EAAC,mBAAO,CAAC,CAAA;6CAA4B,mBAAO;GANlE,mBAAmB,CA8F/B","sourcesContent":["import { injectable, inject, optional } from 'inversify';\nimport { provideFrameworkSingleton } from '@webpieces/core-context';\nimport { mintIdToken } from '@webpieces/gcp-identity';\nimport { LogManager, toError, Secrets } from '@webpieces/core-util';\nimport { TaskInvoker, TaskRequest, JobReference } from './TaskTypes';\n\nconst log = LogManager.getLogger('InMemoryTaskInvoker');\n\n/**\n * TaskInvoker for tests + local dev — the in-memory-queue twin of GcpTaskInvoker\n * (a TypeScript port of webpieces-java's LocalRemoteInvoker).\n *\n * Instead of enqueuing to Google Cloud Tasks, it drops the delivery onto an in-process\n * timer queue and returns IMMEDIATELY (breaking from the caller, exactly like handing a\n * task to Cloud Tasks). The queued job then delivers the task the SAME way real Cloud\n * Tasks does: a plain HTTP POST to `targetUrl + path` with the JSON body and the\n * synthesized delivery auth (OIDC bearer / shared-secret). It does NOT invoke the server\n * in-process — the target may be a different process entirely — so the request travels\n * over real HTTP (e.g. http://localhost:{port}/queue/path) and is served by the target's\n * real routing + filter chain, giving production parity without GCP.\n *\n * Bind it (via appOverrides) in place of GcpTaskInvoker:\n * bind(TaskInvoker).to(InMemoryTaskInvoker)\n */\n@provideFrameworkSingleton()\n@injectable()\nexport class InMemoryTaskInvoker extends TaskInvoker {\n private counter = 0;\n /** Scheduled (not-yet-delivered) jobs, so delete() can cancel them. */\n private readonly pending = new Map<string, ReturnType<typeof setTimeout>>();\n\n // @optional: only @AuthSharedSecret task endpoints need it; the client sends its bound value.\n constructor(@optional() @inject(Secrets) private readonly secrets?: Secrets) {\n super();\n }\n\n override async enqueue(request: TaskRequest): Promise<JobReference> {\n this.counter += 1;\n const taskId = request.scheduleInfo.dedupName ?? `inmem-${request.queueName}-${this.counter}`;\n const delayMs = this.computeDelayMs(request.scheduleInfo.epochMsToRunAt);\n\n // Break from the caller: queue the HTTP delivery and return the reference NOW.\n const timer = setTimeout(() => {\n this.pending.delete(taskId);\n void this.deliver(request, taskId);\n }, delayMs);\n // A queued task must not keep the process alive on its own.\n timer.unref?.();\n this.pending.set(taskId, timer);\n\n log.debug(`queued local task ${taskId} -> ${request.targetUrl}${request.path} (delay ${delayMs}ms)`);\n return new JobReference(taskId);\n }\n\n override async delete(ref: JobReference): Promise<void> {\n const timer = this.pending.get(ref.taskId);\n if (timer) {\n clearTimeout(timer);\n this.pending.delete(ref.taskId);\n }\n }\n\n /** ms until the scheduled run time (0 = as soon as possible / already due). */\n private computeDelayMs(epochMsToRunAt?: number): number {\n if (epochMsToRunAt === undefined) {\n return 0;\n }\n const delay = epochMsToRunAt - Date.now();\n return delay > 0 ? delay : 0;\n }\n\n /** Deliver the queued task over real HTTP, mirroring Cloud Tasks' HTTP callback. */\n private async deliver(request: TaskRequest, taskId: string): Promise<void> {\n const url = `${request.targetUrl}${request.path}`;\n const headers = await this.buildHeaders(request);\n log.info(`delivering local task ${taskId}: POST ${url}`);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- detached queue job: log delivery failure, never crash the queue\n try {\n // webpieces-disable no-fetch -- in-memory queue delivers over real HTTP like Cloud Tasks (no server to invoke in-process)\n const response = await fetch(url, {\n method: 'POST',\n headers: headers,\n body: JSON.stringify(request.body ?? {}),\n });\n if (!response.ok) {\n log.error(`local task ${taskId} delivery to ${url} failed: HTTP ${response.status}`);\n return;\n }\n log.debug(`local task ${taskId} delivered: HTTP ${response.status}`);\n } catch (err: unknown) {\n const error = toError(err);\n log.error(`local task ${taskId} delivery to ${url} threw: ${error.message}`);\n }\n }\n\n /** content-type + propagated context headers + synthesized delivery credential. */\n private async buildHeaders(request: TaskRequest): Promise<Record<string, string>> {\n const headers: Record<string, string> = { 'content-type': 'application/json' };\n for (const entry of request.contextHeaders.entries()) {\n headers[entry[0]] = entry[1];\n }\n await this.attachAuth(request, headers);\n return headers;\n }\n\n /** Synthesize the delivery credential Cloud Tasks would attach, per the auth mode. */\n private async attachAuth(request: TaskRequest, headers: Record<string, string>): Promise<void> {\n const mode = request.authMode;\n if (mode.kind === 'oidc') {\n headers['authorization'] = `Bearer ${await mintIdToken(request.targetUrl)}`;\n return;\n }\n if (mode.kind === 'shared-secret') {\n const secret = this.secrets?.get(mode.secretKey);\n if (secret) {\n headers['x-webpieces-shared-secret'] = secret;\n }\n }\n // public / jwt → no service credential is synthesized.\n }\n}\n"]}
1
+ {"version":3,"file":"InMemoryTaskInvoker.js","sourceRoot":"","sources":["../../../../../packages/cloud/cloudtasks-client/src/InMemoryTaskInvoker.ts"],"names":[],"mappings":"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","sourcesContent":["import { injectable, inject, optional } from 'inversify';\nimport { provideFrameworkSingleton } from '@webpieces/core-context';\nimport { mintIdToken } from '@webpieces/gcp-identity';\nimport { LogManager, toError, Secrets } from '@webpieces/core-util';\nimport { TaskInvoker, TaskRequest, JobReference } from './TaskTypes';\n\nconst log = LogManager.getLogger('InMemoryTaskInvoker');\n\n/**\n * TaskInvoker for tests + local dev — the in-memory-queue twin of GcpTaskInvoker\n * (a TypeScript port of webpieces-java's LocalRemoteInvoker).\n *\n * Instead of enqueuing to Google Cloud Tasks, it drops the delivery onto an in-process\n * timer queue and returns IMMEDIATELY (breaking from the caller, exactly like handing a\n * task to Cloud Tasks). The queued job then delivers the task the SAME way real Cloud\n * Tasks does: a plain HTTP POST to `targetUrl + path` with the JSON body and the\n * synthesized delivery auth (OIDC bearer / shared-secret). It does NOT invoke the server\n * in-process — the target may be a different process entirely — so the request travels\n * over real HTTP (e.g. http://localhost:{port}/queue/path) and is served by the target's\n * real routing + filter chain, giving production parity without GCP.\n *\n * Bind it (via appOverrides) in place of GcpTaskInvoker:\n * bind(TaskInvoker).to(InMemoryTaskInvoker)\n */\n@provideFrameworkSingleton()\n@injectable()\nexport class InMemoryTaskInvoker extends TaskInvoker {\n private counter = 0;\n /** Scheduled (not-yet-delivered) jobs, so delete() can cancel them. */\n private readonly pending = new Map<string, ReturnType<typeof setTimeout>>();\n\n // @optional: only @AuthSharedSecret task endpoints need it; the client sends its bound value.\n constructor(@optional() @inject(Secrets) private readonly secrets?: Secrets) {\n super();\n }\n\n override async enqueue(request: TaskRequest): Promise<JobReference> {\n this.counter += 1;\n const taskId = request.scheduleInfo.dedupName ?? `inmem-${request.queueName}-${this.counter}`;\n const delayMs = this.computeDelayMs(request.scheduleInfo.epochMsToRunAt);\n\n // Break from the caller: queue the HTTP delivery and return the reference NOW.\n const timer = setTimeout(() => {\n this.pending.delete(taskId);\n void this.deliver(request, taskId);\n }, delayMs);\n // A queued task must not keep the process alive on its own.\n timer.unref?.();\n this.pending.set(taskId, timer);\n\n log.debug(`queued local task ${taskId} -> ${request.targetUrl}${request.path} (delay ${delayMs}ms)`);\n return new JobReference(taskId);\n }\n\n override async delete(ref: JobReference): Promise<void> {\n const timer = this.pending.get(ref.taskId);\n if (timer) {\n clearTimeout(timer);\n this.pending.delete(ref.taskId);\n }\n }\n\n /** ms until the scheduled run time (0 = as soon as possible / already due). */\n private computeDelayMs(epochMsToRunAt?: number): number {\n if (epochMsToRunAt === undefined) {\n return 0;\n }\n const delay = epochMsToRunAt - Date.now();\n return delay > 0 ? delay : 0;\n }\n\n /** Deliver the queued task over real HTTP, mirroring Cloud Tasks' HTTP callback. */\n private async deliver(request: TaskRequest, taskId: string): Promise<void> {\n const url = `${request.targetUrl}${request.path}`;\n const headers = await this.buildHeaders(request);\n log.info(`delivering local task ${taskId}: POST ${url}`);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- detached queue job: log delivery failure, never crash the queue\n try {\n // webpieces-disable no-fetch -- in-memory queue delivers over real HTTP like Cloud Tasks (no server to invoke in-process)\n const response = await fetch(url, {\n method: 'POST',\n headers: headers,\n body: JSON.stringify(request.body ?? {}),\n });\n if (!response.ok) {\n log.error(`local task ${taskId} delivery to ${url} failed: HTTP ${response.status}`);\n return;\n }\n log.debug(`local task ${taskId} delivered: HTTP ${response.status}`);\n } catch (err: unknown) {\n const error = toError(err);\n log.error(`local task ${taskId} delivery to ${url} threw: ${error.message}`);\n }\n }\n\n /** content-type + propagated context headers + synthesized delivery credential. */\n private async buildHeaders(request: TaskRequest): Promise<Record<string, string>> {\n const headers: Record<string, string> = { 'content-type': 'application/json' };\n for (const entry of request.contextHeaders.entries()) {\n headers[entry[0]] = entry[1];\n }\n await this.attachAuth(request, headers);\n return headers;\n }\n\n /** Synthesize the delivery credential Cloud Tasks would attach, per the auth mode. */\n private async attachAuth(request: TaskRequest, headers: Record<string, string>): Promise<void> {\n const mode = request.authMode;\n if (mode.kind === 'oidc') {\n headers['authorization'] = `Bearer ${await mintIdToken(request.targetUrl)}`;\n return;\n }\n if (mode.kind === 'shared-secret') {\n const secret = this.secrets?.get(mode.secretKey);\n if (secret) {\n // One credential header; the 'Webpieces' scheme says a shared secret follows, not a token.\n headers['authorization'] = `Webpieces ${secret}`;\n }\n }\n // public / jwt → no service credential is synthesized.\n }\n}\n"]}
@@ -5,16 +5,48 @@ export type ApiPrototype<T> = Function & {
5
5
  /**
6
6
  * Per-client STATE for a Cloud Tasks enqueue client — nothing else.
7
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.
8
+ * Collaborators (TaskInvoker, RequestContextHeaders) are NOT config: they are dependencies of
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.
15
11
  */
16
12
  export declare class TaskClientConfig {
17
- /** The callee's Cloud Run service name (e.g. 'email-svc'). */
18
- gcpCloudRunSvcName: string;
19
- constructor(gcpCloudRunSvcName: string);
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.
24
+ */
25
+ 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
+ constructor(
32
+ /**
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`.
48
+ */
49
+ targetUrl?: string | undefined);
50
+ /** Resolved per enqueue, not at construction — so building a client stays synchronous. */
51
+ resolveTargetUrl(): Promise<string>;
20
52
  }
@@ -1,22 +1,42 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TaskClientConfig = void 0;
4
+ const gcp_identity_1 = require("@webpieces/gcp-identity");
4
5
  /**
5
6
  * Per-client STATE for a Cloud Tasks enqueue client — nothing else.
6
7
  *
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.
8
+ * Collaborators (TaskInvoker, RequestContextHeaders) are NOT config: they are dependencies of
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.
14
11
  */
15
12
  class TaskClientConfig {
16
- /** The callee's Cloud Run service name (e.g. 'email-svc'). */
17
- gcpCloudRunSvcName;
18
- constructor(gcpCloudRunSvcName) {
19
- this.gcpCloudRunSvcName = gcpCloudRunSvcName;
13
+ svcName;
14
+ targetUrl;
15
+ constructor(
16
+ /**
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.
27
+ */
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) {
34
+ this.svcName = svcName;
35
+ this.targetUrl = targetUrl;
36
+ }
37
+ /** 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);
20
40
  }
21
41
  }
22
42
  exports.TaskClientConfig = TaskClientConfig;
@@ -1 +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"]}
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,27 +1,33 @@
1
- import { ContextMgr } from '@webpieces/core-context';
1
+ import { RequestContextHeaders } from '@webpieces/core-context';
2
2
  import { TaskInvoker } from './TaskTypes';
3
3
  import { ApiPrototype, TaskClientConfig } from './TaskClientConfig';
4
4
  /**
5
5
  * TaskProxyClient - the enqueue engine behind one @PubSub API contract's client proxy.
6
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:
7
+ * The fire-and-forget twin of http-client-core's ProxyClient, and TWO-PHASE for the same reason:
8
+ * its COLLABORATORS (invoker, headers) come from the container, while the PER-CLIENT state (which
9
+ * contract, which target) arrives on {@link init}. That is what lets {@link ClientCloudTasksFactory}
10
+ * hold a `Provider<TaskProxyClient>` and hand out a fresh, independently-configured client per
11
+ * contract.
12
+ *
13
+ * Calling an endpoint ENQUEUES a task (it does not call remotely); the task is later delivered to
14
+ * the same endpoint's controller through the full server filter chain.
15
+ *
16
+ * It owns:
10
17
  * - @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)
18
+ * - Resolving the callee's base URL (from svcName, or the explicit targetUrl)
19
+ * - Context propagation onto the task headers (a credential is never a context key, so none can ride along)
13
20
  * - 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
21
  */
18
22
  export declare class TaskProxyClient {
19
- private config;
20
- private invoker;
21
- private contextMgr;
23
+ private readonly invoker;
24
+ private readonly headers;
22
25
  private plans;
23
26
  private apiName;
24
- constructor(apiClass: ApiPrototype<object>, config: TaskClientConfig, invoker: TaskInvoker, contextMgr: ContextMgr);
27
+ private config;
28
+ constructor(invoker: TaskInvoker, headers: RequestContextHeaders);
29
+ /** Bind this client to one @PubSub contract + target. */
30
+ init(apiClass: ApiPrototype<object>, config: TaskClientConfig): void;
25
31
  /** Check whether the contract declares a @PubSub endpoint with this method name. */
26
32
  hasEndpoint(methodName: string): boolean;
27
33
  /**
@@ -32,6 +38,22 @@ export declare class TaskProxyClient {
32
38
  enqueue(methodName: string, requestDto: unknown): Promise<void>;
33
39
  /** Endpoint name -> its resolved path / queue / auth mode, read once from the decorators. */
34
40
  private buildPlans;
35
- /** Transferred context keys (txId/requestId/tenant…) MINUS the caller's auth credentials. */
41
+ /**
42
+ * Every transferred context key (txId/requestId/tenant…), request-id chained.
43
+ * Throws if there is no active RequestContext — an enqueue with no trace is a bug.
44
+ *
45
+ * No credential can appear here: `authorization` is read off the inbound HttpRequest and is not
46
+ * a ContextKey, so it never enters the RequestContext to be transferred. The invoker mints the
47
+ * task's own delivery auth per the endpoint's @AuthOidc / @AuthSharedSecret mode.
48
+ */
36
49
  private buildContextHeaders;
37
50
  }
51
+ /**
52
+ * DI token for the `Provider<TaskProxyClient>` that hands out enqueue clients — one per @PubSub
53
+ * contract. `Provider<T>` is erased at runtime, so it cannot be its own token; this Symbol names T.
54
+ *
55
+ * Because TaskProxyClient is bound TRANSIENT, every `get()` constructs a new one. (Were it bound
56
+ * `@provideFrameworkSingleton`, the very same Provider would hand back one lazily-created instance
57
+ * instead — the provider caches nothing, so the target's scope decides.)
58
+ */
59
+ export declare const TASK_PROXY_CLIENT_PROVIDER: unique symbol;
@@ -1,20 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TaskProxyClient = void 0;
3
+ exports.TASK_PROXY_CLIENT_PROVIDER = exports.TaskProxyClient = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ const inversify_1 = require("inversify");
5
6
  const core_util_1 = require("@webpieces/core-util");
6
7
  const core_context_1 = require("@webpieces/core-context");
7
- const gcp_identity_1 = require("@webpieces/gcp-identity");
8
8
  const TaskTypes_1 = require("./TaskTypes");
9
9
  const ScheduleContext_1 = require("./ScheduleContext");
10
- const TaskClientConfig_1 = require("./TaskClientConfig");
11
10
  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
11
  /** Per-endpoint routing plan resolved once from the contract's decorators. */
19
12
  class EndpointPlan {
20
13
  path;
@@ -29,32 +22,40 @@ class EndpointPlan {
29
22
  /**
30
23
  * TaskProxyClient - the enqueue engine behind one @PubSub API contract's client proxy.
31
24
  *
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:
25
+ * The fire-and-forget twin of http-client-core's ProxyClient, and TWO-PHASE for the same reason:
26
+ * its COLLABORATORS (invoker, headers) come from the container, while the PER-CLIENT state (which
27
+ * contract, which target) arrives on {@link init}. That is what lets {@link ClientCloudTasksFactory}
28
+ * hold a `Provider<TaskProxyClient>` and hand out a fresh, independently-configured client per
29
+ * contract.
30
+ *
31
+ * Calling an endpoint ENQUEUES a task (it does not call remotely); the task is later delivered to
32
+ * the same endpoint's controller through the full server filter chain.
33
+ *
34
+ * It owns:
35
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)
36
+ * - Resolving the callee's base URL (from svcName, or the explicit targetUrl)
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
- *
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
39
  */
43
40
  let TaskProxyClient = class TaskProxyClient {
44
- config;
45
41
  invoker;
46
- contextMgr;
42
+ headers;
43
+ // Assigned by init(), which the factory calls immediately after construction.
47
44
  plans;
48
45
  apiName;
49
- constructor(apiClass, config, invoker, contextMgr) {
50
- this.config = config;
46
+ config;
47
+ constructor(invoker, headers) {
51
48
  this.invoker = invoker;
52
- this.contextMgr = contextMgr;
49
+ this.headers = headers;
50
+ }
51
+ /** Bind this client to one @PubSub contract + target. */
52
+ init(apiClass, config) {
53
53
  if (!(0, core_util_1.isApiPath)(apiClass)) {
54
54
  throw new Error(`Class ${apiClass.name || 'Unknown'} must be decorated with @ApiPath()`);
55
55
  }
56
56
  (0, core_util_1.assertPubSubConventions)(apiClass);
57
57
  (0, core_util_1.assertEveryEndpointHasAuthMode)(apiClass);
58
+ this.config = config;
58
59
  this.apiName = apiClass.name || 'UnknownApi';
59
60
  this.plans = this.buildPlans(apiClass);
60
61
  }
@@ -79,11 +80,12 @@ let TaskProxyClient = class TaskProxyClient {
79
80
  'scheduler.addToQueue(() => taskClient.method(req), { dedupName }).');
80
81
  }
81
82
  // 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
+ // Every metadata read beneath resolveTargetUrl is memoized process-wide, so only the
83
84
  // first enqueue in the process pays a lookup.
84
- const targetUrl = await (0, gcp_identity_1.getCloudRunUrl)(this.config.gcpCloudRunSvcName);
85
+ const targetUrl = await this.config.resolveTargetUrl();
85
86
  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
+ // svcName, not the URL, is the stable name across demo/qa/prod.
88
+ log.debug(`enqueue task ${plan.queueName} -> ${this.config.svcName}${plan.path}`);
87
89
  frame.jobRef = await this.invoker.enqueue(request);
88
90
  }
89
91
  /** Endpoint name -> its resolved path / queue / auth mode, read once from the decorators. */
@@ -101,22 +103,35 @@ let TaskProxyClient = class TaskProxyClient {
101
103
  }
102
104
  return plans;
103
105
  }
104
- /** Transferred context keys (txId/requestId/tenant…) MINUS the caller's auth credentials. */
106
+ /**
107
+ * Every transferred context key (txId/requestId/tenant…), request-id chained.
108
+ * Throws if there is no active RequestContext — an enqueue with no trace is a bug.
109
+ *
110
+ * No credential can appear here: `authorization` is read off the inbound HttpRequest and is not
111
+ * a ContextKey, so it never enters the RequestContext to be transferred. The invoker mints the
112
+ * task's own delivery auth per the endpoint's @AuthOidc / @AuthSharedSecret mode.
113
+ */
105
114
  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;
115
+ return this.headers.buildOutboundHeaders();
113
116
  }
114
117
  };
115
118
  exports.TaskProxyClient = TaskProxyClient;
116
119
  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])
120
+ (0, core_context_1.provideFrameworkTransient)(),
121
+ (0, inversify_1.injectable)(),
122
+ tslib_1.__param(0, (0, inversify_1.inject)(TaskTypes_1.TaskInvoker)),
123
+ tslib_1.__param(1, (0, inversify_1.inject)(core_context_1.RequestContextHeaders)),
124
+ tslib_1.__metadata("design:paramtypes", [TaskTypes_1.TaskInvoker,
125
+ core_context_1.RequestContextHeaders])
121
126
  ], TaskProxyClient);
127
+ /**
128
+ * DI token for the `Provider<TaskProxyClient>` that hands out enqueue clients — one per @PubSub
129
+ * contract. `Provider<T>` is erased at runtime, so it cannot be its own token; this Symbol names T.
130
+ *
131
+ * Because TaskProxyClient is bound TRANSIENT, every `get()` constructs a new one. (Were it bound
132
+ * `@provideFrameworkSingleton`, the very same Provider would hand back one lazily-created instance
133
+ * instead — the provider caches nothing, so the target's scope decides.)
134
+ */
135
+ // webpieces-disable no-symbol-di-tokens -- Provider<T> is erased at runtime; the Symbol names T
136
+ exports.TASK_PROXY_CLIENT_PROVIDER = Symbol.for('Provider<TaskProxyClient>');
122
137
  //# sourceMappingURL=TaskProxyClient.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TaskProxyClient.js","sourceRoot":"","sources":["../../../../../packages/cloud/cloudtasks-client/src/TaskProxyClient.ts"],"names":[],"mappings":";;;;AAAA,oDAW8B;AAC9B,0DAAqD;AACrD,0DAAyD;AACzD,2CAAqE;AACrE,uDAAyD;AACzD,yDAAoE;AAEpE,MAAM,GAAG,GAAG,sBAAU,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;AAEpD;;;;GAIG;AACH,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAS,CAAC,eAAe,EAAE,2BAA2B,CAAC,CAAC,CAAC;AAE1F,8EAA8E;AAC9E,MAAM,YAAY;IACd,IAAI,CAAS;IACb,SAAS,CAAS;IAClB,QAAQ,CAAW;IAEnB,YAAY,IAAY,EAAE,SAAiB,EAAE,QAAkB;QAC3D,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC7B,CAAC;CACJ;AAED;;;;;;;;;;;;;GAaG;AAEI,IAAM,eAAe,GAArB,MAAM,eAAe;IAMZ;IACA;IACA;IAPJ,KAAK,CAA4B;IACjC,OAAO,CAAS;IAExB,YACI,QAA8B,EACtB,MAAwB,EACxB,OAAoB,EACpB,UAAsB;QAFtB,WAAM,GAAN,MAAM,CAAkB;QACxB,YAAO,GAAP,OAAO,CAAa;QACpB,eAAU,GAAV,UAAU,CAAY;QAE9B,IAAI,CAAC,IAAA,qBAAS,EAAC,QAAQ,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,SAAS,QAAQ,CAAC,IAAI,IAAI,SAAS,oCAAoC,CAAC,CAAC;QAC7F,CAAC;QACD,IAAA,mCAAuB,EAAC,QAAQ,CAAC,CAAC;QAClC,IAAA,0CAA8B,EAAC,QAAQ,CAAC,CAAC;QAEzC,IAAI,CAAC,OAAO,GAAG,QAAQ,CAAC,IAAI,IAAI,YAAY,CAAC;QAC7C,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC3C,CAAC;IAED,oFAAoF;IACpF,WAAW,CAAC,UAAkB;QAC1B,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACtC,CAAC;IAED;;;;OAIG;IACH,6FAA6F;IAC7F,KAAK,CAAC,OAAO,CAAC,UAAkB,EAAE,UAAmB;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI,EAAE,CAAC;YACR,MAAM,IAAI,KAAK,CAAC,wBAAwB,UAAU,QAAQ,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9E,CAAC;QAED,MAAM,KAAK,GAAG,IAAA,sCAAoB,GAAE,CAAC;QACrC,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CACX,uEAAuE;gBACvE,oEAAoE,CACvE,CAAC;QACN,CAAC;QAED,uFAAuF;QACvF,mFAAmF;QACnF,8CAA8C;QAC9C,MAAM,SAAS,GAAG,MAAM,IAAA,6BAAc,EAAC,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC;QAEvE,MAAM,OAAO,GAAG,IAAI,uBAAW,CAC3B,SAAS,EACT,IAAI,CAAC,IAAI,EACT,IAAI,CAAC,SAAS,EACd,UAAU,EACV,IAAI,CAAC,mBAAmB,EAAE,EAC1B,IAAI,CAAC,QAAQ,EACb,KAAK,CAAC,IAAI,IAAI,IAAI,wBAAY,EAAE,CACnC,CAAC;QAEF,GAAG,CAAC,KAAK,CAAC,gBAAgB,IAAI,CAAC,SAAS,OAAO,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACxE,KAAK,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD,CAAC;IAED,6FAA6F;IACrF,UAAU,CAAC,QAA8B;QAC7C,MAAM,QAAQ,GAAG,IAAA,sBAAU,EAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC5C,MAAM,SAAS,GAAG,IAAA,wBAAY,EAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC/C,MAAM,KAAK,GAAG,IAAI,GAAG,EAAwB,CAAC;QAE9C,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YAC9C,MAAM,QAAQ,GAAG,IAAA,uBAAW,EAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YACnD,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,aAAa,UAAU,QAAQ,IAAI,CAAC,OAAO,mBAAmB,CAAC,CAAC;YACpF,CAAC;YACD,MAAM,IAAI,GAAG,IAAI,YAAY,CACzB,QAAQ,GAAG,SAAS,CAAC,UAAU,CAAC,EAChC,IAAA,wBAAY,EAAC,QAAQ,EAAE,UAAU,CAAC,EAClC,QAAQ,CACX,CAAC;YACF,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,6FAA6F;IACrF,mBAAmB;QACvB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC1C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,oBAAoB,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC;YACnE,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;gBACjD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACpC,CAAC;QACL,CAAC;QACD,OAAO,OAAO,CAAC;IACnB,CAAC;CACJ,CAAA;AA/FY,0CAAe;0BAAf,eAAe;IAD3B,IAAA,0BAAc,GAAE;qDAOO,mCAAgB;QACf,uBAAW;QACR,yBAAU;GARzB,eAAe,CA+F3B","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"]}
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"]}
package/src/index.d.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  */
9
9
  export { ScheduleInfo, JobReference, TaskRequest, TaskInvoker, } from './TaskTypes';
10
10
  export { ClientCloudTasksFactory } from './ClientCloudTasksFactory';
11
- export { TaskProxyClient } from './TaskProxyClient';
11
+ export { TaskProxyClient, TASK_PROXY_CLIENT_PROVIDER } from './TaskProxyClient';
12
12
  export { TaskClientConfig } from './TaskClientConfig';
13
13
  export type { ApiPrototype } from './TaskClientConfig';
14
14
  export { CloudTaskScheduler, ScheduleOptions } from './CloudTaskScheduler';
package/src/index.js CHANGED
@@ -8,7 +8,7 @@
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.TaskClientConfig = exports.TaskProxyClient = exports.ClientCloudTasksFactory = 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.TASK_PROXY_CLIENT_PROVIDER = 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; } });
@@ -18,6 +18,7 @@ var ClientCloudTasksFactory_1 = require("./ClientCloudTasksFactory");
18
18
  Object.defineProperty(exports, "ClientCloudTasksFactory", { enumerable: true, get: function () { return ClientCloudTasksFactory_1.ClientCloudTasksFactory; } });
19
19
  var TaskProxyClient_1 = require("./TaskProxyClient");
20
20
  Object.defineProperty(exports, "TaskProxyClient", { enumerable: true, get: function () { return TaskProxyClient_1.TaskProxyClient; } });
21
+ Object.defineProperty(exports, "TASK_PROXY_CLIENT_PROVIDER", { enumerable: true, get: function () { return TaskProxyClient_1.TASK_PROXY_CLIENT_PROVIDER; } });
21
22
  var TaskClientConfig_1 = require("./TaskClientConfig");
22
23
  Object.defineProperty(exports, "TaskClientConfig", { enumerable: true, get: function () { return TaskClientConfig_1.TaskClientConfig; } });
23
24
  var CloudTaskScheduler_1 = require("./CloudTaskScheduler");
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,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
+ {"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,qDAAgF;AAAvE,kHAAA,eAAe,OAAA;AAAE,6HAAA,0BAA0B,OAAA;AACpD,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, TASK_PROXY_CLIENT_PROVIDER } 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"]}