@proompteng/temporal-bun-sdk 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +18 -0
  2. package/dist/agent-readiness.json +191 -5
  3. package/dist/production-readiness.json +409 -15
  4. package/dist/src/activities/lifecycle.d.ts.map +1 -1
  5. package/dist/src/activities/lifecycle.js +12 -5
  6. package/dist/src/activities/lifecycle.js.map +1 -1
  7. package/dist/src/bin/lint-workflows-command.d.ts +3 -3
  8. package/dist/src/bin/lint-workflows-command.d.ts.map +1 -1
  9. package/dist/src/bin/lint-workflows-command.js +5 -1
  10. package/dist/src/bin/lint-workflows-command.js.map +1 -1
  11. package/dist/src/bin/replay-command.d.ts +2 -2
  12. package/dist/src/bin/replay-command.d.ts.map +1 -1
  13. package/dist/src/bin/replay-command.js +9 -5
  14. package/dist/src/bin/replay-command.js.map +1 -1
  15. package/dist/src/bin/temporal-bun.d.ts +2 -2
  16. package/dist/src/client/interceptors.d.ts.map +1 -1
  17. package/dist/src/client/interceptors.js +1 -1
  18. package/dist/src/client/interceptors.js.map +1 -1
  19. package/dist/src/client.d.ts.map +1 -1
  20. package/dist/src/client.js +102 -35
  21. package/dist/src/client.js.map +1 -1
  22. package/dist/src/common/payloads/codecs.d.ts +4 -4
  23. package/dist/src/common/payloads/codecs.d.ts.map +1 -1
  24. package/dist/src/config.d.ts +10 -10
  25. package/dist/src/config.d.ts.map +1 -1
  26. package/dist/src/interceptors/types.d.ts.map +1 -1
  27. package/dist/src/interceptors/types.js +6 -4
  28. package/dist/src/interceptors/types.js.map +1 -1
  29. package/dist/src/otel/auto-instrumentations-node.d.ts +1 -1
  30. package/dist/src/otel/auto-instrumentations-node.d.ts.map +1 -1
  31. package/dist/src/worker/runtime.d.ts.map +1 -1
  32. package/dist/src/worker/runtime.js +169 -24
  33. package/dist/src/worker/runtime.js.map +1 -1
  34. package/dist/src/workflow/command-event-matrix.d.ts +13 -0
  35. package/dist/src/workflow/command-event-matrix.d.ts.map +1 -0
  36. package/dist/src/workflow/command-event-matrix.js +158 -0
  37. package/dist/src/workflow/command-event-matrix.js.map +1 -0
  38. package/dist/src/workflow/context.d.ts.map +1 -1
  39. package/dist/src/workflow/context.js +0 -11
  40. package/dist/src/workflow/context.js.map +1 -1
  41. package/dist/src/workflow/determinism.d.ts.map +1 -1
  42. package/dist/src/workflow/determinism.js +27 -1
  43. package/dist/src/workflow/determinism.js.map +1 -1
  44. package/dist/src/workflow/executor.d.ts.map +1 -1
  45. package/dist/src/workflow/executor.js +22 -21
  46. package/dist/src/workflow/executor.js.map +1 -1
  47. package/dist/src/workflow/guards.d.ts.map +1 -1
  48. package/dist/src/workflow/guards.js +141 -48
  49. package/dist/src/workflow/guards.js.map +1 -1
  50. package/dist/src/workflow/index.d.ts +1 -0
  51. package/dist/src/workflow/index.d.ts.map +1 -1
  52. package/dist/src/workflow/index.js +1 -0
  53. package/dist/src/workflow/index.js.map +1 -1
  54. package/docs/agent-adoption-guide.md +42 -1
  55. package/docs/default-choice-hardening-plan.md +183 -0
  56. package/docs/feature-matrix.md +23 -21
  57. package/docs/production-design.md +1 -1
  58. package/docs/production-readiness-implementation-plan.md +42 -22
  59. package/docs/semantic-readiness.md +133 -0
  60. package/docs/support-policy.md +16 -9
  61. package/package.json +9 -7
