@io-orkes/conductor-javascript 3.0.0 → 3.0.2

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
@@ -77,7 +77,7 @@ await workflow.register();
77
77
 
78
78
  **Step 2: Write a worker**
79
79
 
80
- Workers are TypeScript functions decorated with `@worker` that poll Conductor for tasks and execute them.
80
+ Workers are TypeScript functions decorated with `@worker` that poll Conductor for tasks and execute them. The example below uses the legacy decorator style (standalone function). See [Workers](#workers) for the new TypeScript 5.0+ decorator style (class methods).
81
81
 
82
82
  ```typescript
83
83
  import { worker } from "@io-orkes/conductor-javascript";
@@ -215,30 +215,77 @@ All of these are type-safe, composable, and registered to the server as JSON —
215
215
 
216
216
  ## Workers
217
217
 
218
- Workers are TypeScript functions that execute Conductor tasks. Decorate any function with `@worker` to register it as a worker (auto-discovered by `TaskHandler`) and use it as a workflow task.
218
+ Workers are TypeScript functions that execute Conductor tasks. Decorate functions with `@worker` to register them as workers (auto-discovered by `TaskHandler`) and use them as workflow tasks.
219
+
220
+ The SDK supports **both** decorator styles:
221
+
222
+ ### Option 1: New decorators (TypeScript 5.0+)
223
+
224
+ Use class methods with the new Stage 3 decorators. No `experimentalDecorators` needed — remove it from your `tsconfig.json`.
225
+
226
+ ```typescript
227
+ import { worker, TaskHandler } from "@io-orkes/conductor-javascript";
228
+ import type { Task } from "@io-orkes/conductor-javascript";
229
+
230
+ class Workers {
231
+ @worker({ taskDefName: "greet", concurrency: 5, pollInterval: 100 })
232
+ async greet(task: Task) {
233
+ return {
234
+ status: "COMPLETED" as const,
235
+ outputData: { result: `Hello ${task.inputData?.name ?? "World"}` },
236
+ };
237
+ }
238
+
239
+ @worker({ taskDefName: "process_payment", domain: "payments" })
240
+ async processPayment(task: Task) {
241
+ const result = await paymentGateway.charge(task.inputData.customerId, task.inputData.amount);
242
+ return { status: "COMPLETED" as const, outputData: { transactionId: result.id } };
243
+ }
244
+ }
245
+
246
+ // Class definition triggers decorators — workers are registered
247
+ void new Workers();
248
+
249
+ const handler = new TaskHandler({ client, scanForDecorated: true });
250
+ await handler.startWorkers();
251
+ ```
252
+
253
+ ### Option 2: Legacy decorators (experimentalDecorators)
254
+
255
+ Use standalone functions. Add `"experimentalDecorators": true` to your `tsconfig.json`.
219
256
 
220
257
  ```typescript
221
258
  import { worker, TaskHandler } from "@io-orkes/conductor-javascript";
259
+ import type { Task } from "@io-orkes/conductor-javascript";
222
260
 
223
261
  @worker({ taskDefName: "greet", concurrency: 5, pollInterval: 100 })
224
262
  async function greet(task: Task) {
225
263
  return {
226
- status: "COMPLETED",
227
- outputData: { result: `Hello ${task.inputData.name}` },
264
+ status: "COMPLETED" as const,
265
+ outputData: { result: `Hello ${task.inputData?.name ?? "World"}` },
228
266
  };
229
267
  }
230
268
 
231
269
  @worker({ taskDefName: "process_payment", domain: "payments" })
232
270
  async function processPayment(task: Task) {
233
271
  const result = await paymentGateway.charge(task.inputData.customerId, task.inputData.amount);
234
- return { status: "COMPLETED", outputData: { transactionId: result.id } };
272
+ return { status: "COMPLETED" as const, outputData: { transactionId: result.id } };
235
273
  }
236
274
 
237
- // Auto-discover and start all decorated workers
238
275
  const handler = new TaskHandler({ client, scanForDecorated: true });
239
276
  await handler.startWorkers();
277
+ ```
278
+
279
+ ### tsconfig setup
240
280
 
241
- // Graceful shutdown
281
+ | Decorator style | tsconfig.json |
282
+ |-----------------|---------------|
283
+ | **New** (TypeScript 5.0+) | Omit `experimentalDecorators` — use class methods |
284
+ | **Legacy** | `"experimentalDecorators": true` — use standalone functions |
285
+
286
+ **Graceful shutdown:**
287
+
288
+ ```typescript
242
289
  process.on("SIGTERM", async () => {
243
290
  await handler.stopWorkers();
244
291
  process.exit(0);
package/dist/index.d.mts CHANGED
@@ -5289,6 +5289,11 @@ declare class TaskRunner {
5289
5289
  get isPaused(): boolean;
5290
5290
  get getOptions(): TaskRunnerOptions;
5291
5291
  private batchPoll;
5292
+ /**
5293
+ * Probed once per process. null = unknown, true = v2 endpoint available,
5294
+ * false = legacy server (no /api/tasks/update-v2 endpoint).
5295
+ */
5296
+ private static updateV2Available;
5292
5297
  updateTaskWithRetry: (task: Task, taskResult: TaskResult) => Promise<Task | undefined>;
5293
5298
  private isValidTask;
5294
5299
  /**
@@ -6317,7 +6322,16 @@ interface WorkerOptions {
6317
6322
  * }
6318
6323
  * ```
6319
6324
  */
6320
- declare function worker(options: WorkerOptions): (target: unknown, propertyKey?: string, descriptor?: PropertyDescriptor) => PropertyDescriptor | ((this: unknown, ...args: unknown[]) => unknown);
6325
+ /** Minimal context shape for Stage 3 method decorators (TypeScript 5.0+). */
6326
+ interface MethodDecoratorContext {
6327
+ kind: string;
6328
+ name: string | symbol;
6329
+ }
6330
+ type WorkerMethod = (task: Task) => Promise<Omit<TaskResult, "workflowInstanceId" | "taskId">>;
6331
+ declare function worker(options: WorkerOptions): {
6332
+ <T extends WorkerMethod>(value: T, context: MethodDecoratorContext): T | undefined;
6333
+ (target: object, propertyKey?: string, descriptor?: PropertyDescriptor): PropertyDescriptor | WorkerMethod | undefined;
6334
+ };
6321
6335
 
6322
6336
  /**
6323
6337
  * Registered worker metadata stored in the global registry.
@@ -6717,9 +6731,12 @@ interface SchemaFieldOptions {
6717
6731
  * When used with `generateSchemaFromClass()`, produces a JSON Schema draft-07
6718
6732
  * object from the decorated properties.
6719
6733
  *
6720
- * If `emitDecoratorMetadata` is enabled in tsconfig.json, the TypeScript type
6721
- * is automatically inferred for `string`, `number`, `boolean` — no need to
6722
- * specify `type` explicitly for those.
6734
+ * Supports both TypeScript 5.0+ (Stage 3) and legacy (experimentalDecorators)
6735
+ * decorator APIs.
6736
+ *
6737
+ * If `emitDecoratorMetadata` is enabled in tsconfig.json (legacy mode), the
6738
+ * TypeScript type is automatically inferred for `string`, `number`, `boolean` —
6739
+ * no need to specify `type` explicitly for those.
6723
6740
  *
6724
6741
  * @example
6725
6742
  * ```typescript
@@ -6737,7 +6754,10 @@ interface SchemaFieldOptions {
6737
6754
  * const schema = generateSchemaFromClass(OrderInput);
6738
6755
  * ```
6739
6756
  */
6740
- declare function schemaField(options?: SchemaFieldOptions): (target: object, propertyKey: string) => void;
6757
+ declare function schemaField(options?: SchemaFieldOptions): (targetOrValue: object | undefined, propertyKeyOrContext?: string | {
6758
+ kind: string;
6759
+ name: string | symbol;
6760
+ }) => ((initialValue: unknown) => unknown) | undefined;
6741
6761
  /**
6742
6762
  * Generate a JSON Schema (draft-07) from a class decorated with `@schemaField()`.
6743
6763
  *
package/dist/index.d.ts CHANGED
@@ -5289,6 +5289,11 @@ declare class TaskRunner {
5289
5289
  get isPaused(): boolean;
5290
5290
  get getOptions(): TaskRunnerOptions;
5291
5291
  private batchPoll;
5292
+ /**
5293
+ * Probed once per process. null = unknown, true = v2 endpoint available,
5294
+ * false = legacy server (no /api/tasks/update-v2 endpoint).
5295
+ */
5296
+ private static updateV2Available;
5292
5297
  updateTaskWithRetry: (task: Task, taskResult: TaskResult) => Promise<Task | undefined>;
5293
5298
  private isValidTask;
5294
5299
  /**
@@ -6317,7 +6322,16 @@ interface WorkerOptions {
6317
6322
  * }
6318
6323
  * ```
6319
6324
  */
6320
- declare function worker(options: WorkerOptions): (target: unknown, propertyKey?: string, descriptor?: PropertyDescriptor) => PropertyDescriptor | ((this: unknown, ...args: unknown[]) => unknown);
6325
+ /** Minimal context shape for Stage 3 method decorators (TypeScript 5.0+). */
6326
+ interface MethodDecoratorContext {
6327
+ kind: string;
6328
+ name: string | symbol;
6329
+ }
6330
+ type WorkerMethod = (task: Task) => Promise<Omit<TaskResult, "workflowInstanceId" | "taskId">>;
6331
+ declare function worker(options: WorkerOptions): {
6332
+ <T extends WorkerMethod>(value: T, context: MethodDecoratorContext): T | undefined;
6333
+ (target: object, propertyKey?: string, descriptor?: PropertyDescriptor): PropertyDescriptor | WorkerMethod | undefined;
6334
+ };
6321
6335
 
6322
6336
  /**
6323
6337
  * Registered worker metadata stored in the global registry.
@@ -6717,9 +6731,12 @@ interface SchemaFieldOptions {
6717
6731
  * When used with `generateSchemaFromClass()`, produces a JSON Schema draft-07
6718
6732
  * object from the decorated properties.
6719
6733
  *
6720
- * If `emitDecoratorMetadata` is enabled in tsconfig.json, the TypeScript type
6721
- * is automatically inferred for `string`, `number`, `boolean` — no need to
6722
- * specify `type` explicitly for those.
6734
+ * Supports both TypeScript 5.0+ (Stage 3) and legacy (experimentalDecorators)
6735
+ * decorator APIs.
6736
+ *
6737
+ * If `emitDecoratorMetadata` is enabled in tsconfig.json (legacy mode), the
6738
+ * TypeScript type is automatically inferred for `string`, `number`, `boolean` —
6739
+ * no need to specify `type` explicitly for those.
6723
6740
  *
6724
6741
  * @example
6725
6742
  * ```typescript
@@ -6737,7 +6754,10 @@ interface SchemaFieldOptions {
6737
6754
  * const schema = generateSchemaFromClass(OrderInput);
6738
6755
  * ```
6739
6756
  */
6740
- declare function schemaField(options?: SchemaFieldOptions): (target: object, propertyKey: string) => void;
6757
+ declare function schemaField(options?: SchemaFieldOptions): (targetOrValue: object | undefined, propertyKeyOrContext?: string | {
6758
+ kind: string;
6759
+ name: string | symbol;
6760
+ }) => ((initialValue: unknown) => unknown) | undefined;
6741
6761
  /**
6742
6762
  * Generate a JSON Schema (draft-07) from a class decorated with `@schemaField()`.
6743
6763
  *
package/dist/index.js CHANGED
@@ -39271,7 +39271,7 @@ var defaultRunnerOptions = {
39271
39271
  concurrency: DEFAULT_CONCURRENCY,
39272
39272
  batchPollingTimeout: DEFAULT_BATCH_POLLING_TIMEOUT
39273
39273
  };
39274
- var TaskRunner = class {
39274
+ var TaskRunner = class _TaskRunner {
39275
39275
  _client;
39276
39276
  worker;
39277
39277
  logger;
@@ -39396,21 +39396,94 @@ var TaskRunner = class {
39396
39396
  throw error;
39397
39397
  }
39398
39398
  };
39399
+ /**
39400
+ * Probed once per process. null = unknown, true = v2 endpoint available,
39401
+ * false = legacy server (no /api/tasks/update-v2 endpoint).
39402
+ */
39403
+ static updateV2Available = null;
39399
39404
  updateTaskWithRetry = async (task, taskResult) => {
39400
39405
  const { workerID } = this.options;
39401
39406
  let retryCount = 0;
39402
39407
  let lastError = null;
39403
39408
  while (retryCount < this.maxRetries) {
39404
39409
  try {
39410
+ if (process.env.CI) {
39411
+ console.log(
39412
+ `[TaskRunner] Submitting task result taskId=${taskResult.taskId} workflowId=${taskResult.workflowInstanceId} taskType=${this.worker.taskDefName} attempt=${retryCount + 1}/${this.maxRetries}`
39413
+ );
39414
+ }
39405
39415
  const updateStart = Date.now();
39406
- const { data: nextTask } = await TaskResource.updateTaskV2({
39407
- client: this._client,
39408
- body: {
39409
- ...taskResult,
39410
- workerId: workerID
39416
+ if (_TaskRunner.updateV2Available === false) {
39417
+ await TaskResource.updateTask({
39418
+ client: this._client,
39419
+ body: { ...taskResult, workerId: workerID },
39420
+ throwOnError: true
39421
+ });
39422
+ const updateDurationMs2 = Date.now() - updateStart;
39423
+ if (process.env.CI) {
39424
+ console.log(
39425
+ `[TaskRunner] Task result accepted (legacy) taskId=${taskResult.taskId} durationMs=${updateDurationMs2}`
39426
+ );
39411
39427
  }
39428
+ await this.eventDispatcher.publishTaskUpdateCompleted({
39429
+ taskType: this.worker.taskDefName,
39430
+ taskId: taskResult.taskId ?? "",
39431
+ workerId: workerID,
39432
+ workflowInstanceId: taskResult.workflowInstanceId,
39433
+ durationMs: updateDurationMs2,
39434
+ timestamp: /* @__PURE__ */ new Date()
39435
+ });
39436
+ return void 0;
39437
+ }
39438
+ const {
39439
+ data: nextTask,
39440
+ error,
39441
+ response
39442
+ } = await TaskResource.updateTaskV2({
39443
+ client: this._client,
39444
+ body: { ...taskResult, workerId: workerID },
39445
+ throwOnError: false
39412
39446
  });
39447
+ if (response.status === 404 || response.status === 405) {
39448
+ if (_TaskRunner.updateV2Available === null) {
39449
+ console.log(
39450
+ `[TaskRunner] /api/tasks/update-v2 not available (HTTP ${response.status}), falling back to legacy /api/tasks endpoint`
39451
+ );
39452
+ _TaskRunner.updateV2Available = false;
39453
+ }
39454
+ await TaskResource.updateTask({
39455
+ client: this._client,
39456
+ body: { ...taskResult, workerId: workerID },
39457
+ throwOnError: true
39458
+ });
39459
+ const updateDurationMs2 = Date.now() - updateStart;
39460
+ if (process.env.CI) {
39461
+ console.log(
39462
+ `[TaskRunner] Task result accepted (legacy) taskId=${taskResult.taskId} durationMs=${updateDurationMs2}`
39463
+ );
39464
+ }
39465
+ await this.eventDispatcher.publishTaskUpdateCompleted({
39466
+ taskType: this.worker.taskDefName,
39467
+ taskId: taskResult.taskId ?? "",
39468
+ workerId: workerID,
39469
+ workflowInstanceId: taskResult.workflowInstanceId,
39470
+ durationMs: updateDurationMs2,
39471
+ timestamp: /* @__PURE__ */ new Date()
39472
+ });
39473
+ return void 0;
39474
+ }
39475
+ if (!response.ok) {
39476
+ throw error ?? new Error(`Task update failed with HTTP ${response.status}`);
39477
+ }
39478
+ if (_TaskRunner.updateV2Available === null) {
39479
+ _TaskRunner.updateV2Available = true;
39480
+ }
39413
39481
  const updateDurationMs = Date.now() - updateStart;
39482
+ if (process.env.CI) {
39483
+ console.log(
39484
+ `[TaskRunner] Task result accepted taskId=${taskResult.taskId} durationMs=${updateDurationMs}`
39485
+ );
39486
+ }
39414
39487
  await this.eventDispatcher.publishTaskUpdateCompleted({
39415
39488
  taskType: this.worker.taskDefName,
39416
39489
  taskId: taskResult.taskId ?? "",
@@ -39427,6 +39500,9 @@ var TaskRunner = class {
39427
39500
  `Error updating task ${taskResult.taskId} on retry ${retryCount + 1}/${this.maxRetries}`,
39428
39501
  error
39429
39502
  );
39503
+ console.log(
39504
+ `[TaskRunner] Task update failed taskId=${taskResult.taskId} attempt=${retryCount + 1}/${this.maxRetries} error=${lastError?.message ?? String(error)}`
39505
+ );
39430
39506
  retryCount++;
39431
39507
  if (retryCount < this.maxRetries) {
39432
39508
  const delayMs = retryCount * 10 * 1e3;
@@ -41439,20 +41515,51 @@ var TaskHandler = class _TaskHandler {
41439
41515
 
41440
41516
  // src/sdk/worker/schema/decorators.ts
41441
41517
  var SCHEMA_METADATA_KEY = Symbol("conductor:schemaField");
41518
+ function isNewDecoratorContext(arg) {
41519
+ return typeof arg === "object" && arg !== null && "kind" in arg && typeof arg.kind === "string";
41520
+ }
41521
+ var schemaFieldProcessed = /* @__PURE__ */ new WeakMap();
41522
+ function storeSchemaFieldMetadata(cls, propertyKey, options, designType) {
41523
+ const existing = Reflect.getOwnMetadata(SCHEMA_METADATA_KEY, cls) ?? [];
41524
+ existing.push({
41525
+ ...options,
41526
+ propertyKey,
41527
+ designType
41528
+ });
41529
+ Reflect.defineMetadata(SCHEMA_METADATA_KEY, existing, cls);
41530
+ }
41442
41531
  function schemaField(options = {}) {
41443
- return function(target, propertyKey) {
41444
- const existing = Reflect.getOwnMetadata(SCHEMA_METADATA_KEY, target.constructor) ?? [];
41532
+ return function(targetOrValue, propertyKeyOrContext) {
41533
+ if (isNewDecoratorContext(propertyKeyOrContext)) {
41534
+ const propertyKey2 = String(propertyKeyOrContext.name);
41535
+ return function(initialValue) {
41536
+ const cls = this.constructor;
41537
+ const processed = schemaFieldProcessed.get(cls) ?? /* @__PURE__ */ new Set();
41538
+ if (!processed.has(propertyKey2)) {
41539
+ processed.add(propertyKey2);
41540
+ schemaFieldProcessed.set(cls, processed);
41541
+ let designType2;
41542
+ try {
41543
+ designType2 = Reflect.getMetadata(
41544
+ "design:type",
41545
+ this,
41546
+ propertyKey2
41547
+ );
41548
+ } catch {
41549
+ }
41550
+ storeSchemaFieldMetadata(cls, propertyKey2, options, designType2);
41551
+ }
41552
+ return initialValue;
41553
+ };
41554
+ }
41555
+ const target = targetOrValue;
41556
+ const propertyKey = propertyKeyOrContext;
41445
41557
  let designType;
41446
41558
  try {
41447
41559
  designType = Reflect.getMetadata("design:type", target, propertyKey);
41448
41560
  } catch {
41449
41561
  }
41450
- existing.push({
41451
- ...options,
41452
- propertyKey,
41453
- designType
41454
- });
41455
- Reflect.defineMetadata(SCHEMA_METADATA_KEY, existing, target.constructor);
41562
+ storeSchemaFieldMetadata(target.constructor, propertyKey, options, designType);
41456
41563
  };
41457
41564
  }
41458
41565
  function inferType(designType) {
@@ -41513,9 +41620,20 @@ function generateSchemaFromClass(cls) {
41513
41620
  }
41514
41621
 
41515
41622
  // src/sdk/worker/decorators/worker.ts
41623
+ function isNewDecoratorContext2(arg) {
41624
+ return typeof arg === "object" && arg !== null && "kind" in arg && typeof arg.kind === "string";
41625
+ }
41516
41626
  function worker(options) {
41517
- return function(target, propertyKey, descriptor) {
41518
- const executeFunction = descriptor?.value || target;
41627
+ function decorator(target, propertyKeyOrContext, descriptor) {
41628
+ let executeFunction;
41629
+ let isNewApi = false;
41630
+ if (isNewDecoratorContext2(propertyKeyOrContext)) {
41631
+ executeFunction = target;
41632
+ isNewApi = true;
41633
+ } else {
41634
+ const fn = descriptor?.value ?? target;
41635
+ executeFunction = fn;
41636
+ }
41519
41637
  if (typeof executeFunction !== "function") {
41520
41638
  throw new Error(
41521
41639
  `@worker decorator can only be applied to functions. Received: ${typeof executeFunction}`
@@ -41529,10 +41647,14 @@ function worker(options) {
41529
41647
  let resolvedInputSchema = options.inputSchema;
41530
41648
  let resolvedOutputSchema = options.outputSchema;
41531
41649
  if (options.inputType) {
41532
- resolvedInputSchema = generateSchemaFromClass(options.inputType);
41650
+ resolvedInputSchema = generateSchemaFromClass(
41651
+ options.inputType
41652
+ );
41533
41653
  }
41534
41654
  if (options.outputType) {
41535
- resolvedOutputSchema = generateSchemaFromClass(options.outputType);
41655
+ resolvedOutputSchema = generateSchemaFromClass(
41656
+ options.outputType
41657
+ );
41536
41658
  }
41537
41659
  const registeredWorker = {
41538
41660
  taskDefName: options.taskDefName,
@@ -41559,18 +41681,25 @@ function worker(options) {
41559
41681
  builderArgs.inputParameters ?? {}
41560
41682
  );
41561
41683
  }
41562
- return executeFunction.apply(this, args);
41684
+ return executeFunction.apply(
41685
+ this,
41686
+ args
41687
+ );
41563
41688
  };
41564
41689
  Object.defineProperty(dualModeFunction, "name", {
41565
41690
  value: executeFunction.name,
41566
41691
  configurable: true
41567
41692
  });
41693
+ if (isNewApi) {
41694
+ return dualModeFunction;
41695
+ }
41568
41696
  if (descriptor) {
41569
41697
  descriptor.value = dualModeFunction;
41570
41698
  return descriptor;
41571
41699
  }
41572
41700
  return dualModeFunction;
41573
- };
41701
+ }
41702
+ return decorator;
41574
41703
  }
41575
41704
 
41576
41705
  // src/sdk/worker/metrics/MetricsCollector.ts