@@ -5,6 +5,7 @@ import { createGrpcTransport } from '@connectrpc/connect-node';
5
5
  import { Cause, Duration, Effect, Exit, Fiber, Schedule } from 'effect';
6
6
  import { makeActivityLifecycle, } from '../activities/lifecycle';
7
7
  import { buildTransportOptions, normalizeTemporalAddress } from '../client';
8
+ import { withTemporalRetry } from '../client/retries';
8
9
  import { durationFromMillis, durationToMillis } from '../common/duration';
9
10
  import { buildCodecsFromConfig, createDefaultDataConverter, decodePayloadsToValues, encodeValuesToPayloads, } from '../common/payloads/converter';
10
11
  import { encodeErrorToFailure, encodeFailurePayloads, failureToError } from '../common/payloads/failure';
@@ -12,7 +13,7 @@ import { sleep } from '../common/sleep';
12
13
  import { loadTemporalConfig } from '../config';
13
14
  import { makeDefaultWorkerInterceptors, runWorkerInterceptors, } from '../interceptors/worker';
14
15
  import { createObservabilityServices } from '../observability';
15
- import { CommandSchema, RecordMarkerCommandAttributesSchema, } from '../proto/temporal/api/command/v1/message_pb';
16
+ import { CancelWorkflowExecutionCommandAttributesSchema, CommandSchema, RecordMarkerCommandAttributesSchema, } from '../proto/temporal/api/command/v1/message_pb';
16
17
  import { PayloadsSchema, WorkflowExecutionSchema, } from '../proto/temporal/api/common/v1/message_pb';
17
18
  import { WorkerDeploymentOptionsSchema, } from '../proto/temporal/api/deployment/v1/message_pb';
18
19
  import { CommandType } from '../proto/temporal/api/enums/v1/command_type_pb';
@@ -206,7 +207,6 @@ export class WorkerRuntime {
206
207
  const versioningBehavior = workerVersioningMode === WorkerVersioningMode.VERSIONED
207
208
  ? (options.deployment?.versioningBehavior ?? VersioningBehavior.PINNED)
208
209
  : null;
209
- // TODO(TBS-NDG-002): enforce strict versioning policy.
210
210
  if (workflowGuards === 'strict') {
211
211
  if (workerVersioningMode !== WorkerVersioningMode.VERSIONED) {
212
212
  throw new Error('workflowGuards=strict requires worker versioning to be enabled (deployment.versioningMode=VERSIONED)');
@@ -679,10 +679,10 @@ export class WorkerRuntime {
679
679
  timeoutMs: POLL_TIMEOUT_MS,
680
680
  signal,
681
681
  });
682
- this.#observeHistogram(this.#metrics.workflowPollLatency, Date.now() - start);
683
682
  if (!response.taskToken || response.taskToken.length === 0) {
684
683
  return;
685
684
  }
685
+ this.#observeHistogram(this.#metrics.workflowPollLatency, Date.now() - start);
686
686
  await this.#enqueueWorkflowTask(response);
687
687
  }).pipe(Effect.catchAll((error) => this.#isRpcAbortError(error) ? Effect.void : this.#handleWorkflowPollerError(queueName, error)));
688
688
  return Effect.forever(pollOnce);
@@ -713,10 +713,10 @@ export class WorkerRuntime {
713
713
  timeoutMs: POLL_TIMEOUT_MS,
714
714
  signal,
715
715
  });
716
- this.#observeHistogram(this.#metrics.activityPollLatency, Date.now() - start);
717
716
  if (!response.taskToken || response.taskToken.length === 0) {
718
717
  return;
719
718
  }
719
+ this.#observeHistogram(this.#metrics.activityPollLatency, Date.now() - start);
720
720
  await this.#enqueueActivityTask(response);
721
721
  }).pipe(Effect.catchAll((error) => (this.#isRpcAbortError(error) ? Effect.void : this.#handleActivityPollerError(error))));
722
722
  return Effect.forever(pollOnce);
@@ -732,6 +732,23 @@ export class WorkerRuntime {
732
732
  #isRpcAbortError(error) {
733
733
  return isAbortError(error) || (error instanceof ConnectError && error.code === Code.Canceled);
734
734
  }
735
+ async #runWorkerResponseRpc(operation) {
736
+ const exit = await Effect.runPromiseExit(withTemporalRetry(Effect.tryPromise({
737
+ try: operation,
738
+ catch: (error) => error,
739
+ }), this.#config.rpcRetryPolicy));
740
+ if (Exit.isSuccess(exit)) {
741
+ return exit.value;
742
+ }
743
+ throw Cause.squash(exit.cause);
744
+ }
745
+ async #runActivityHeartbeat(effect) {
746
+ const exit = await Effect.runPromiseExit(effect);
747
+ if (Exit.isSuccess(exit)) {
748
+ return;
749
+ }
750
+ throw Cause.squash(exit.cause);
751
+ }
735
752
  #buildNormalTaskQueue(name) {
736
753
  return create(TaskQueueSchema, {
737
754
  name,
@@ -895,6 +912,14 @@ export class WorkerRuntime {
895
912
  historyEventCount: response.history?.events?.length ?? 0,
896
913
  });
897
914
  const stickyKey = this.#buildStickyKey(execution.workflowId, execution.runId);
915
+ if (!isLegacyQueryTask && hasOpenWorkflowCancellationRequest(historyEvents)) {
916
+ await this.#respondWorkflowCancellation(response, execution, baseLogFields);
917
+ if (stickyKey) {
918
+ await this.#removeStickyEntry(stickyKey);
919
+ await this.#removeStickyEntriesForWorkflow(stickyKey.workflowId);
920
+ }
921
+ return;
922
+ }
898
923
  const stickyEntry = stickyKey ? await this.#getStickyEntry(stickyKey) : undefined;
899
924
  const decodedArgs = await this.#decodeWorkflowArgs(historyEvents);
900
925
  let args = decodedArgs.args;
@@ -1233,7 +1258,7 @@ export class WorkerRuntime {
1233
1258
  ...(updateProtocolMessages.length > 0 ? { messages: updateProtocolMessages } : {}),
1234
1259
  });
1235
1260
  try {
1236
- await this.#workflowService.respondWorkflowTaskCompleted(completion, { timeoutMs: RESPOND_TIMEOUT_MS });
1261
+ await this.#runWorkerResponseRpc(() => this.#workflowService.respondWorkflowTaskCompleted(completion, { timeoutMs: RESPOND_TIMEOUT_MS }));
1237
1262
  workflowTaskCommitted = true;
1238
1263
  }
1239
1264
  catch (rpcError) {
@@ -1336,6 +1361,34 @@ export class WorkerRuntime {
1336
1361
  await this.#failWorkflowTask(response, execution, error);
1337
1362
  }
1338
1363
  }
1364
+ async #respondWorkflowCancellation(response, execution, logFields) {
1365
+ const command = create(CommandSchema, {
1366
+ commandType: CommandType.CANCEL_WORKFLOW_EXECUTION,
1367
+ attributes: {
1368
+ case: 'cancelWorkflowExecutionCommandAttributes',
1369
+ value: create(CancelWorkflowExecutionCommandAttributesSchema, {}),
1370
+ },
1371
+ });
1372
+ const completion = create(RespondWorkflowTaskCompletedRequestSchema, {
1373
+ taskToken: response.taskToken,
1374
+ commands: [command],
1375
+ identity: this.#identity,
1376
+ namespace: this.#namespace,
1377
+ deploymentOptions: this.#rpcDeploymentOptions,
1378
+ ...(this.#versioningBehavior !== null ? { versioningBehavior: this.#versioningBehavior } : {}),
1379
+ });
1380
+ try {
1381
+ await this.#runWorkerResponseRpc(() => this.#workflowService.respondWorkflowTaskCompleted(completion, { timeoutMs: RESPOND_TIMEOUT_MS }));
1382
+ this.#log('info', 'workflow cancellation request acknowledged', logFields);
1383
+ }
1384
+ catch (error) {
1385
+ if (this.#isTaskNotFoundError(error)) {
1386
+ this.#logWorkflowTaskNotFound('respondWorkflowCancellation', execution);
1387
+ return;
1388
+ }
1389
+ throw error;
1390
+ }
1391
+ }
1339
1392
  #buildStickyKey(workflowId, runId) {
1340
1393
  if (!workflowId || !runId) {
1341
1394
  return null;
@@ -1873,7 +1926,7 @@ export class WorkerRuntime {
1873
1926
  failure: queryResult.failure,
1874
1927
  cause: WorkflowTaskFailedCause.UNSPECIFIED,
1875
1928
  });
1876
- await this.#workflowService.respondQueryTaskCompleted(request, { timeoutMs: RESPOND_TIMEOUT_MS });
1929
+ await this.#runWorkerResponseRpc(() => this.#workflowService.respondQueryTaskCompleted(request, { timeoutMs: RESPOND_TIMEOUT_MS }));
1877
1930
  }
1878
1931
  async #respondLegacyQueryFailure(response, cause) {
1879
1932
  const failure = await encodeErrorToFailure(this.#dataConverter, cause);
@@ -1887,7 +1940,7 @@ export class WorkerRuntime {
1887
1940
  cause: WorkflowTaskFailedCause.UNSPECIFIED,
1888
1941
  });
1889
1942
  try {
1890
- await this.#workflowService.respondQueryTaskCompleted(request, { timeoutMs: RESPOND_TIMEOUT_MS });
1943
+ await this.#runWorkerResponseRpc(() => this.#workflowService.respondQueryTaskCompleted(request, { timeoutMs: RESPOND_TIMEOUT_MS }));
1891
1944
  }
1892
1945
  catch (rpcError) {
1893
1946
  this.#log('error', 'respondQueryTaskCompleted failed for legacy query', {
@@ -2091,7 +2144,27 @@ export class WorkerRuntime {
2091
2144
  return stableStringify(signature);
2092
2145
  }
2093
2146
  #isTaskNotFoundError(error) {
2094
- return error instanceof ConnectError && error.code === Code.NotFound;
2147
+ const underlying = this.#unwrapWorkerRpcError(error);
2148
+ return underlying instanceof ConnectError && underlying.code === Code.NotFound;
2149
+ }
2150
+ #unwrapWorkerRpcError(error) {
2151
+ if (error instanceof ConnectError) {
2152
+ return error;
2153
+ }
2154
+ if (!error || typeof error !== 'object') {
2155
+ return error;
2156
+ }
2157
+ const candidate = error;
2158
+ if (candidate._tag === 'UnknownException') {
2159
+ return this.#unwrapWorkerRpcError(candidate.cause ?? candidate.error ?? error);
2160
+ }
2161
+ if (candidate.cause) {
2162
+ return this.#unwrapWorkerRpcError(candidate.cause);
2163
+ }
2164
+ if (candidate.error) {
2165
+ return this.#unwrapWorkerRpcError(candidate.error);
2166
+ }
2167
+ return error;
2095
2168
  }
2096
2169
  #buildDeterminismMarkerCommand(details) {
2097
2170
  return create(CommandSchema, {
@@ -2339,7 +2412,7 @@ export class WorkerRuntime {
2339
2412
  deploymentOptions: this.#rpcDeploymentOptions,
2340
2413
  });
2341
2414
  try {
2342
- await this.#workflowService.respondWorkflowTaskFailed(failed, { timeoutMs: RESPOND_TIMEOUT_MS });
2415
+ await this.#runWorkerResponseRpc(() => this.#workflowService.respondWorkflowTaskFailed(failed, { timeoutMs: RESPOND_TIMEOUT_MS }));
2343
2416
  this.#incrementCounter(this.#metrics.workflowFailures);
2344
2417
  }
2345
2418
  catch (rpcError) {
@@ -2405,7 +2478,7 @@ export class WorkerRuntime {
2405
2478
  if (!registration) {
2406
2479
  return;
2407
2480
  }
2408
- await Effect.runPromise(registration.heartbeat(details));
2481
+ await this.#runActivityHeartbeat(registration.heartbeat(details));
2409
2482
  };
2410
2483
  }
2411
2484
  catch (registrationError) {
@@ -2428,18 +2501,9 @@ export class WorkerRuntime {
2428
2501
  };
2429
2502
  try {
2430
2503
  while (true) {
2504
+ let result;
2431
2505
  try {
2432
- const result = await runWithActivityContext(context, async () => await handler(...args));
2433
- const payloads = await encodeValuesToPayloads(this.#dataConverter, result === undefined ? [] : [result]);
2434
- const completion = create(RespondActivityTaskCompletedRequestSchema, {
2435
- taskToken: response.taskToken,
2436
- identity: this.#identity,
2437
- namespace: this.#namespace,
2438
- result: payloads && payloads.length > 0 ? create(PayloadsSchema, { payloads }) : undefined,
2439
- deploymentOptions: this.#rpcDeploymentOptions,
2440
- });
2441
- await this.#workflowService.respondActivityTaskCompleted(completion, { timeoutMs: RESPOND_TIMEOUT_MS });
2442
- break;
2506
+ result = await runWithActivityContext(context, async () => await handler(...args));
2443
2507
  }
2444
2508
  catch (error) {
2445
2509
  if (isAbortError(error)) {
@@ -2465,6 +2529,19 @@ export class WorkerRuntime {
2465
2529
  context.throwIfCancelled();
2466
2530
  context.info.attempt = nextRetry.attempt;
2467
2531
  retryState = nextRetry;
2532
+ continue;
2533
+ }
2534
+ try {
2535
+ const payloads = await encodeValuesToPayloads(this.#dataConverter, result === undefined ? [] : [result]);
2536
+ const completed = await this.#completeActivityTask(response, payloads);
2537
+ if (!completed) {
2538
+ return;
2539
+ }
2540
+ break;
2541
+ }
2542
+ catch (error) {
2543
+ await this.#failActivityTask(response, error, context);
2544
+ return;
2468
2545
  }
2469
2546
  }
2470
2547
  }
@@ -2494,7 +2571,16 @@ export class WorkerRuntime {
2494
2571
  lastHeartbeatDetails,
2495
2572
  deploymentOptions: this.#rpcDeploymentOptions,
2496
2573
  });
2497
- await this.#workflowService.respondActivityTaskFailed(request, { timeoutMs: RESPOND_TIMEOUT_MS });
2574
+ try {
2575
+ await this.#runWorkerResponseRpc(() => this.#workflowService.respondActivityTaskFailed(request, { timeoutMs: RESPOND_TIMEOUT_MS }));
2576
+ }
2577
+ catch (rpcError) {
2578
+ if (this.#isTaskNotFoundError(rpcError)) {
2579
+ this.#logActivityTaskNotFound('respondActivityTaskFailed', response);
2580
+ return;
2581
+ }
2582
+ throw rpcError;
2583
+ }
2498
2584
  this.#incrementCounter(this.#metrics.activityFailures);
2499
2585
  }
2500
2586
  async #cancelActivityTask(response, context) {
@@ -2506,7 +2592,36 @@ export class WorkerRuntime {
2506
2592
  details,
2507
2593
  deploymentOptions: this.#rpcDeploymentOptions,
2508
2594
  });
2509
- await this.#workflowService.respondActivityTaskCanceled(request, { timeoutMs: RESPOND_TIMEOUT_MS });
2595
+ try {
2596
+ await this.#runWorkerResponseRpc(() => this.#workflowService.respondActivityTaskCanceled(request, { timeoutMs: RESPOND_TIMEOUT_MS }));
2597
+ }
2598
+ catch (rpcError) {
2599
+ if (this.#isTaskNotFoundError(rpcError)) {
2600
+ this.#logActivityTaskNotFound('respondActivityTaskCanceled', response);
2601
+ return;
2602
+ }
2603
+ throw rpcError;
2604
+ }
2605
+ }
2606
+ async #completeActivityTask(response, payloads) {
2607
+ const completion = create(RespondActivityTaskCompletedRequestSchema, {
2608
+ taskToken: response.taskToken,
2609
+ identity: this.#identity,
2610
+ namespace: this.#namespace,
2611
+ result: payloads && payloads.length > 0 ? create(PayloadsSchema, { payloads }) : undefined,
2612
+ deploymentOptions: this.#rpcDeploymentOptions,
2613
+ });
2614
+ try {
2615
+ await this.#runWorkerResponseRpc(() => this.#workflowService.respondActivityTaskCompleted(completion, { timeoutMs: RESPOND_TIMEOUT_MS }));
2616
+ return true;
2617
+ }
2618
+ catch (rpcError) {
2619
+ if (this.#isTaskNotFoundError(rpcError)) {
2620
+ this.#logActivityTaskNotFound('respondActivityTaskCompleted', response);
2621
+ return false;
2622
+ }
2623
+ throw rpcError;
2624
+ }
2510
2625
  }
2511
2626
  async #encodeHeartbeatPayloads(details, reason) {
2512
2627
  const finalDetails = details && details.length > 0
@@ -2521,7 +2636,7 @@ export class WorkerRuntime {
2521
2636
  if (!finalDetails) {
2522
2637
  return undefined;
2523
2638
  }
2524
- const payloads = await encodeValuesToPayloads(this.#dataConverter, finalDetails);
2639
+ const payloads = (await encodeValuesToPayloads(this.#dataConverter, finalDetails)) ?? [];
2525
2640
  return payloads.length > 0 ? create(PayloadsSchema, { payloads }) : undefined;
2526
2641
  }
2527
2642
  #createActivityContext(response, cancelRequested, lastHeartbeatDetails) {
@@ -2693,6 +2808,15 @@ export class WorkerRuntime {
2693
2808
  runId: execution.runId,
2694
2809
  });
2695
2810
  }
2811
+ #logActivityTaskNotFound(context, response) {
2812
+ this.#log('warn', 'activity task already resolved', {
2813
+ context,
2814
+ workflowId: response.workflowExecution?.workflowId,
2815
+ runId: response.workflowExecution?.runId,
2816
+ activityId: response.activityId,
2817
+ activityType: response.activityType?.name,
2818
+ });
2819
+ }
2696
2820
  }
2697
2821
  const mergeSchedulerHooks = (first, second) => {
2698
2822
  if (!first && !second) {
@@ -2731,6 +2855,27 @@ const markErrorNonRetryable = (error) => {
2731
2855
  }
2732
2856
  };
2733
2857
  const isActivityCancelRequested = (response) => Boolean(response.cancelRequested);
2858
+ const hasOpenWorkflowCancellationRequest = (events) => {
2859
+ let cancelRequested = false;
2860
+ for (const event of events) {
2861
+ switch (event.eventType) {
2862
+ case EventType.WORKFLOW_EXECUTION_CANCEL_REQUESTED:
2863
+ cancelRequested = true;
2864
+ break;
2865
+ case EventType.WORKFLOW_EXECUTION_CANCELED:
2866
+ case EventType.WORKFLOW_EXECUTION_COMPLETED:
2867
+ case EventType.WORKFLOW_EXECUTION_CONTINUED_AS_NEW:
2868
+ case EventType.WORKFLOW_EXECUTION_FAILED:
2869
+ case EventType.WORKFLOW_EXECUTION_TERMINATED:
2870
+ case EventType.WORKFLOW_EXECUTION_TIMED_OUT:
2871
+ cancelRequested = false;
2872
+ break;
2873
+ default:
2874
+ break;
2875
+ }
2876
+ }
2877
+ return cancelRequested;
2878
+ };
2734
2879
  const isAbortError = (error) => error instanceof Error && error.name === 'AbortError';
2735
2880
  async function loadWorkflows(workflowsPath, overrides) {
2736
2881
  if (overrides && overrides.length > 0) {