@proompteng/temporal-bun-sdk 0.3.0 → 0.5.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 (170) hide show
  1. package/README.md +54 -11
  2. package/dist/src/bin/replay-command.d.ts.map +1 -1
  3. package/dist/src/bin/replay-command.js +6 -2
  4. package/dist/src/bin/replay-command.js.map +1 -1
  5. package/dist/src/bin/temporal-bun.d.ts +1 -1
  6. package/dist/src/bin/temporal-bun.d.ts.map +1 -1
  7. package/dist/src/bin/temporal-bun.js +75 -1
  8. package/dist/src/bin/temporal-bun.js.map +1 -1
  9. package/dist/src/client/layer.d.ts +2 -2
  10. package/dist/src/client/layer.d.ts.map +1 -1
  11. package/dist/src/client/retries.d.ts.map +1 -1
  12. package/dist/src/client/retries.js +27 -3
  13. package/dist/src/client/retries.js.map +1 -1
  14. package/dist/src/client/serialization.d.ts +4 -1
  15. package/dist/src/client/serialization.d.ts.map +1 -1
  16. package/dist/src/client/serialization.js +2 -2
  17. package/dist/src/client/serialization.js.map +1 -1
  18. package/dist/src/client.d.ts +12 -4
  19. package/dist/src/client.d.ts.map +1 -1
  20. package/dist/src/client.js +270 -63
  21. package/dist/src/client.js.map +1 -1
  22. package/dist/src/common/payloads/codecs.d.ts +38 -0
  23. package/dist/src/common/payloads/codecs.d.ts.map +1 -0
  24. package/dist/src/common/payloads/codecs.js +174 -0
  25. package/dist/src/common/payloads/codecs.js.map +1 -0
  26. package/dist/src/common/payloads/converter.d.ts +52 -3
  27. package/dist/src/common/payloads/converter.d.ts.map +1 -1
  28. package/dist/src/common/payloads/converter.js +340 -2
  29. package/dist/src/common/payloads/converter.js.map +1 -1
  30. package/dist/src/common/payloads/failure.d.ts +5 -6
  31. package/dist/src/common/payloads/failure.d.ts.map +1 -1
  32. package/dist/src/common/payloads/failure.js +3 -52
  33. package/dist/src/common/payloads/failure.js.map +1 -1
  34. package/dist/src/common/payloads/index.d.ts +1 -0
  35. package/dist/src/common/payloads/index.d.ts.map +1 -1
  36. package/dist/src/common/payloads/index.js +1 -0
  37. package/dist/src/common/payloads/index.js.map +1 -1
  38. package/dist/src/common/payloads/json-codec.d.ts.map +1 -1
  39. package/dist/src/common/payloads/json-codec.js +4 -6
  40. package/dist/src/common/payloads/json-codec.js.map +1 -1
  41. package/dist/src/config.d.ts +25 -0
  42. package/dist/src/config.d.ts.map +1 -1
  43. package/dist/src/config.js +111 -1
  44. package/dist/src/config.js.map +1 -1
  45. package/dist/src/interceptors/client.d.ts +28 -0
  46. package/dist/src/interceptors/client.d.ts.map +1 -0
  47. package/dist/src/interceptors/client.js +190 -0
  48. package/dist/src/interceptors/client.js.map +1 -0
  49. package/dist/src/interceptors/types.d.ts +33 -0
  50. package/dist/src/interceptors/types.d.ts.map +1 -0
  51. package/dist/src/interceptors/types.js +44 -0
  52. package/dist/src/interceptors/types.js.map +1 -0
  53. package/dist/src/interceptors/worker.d.ts +22 -0
  54. package/dist/src/interceptors/worker.d.ts.map +1 -0
  55. package/dist/src/interceptors/worker.js +177 -0
  56. package/dist/src/interceptors/worker.js.map +1 -0
  57. package/dist/src/observability/index.d.ts +4 -0
  58. package/dist/src/observability/index.d.ts.map +1 -1
  59. package/dist/src/observability/index.js +4 -0
  60. package/dist/src/observability/index.js.map +1 -1
  61. package/dist/src/observability/metrics.d.ts.map +1 -1
  62. package/dist/src/observability/metrics.js +70 -10
  63. package/dist/src/observability/metrics.js.map +1 -1
  64. package/dist/src/observability/opentelemetry.d.ts +20 -0
  65. package/dist/src/observability/opentelemetry.d.ts.map +1 -0
  66. package/dist/src/observability/opentelemetry.js +334 -0
  67. package/dist/src/observability/opentelemetry.js.map +1 -0
  68. package/dist/src/proto/temporal/api/activity/v1/message_pb.js +1 -1
  69. package/dist/src/proto/temporal/api/batch/v1/message_pb.js +1 -1
  70. package/dist/src/proto/temporal/api/command/v1/message_pb.js +1 -1
  71. package/dist/src/proto/temporal/api/common/v1/message_pb.js +1 -1
  72. package/dist/src/proto/temporal/api/deployment/v1/message_pb.js +1 -1
  73. package/dist/src/proto/temporal/api/enums/v1/batch_operation_pb.js +1 -1
  74. package/dist/src/proto/temporal/api/enums/v1/command_type_pb.js +1 -1
  75. package/dist/src/proto/temporal/api/enums/v1/common_pb.js +1 -1
  76. package/dist/src/proto/temporal/api/enums/v1/deployment_pb.js +1 -1
  77. package/dist/src/proto/temporal/api/enums/v1/event_type_pb.js +1 -1
  78. package/dist/src/proto/temporal/api/enums/v1/failed_cause_pb.js +1 -1
  79. package/dist/src/proto/temporal/api/enums/v1/namespace_pb.js +1 -1
  80. package/dist/src/proto/temporal/api/enums/v1/nexus_pb.js +1 -1
  81. package/dist/src/proto/temporal/api/enums/v1/query_pb.js +1 -1
  82. package/dist/src/proto/temporal/api/enums/v1/reset_pb.js +1 -1
  83. package/dist/src/proto/temporal/api/enums/v1/schedule_pb.js +1 -1
  84. package/dist/src/proto/temporal/api/enums/v1/task_queue_pb.js +1 -1
  85. package/dist/src/proto/temporal/api/enums/v1/update_pb.js +1 -1
  86. package/dist/src/proto/temporal/api/enums/v1/workflow_pb.js +1 -1
  87. package/dist/src/proto/temporal/api/errordetails/v1/message_pb.js +1 -1
  88. package/dist/src/proto/temporal/api/export/v1/message_pb.js +1 -1
  89. package/dist/src/proto/temporal/api/failure/v1/message_pb.js +1 -1
  90. package/dist/src/proto/temporal/api/filter/v1/message_pb.js +1 -1
  91. package/dist/src/proto/temporal/api/history/v1/message_pb.js +1 -1
  92. package/dist/src/proto/temporal/api/namespace/v1/message_pb.js +1 -1
  93. package/dist/src/proto/temporal/api/nexus/v1/message_pb.js +1 -1
  94. package/dist/src/proto/temporal/api/operatorservice/v1/request_response_pb.js +1 -1
  95. package/dist/src/proto/temporal/api/operatorservice/v1/service_pb.js +1 -1
  96. package/dist/src/proto/temporal/api/protocol/v1/message_pb.js +1 -1
  97. package/dist/src/proto/temporal/api/query/v1/message_pb.js +1 -1
  98. package/dist/src/proto/temporal/api/replication/v1/message_pb.js +1 -1
  99. package/dist/src/proto/temporal/api/rules/v1/message_pb.js +1 -1
  100. package/dist/src/proto/temporal/api/sdk/v1/enhanced_stack_trace_pb.js +1 -1
  101. package/dist/src/proto/temporal/api/sdk/v1/task_complete_metadata_pb.js +1 -1
  102. package/dist/src/proto/temporal/api/sdk/v1/user_metadata_pb.js +1 -1
  103. package/dist/src/proto/temporal/api/sdk/v1/worker_config_pb.js +1 -1
  104. package/dist/src/proto/temporal/api/sdk/v1/workflow_metadata_pb.js +1 -1
  105. package/dist/src/proto/temporal/api/taskqueue/v1/message_pb.js +1 -1
  106. package/dist/src/proto/temporal/api/update/v1/message_pb.js +1 -1
  107. package/dist/src/proto/temporal/api/version/v1/message_pb.js +1 -1
  108. package/dist/src/proto/temporal/api/worker/v1/message_pb.js +1 -1
  109. package/dist/src/proto/temporal/api/workflow/v1/message_pb.js +1 -1
  110. package/dist/src/proto/temporal/api/workflowservice/v1/request_response_pb.js +1 -1
  111. package/dist/src/proto/temporal/api/workflowservice/v1/service_pb.js +1 -1
  112. package/dist/src/runtime/cli-layer.d.ts +4 -3
  113. package/dist/src/runtime/cli-layer.d.ts.map +1 -1
  114. package/dist/src/runtime/cli-layer.js +5 -2
  115. package/dist/src/runtime/cli-layer.js.map +1 -1
  116. package/dist/src/runtime/effect-layers.d.ts +9 -0
  117. package/dist/src/runtime/effect-layers.d.ts.map +1 -1
  118. package/dist/src/runtime/effect-layers.js +15 -0
  119. package/dist/src/runtime/effect-layers.js.map +1 -1
  120. package/dist/src/worker/runtime.d.ts +6 -0
  121. package/dist/src/worker/runtime.d.ts.map +1 -1
  122. package/dist/src/worker/runtime.js +853 -70
  123. package/dist/src/worker/runtime.js.map +1 -1
  124. package/dist/src/worker/sticky-cache.d.ts +15 -0
  125. package/dist/src/worker/sticky-cache.d.ts.map +1 -1
  126. package/dist/src/worker/sticky-cache.js +26 -0
  127. package/dist/src/worker/sticky-cache.js.map +1 -1
  128. package/dist/src/worker/update-protocol.d.ts.map +1 -1
  129. package/dist/src/worker/update-protocol.js +15 -0
  130. package/dist/src/worker/update-protocol.js.map +1 -1
  131. package/dist/src/worker.js +1 -0
  132. package/dist/src/worker.js.map +1 -1
  133. package/dist/src/workflow/commands.d.ts +40 -3
  134. package/dist/src/workflow/commands.d.ts.map +1 -1
  135. package/dist/src/workflow/commands.js +161 -7
  136. package/dist/src/workflow/commands.js.map +1 -1
  137. package/dist/src/workflow/context.d.ts +59 -2
  138. package/dist/src/workflow/context.d.ts.map +1 -1
  139. package/dist/src/workflow/context.js +345 -16
  140. package/dist/src/workflow/context.js.map +1 -1
  141. package/dist/src/workflow/definition.js.map +1 -1
  142. package/dist/src/workflow/determinism.d.ts +9 -0
  143. package/dist/src/workflow/determinism.d.ts.map +1 -1
  144. package/dist/src/workflow/determinism.js +145 -16
  145. package/dist/src/workflow/determinism.js.map +1 -1
  146. package/dist/src/workflow/errors.d.ts +3 -0
  147. package/dist/src/workflow/errors.d.ts.map +1 -1
  148. package/dist/src/workflow/errors.js +6 -0
  149. package/dist/src/workflow/errors.js.map +1 -1
  150. package/dist/src/workflow/executor.d.ts +6 -0
  151. package/dist/src/workflow/executor.d.ts.map +1 -1
  152. package/dist/src/workflow/executor.js +147 -18
  153. package/dist/src/workflow/executor.js.map +1 -1
  154. package/dist/src/workflow/inbound.d.ts +1 -0
  155. package/dist/src/workflow/inbound.d.ts.map +1 -1
  156. package/dist/src/workflow/inbound.js +1 -0
  157. package/dist/src/workflow/inbound.js.map +1 -1
  158. package/dist/src/workflow/index.d.ts +5 -0
  159. package/dist/src/workflow/index.d.ts.map +1 -1
  160. package/dist/src/workflow/index.js +3 -0
  161. package/dist/src/workflow/index.js.map +1 -1
  162. package/dist/src/workflow/log.d.ts +18 -0
  163. package/dist/src/workflow/log.d.ts.map +1 -0
  164. package/dist/src/workflow/log.js +36 -0
  165. package/dist/src/workflow/log.js.map +1 -0
  166. package/dist/src/workflow/replay.d.ts +31 -3
  167. package/dist/src/workflow/replay.d.ts.map +1 -1
  168. package/dist/src/workflow/replay.js +763 -67
  169. package/dist/src/workflow/replay.js.map +1 -1
  170. package/package.json +4 -3
@@ -5,17 +5,19 @@ import { Context, Effect } from 'effect';
5
5
  import * as Option from 'effect/Option';
6
6
  import { createDefaultHeaders, mergeHeaders, normalizeMetadataHeaders } from './client/headers';
7
7
  import { makeDefaultInterceptorBuilder } from './client/interceptors';
8
- import { withTemporalRetry } from './client/retries';
9
8
  import { buildCancelRequest, buildPollWorkflowUpdateRequest, buildQueryRequest, buildSignalRequest, buildSignalWithStartRequest, buildStartWorkflowRequest, buildTerminateRequest, buildUpdateWorkflowRequest, computeSignalRequestId, createSignalRequestEntropy, createUpdateRequestId, decodeMemoAttributes, decodeSearchAttributes, decodeUpdateOutcome, encodeMemoAttributes, encodeSearchAttributes, } from './client/serialization';
10
9
  import { buildTransportOptions, normalizeTemporalAddress } from './client/transport';
11
10
  import { createWorkflowHandle, } from './client/types';
12
- import { createDefaultDataConverter, decodePayloadsToValues } from './common/payloads';
11
+ import { buildCodecsFromConfig, createDefaultDataConverter, decodePayloadsToValues, } from './common/payloads';
13
12
  import { loadTemporalConfig } from './config';
13
+ import { makeDefaultClientInterceptors, runClientInterceptors, } from './interceptors/client';
14
14
  import { createObservabilityServices } from './observability';
15
+ import { WorkflowExecutionSchema, } from './proto/temporal/api/common/v1/message_pb';
15
16
  import { UpdateWorkflowExecutionLifecycleStage } from './proto/temporal/api/enums/v1/update_pb';
16
- import { DescribeNamespaceRequestSchema, DescribeNamespaceResponseSchema, } from './proto/temporal/api/workflowservice/v1/request_response_pb';
17
+ import { HistoryEventFilterType } from './proto/temporal/api/enums/v1/workflow_pb';
18
+ import { DescribeNamespaceRequestSchema, DescribeNamespaceResponseSchema, GetWorkflowExecutionHistoryRequestSchema, } from './proto/temporal/api/workflowservice/v1/request_response_pb';
17
19
  import { WorkflowService } from './proto/temporal/api/workflowservice/v1/service_pb';
18
- import { LoggerService, MetricsExporterService, MetricsService, TemporalConfigService, WorkflowServiceClientService, } from './runtime/effect-layers';
20
+ import { DataConverterService, LoggerService, MetricsExporterService, MetricsService, TemporalConfigService, WorkflowServiceClientService, } from './runtime/effect-layers';
19
21
  export const temporalCallOptions = (options) => {
20
22
  const copy = { ...options };
21
23
  Object.defineProperty(copy, CALL_OPTIONS_MARKER, {
@@ -54,6 +56,38 @@ const describeError = (error) => {
54
56
  };
55
57
  const TLS_ERROR_CODE_PREFIXES = ['ERR_TLS_', 'ERR_SSL_'];
56
58
  const TLS_ERROR_MESSAGE_HINTS = [/handshake/i, /certificate/i, /secure tls/i, /ssl/i];
59
+ const isAbortLikeError = (error) => (error instanceof Error && error.name === 'AbortError') ||
60
+ (error instanceof ConnectError && error.code === Code.Canceled);
61
+ const normalizeUnknownError = (error) => {
62
+ const unwrap = (value) => {
63
+ if (!value || typeof value !== 'object') {
64
+ return value;
65
+ }
66
+ if (value instanceof TemporalTlsHandshakeError || value instanceof ConnectError) {
67
+ return value;
68
+ }
69
+ const candidate = value;
70
+ if (candidate.cause !== undefined) {
71
+ return unwrap(candidate.cause);
72
+ }
73
+ if (candidate.error !== undefined) {
74
+ return unwrap(candidate.error);
75
+ }
76
+ if (candidate._tag === 'UnknownException') {
77
+ return unwrap(candidate.cause ?? candidate.error ?? value);
78
+ }
79
+ const symbols = Object.getOwnPropertySymbols(value);
80
+ for (const symbol of symbols) {
81
+ const inner = value[symbol];
82
+ const unwrapped = unwrap(inner);
83
+ if (unwrapped !== inner) {
84
+ return unwrapped;
85
+ }
86
+ }
87
+ return value;
88
+ };
89
+ return unwrap(error);
90
+ };
57
91
  const isCallOptionsCandidate = (value) => {
58
92
  if (!value || typeof value !== 'object') {
59
93
  return false;
@@ -132,6 +166,12 @@ export const createTemporalClient = async (options = {}) => {
132
166
  if (!workflowService) {
133
167
  throw new Error('Temporal workflow service is not available');
134
168
  }
169
+ const dataConverter = options.dataConverter ??
170
+ createDefaultDataConverter({
171
+ payloadCodecs: buildCodecsFromConfig(config.payloadCodecs),
172
+ logger,
173
+ metricsRegistry,
174
+ });
135
175
  const effect = makeTemporalClientEffect({
136
176
  ...options,
137
177
  config,
@@ -140,9 +180,10 @@ export const createTemporalClient = async (options = {}) => {
140
180
  metricsExporter,
141
181
  workflowService,
142
182
  transport,
183
+ dataConverter,
143
184
  });
144
185
  try {
145
- return await Effect.runPromise(effect.pipe(Effect.provideService(TemporalConfigService, config), Effect.provideService(LoggerService, logger), Effect.provideService(MetricsService, metricsRegistry), Effect.provideService(MetricsExporterService, metricsExporter), Effect.provideService(WorkflowServiceClientService, workflowService)));
186
+ return await Effect.runPromise(effect.pipe(Effect.provideService(TemporalConfigService, config), Effect.provideService(LoggerService, logger), Effect.provideService(MetricsService, metricsRegistry), Effect.provideService(MetricsExporterService, metricsExporter), Effect.provideService(WorkflowServiceClientService, workflowService), Effect.provideService(DataConverterService, dataConverter)));
146
187
  }
147
188
  catch (error) {
148
189
  await createdTransport?.close?.();
@@ -154,11 +195,33 @@ export const makeTemporalClientEffect = (options = {}) => Effect.gen(function* (
154
195
  const namespace = options.namespace ?? config.namespace;
155
196
  const identity = options.identity ?? config.workerIdentity;
156
197
  const taskQueue = options.taskQueue ?? config.taskQueue;
157
- const dataConverter = options.dataConverter ?? createDefaultDataConverter();
158
- const initialHeaders = createDefaultHeaders(config.apiKey);
159
198
  const logger = options.logger ?? (yield* LoggerService);
160
199
  const metricsRegistry = options.metrics ?? (yield* MetricsService);
161
200
  const metricsExporter = options.metricsExporter ?? (yield* MetricsExporterService);
201
+ const contextualDataConverter = yield* Effect.contextWith((context) => Context.getOption(context, DataConverterService));
202
+ const dataConverter = options.dataConverter ??
203
+ Option.getOrUndefined(contextualDataConverter) ??
204
+ createDefaultDataConverter({
205
+ payloadCodecs: buildCodecsFromConfig(config.payloadCodecs),
206
+ logger,
207
+ metricsRegistry,
208
+ });
209
+ const initialHeaders = createDefaultHeaders(config.apiKey);
210
+ const tracingEnabled = options.tracingEnabled ?? config.tracingInterceptorsEnabled ?? false;
211
+ const clientInterceptorBuilder = options.clientInterceptorBuilder ?? {
212
+ build: (input) => makeDefaultClientInterceptors(input),
213
+ };
214
+ const defaultClientInterceptors = yield* clientInterceptorBuilder.build({
215
+ namespace,
216
+ taskQueue,
217
+ identity,
218
+ logger,
219
+ metricsRegistry,
220
+ metricsExporter,
221
+ retryPolicy: config.rpcRetryPolicy,
222
+ tracingEnabled,
223
+ });
224
+ const clientInterceptors = [...defaultClientInterceptors, ...(options.clientInterceptors ?? [])];
162
225
  const workflowServiceFromContext = yield* Effect.contextWith((context) => Context.getOption(context, WorkflowServiceClientService));
163
226
  let workflowService = options.workflowService ?? Option.getOrUndefined(workflowServiceFromContext);
164
227
  let transport = options.transport;
@@ -199,6 +262,7 @@ export const makeTemporalClientEffect = (options = {}) => Effect.gen(function* (
199
262
  logger,
200
263
  metrics: clientMetrics,
201
264
  metricsExporter,
265
+ clientInterceptors,
202
266
  });
203
267
  return { client, config };
204
268
  });
@@ -216,7 +280,9 @@ class TemporalClientImpl {
216
280
  #logger;
217
281
  #clientMetrics;
218
282
  #metricsExporter;
283
+ #clientInterceptors;
219
284
  #pendingUpdateControllers = new Map();
285
+ #abortedUpdates = new Set();
220
286
  closed = false;
221
287
  headers;
222
288
  static async initMetrics(registry) {
@@ -240,6 +306,7 @@ class TemporalClientImpl {
240
306
  this.#logger = handles.logger;
241
307
  this.#clientMetrics = handles.metrics;
242
308
  this.#metricsExporter = handles.metricsExporter;
309
+ this.#clientInterceptors = handles.clientInterceptors;
243
310
  this.memo = {
244
311
  encode: (input) => encodeMemoAttributes(this.dataConverter, input),
245
312
  decode: (memo) => decodeMemoAttributes(this.dataConverter, memo),
@@ -259,12 +326,13 @@ class TemporalClientImpl {
259
326
  awaitUpdate: (updateHandle, options, callOptions) => this.awaitWorkflowUpdate(updateHandle, options, callOptions),
260
327
  cancelUpdate: (updateHandle) => this.cancelWorkflowUpdate(updateHandle),
261
328
  getUpdateHandle: (workflowHandle, updateId, firstExecutionRunId) => this.getWorkflowUpdateHandle(workflowHandle, updateId, firstExecutionRunId),
329
+ result: (handle, callOptions) => this.getWorkflowResult(handle, callOptions),
262
330
  };
263
331
  }
264
332
  async startWorkflow(options, callOptions) {
265
- return this.#instrumentOperation('startWorkflow', async () => {
333
+ const parsedOptions = sanitizeStartWorkflowOptions(options);
334
+ return this.#instrumentOperation('workflow.start', async () => {
266
335
  this.ensureOpen();
267
- const parsedOptions = sanitizeStartWorkflowOptions(options);
268
336
  const request = await buildStartWorkflowRequest({
269
337
  options: parsedOptions,
270
338
  defaults: {
@@ -279,12 +347,15 @@ class TemporalClientImpl {
279
347
  namespace: request.namespace,
280
348
  firstExecutionRunId: response.started ? response.runId : undefined,
281
349
  });
350
+ }, {
351
+ workflowId: parsedOptions.workflowId,
352
+ taskQueue: options.taskQueue ?? this.defaultTaskQueue,
282
353
  });
283
354
  }
284
355
  async signalWorkflow(handle, signalName, ...rawArgs) {
285
- return this.#instrumentOperation('signalWorkflow', async () => {
356
+ const resolvedHandle = resolveHandle(this.namespace, handle);
357
+ return this.#instrumentOperation('workflow.signal', async () => {
286
358
  this.ensureOpen();
287
- const resolvedHandle = resolveHandle(this.namespace, handle);
288
359
  const normalizedSignalName = ensureNonEmptyString(signalName, 'signalName');
289
360
  const { values, callOptions } = this.#splitArgsAndOptions(rawArgs);
290
361
  const identity = this.defaultIdentity;
@@ -306,39 +377,101 @@ class TemporalClientImpl {
306
377
  requestId,
307
378
  }, this.dataConverter);
308
379
  await this.executeRpc('signalWorkflow', (rpcOptions) => this.workflowService.signalWorkflowExecution(request, rpcOptions), callOptions);
309
- });
380
+ }, { workflowId: resolvedHandle.workflowId, runId: resolvedHandle.runId, taskQueue: this.defaultTaskQueue });
310
381
  }
311
382
  async queryWorkflow(handle, queryName, ...rawArgs) {
312
- return this.#instrumentOperation('queryWorkflow', async () => {
383
+ const resolvedHandle = resolveHandle(this.namespace, handle);
384
+ return this.#instrumentOperation('workflow.query', async () => {
313
385
  this.ensureOpen();
314
- const resolvedHandle = resolveHandle(this.namespace, handle);
315
386
  const { values, callOptions } = this.#splitArgsAndOptions(rawArgs);
316
- const request = await buildQueryRequest(resolvedHandle, queryName, values, this.dataConverter);
387
+ const request = await buildQueryRequest(resolvedHandle, queryName, values, this.dataConverter, {
388
+ rejectCondition: callOptions?.queryRejectCondition,
389
+ });
317
390
  const response = await this.executeRpc('queryWorkflow', (rpcOptions) => this.workflowService.queryWorkflow(request, rpcOptions), callOptions);
318
391
  return this.parseQueryResult(response);
319
- });
392
+ }, { workflowId: resolvedHandle.workflowId, runId: resolvedHandle.runId });
320
393
  }
321
394
  async terminateWorkflow(handle, options = {}, callOptions) {
322
- return this.#instrumentOperation('terminateWorkflow', async () => {
395
+ return this.#instrumentOperation('workflow.terminate', async () => {
323
396
  this.ensureOpen();
324
397
  const resolvedHandle = resolveHandle(this.namespace, handle);
325
398
  const parsedOptions = sanitizeTerminateWorkflowOptions(options);
326
399
  const request = await buildTerminateRequest(resolvedHandle, parsedOptions, this.dataConverter, this.defaultIdentity);
327
400
  await this.executeRpc('terminateWorkflow', (rpcOptions) => this.workflowService.terminateWorkflowExecution(request, rpcOptions), callOptions);
328
- });
401
+ }, { workflowId: handle.workflowId, runId: handle.runId });
329
402
  }
330
403
  async cancelWorkflow(handle, callOptions) {
331
- return this.#instrumentOperation('cancelWorkflow', async () => {
404
+ const resolvedHandle = resolveHandle(this.namespace, handle);
405
+ return this.#instrumentOperation('workflow.cancel', async () => {
332
406
  this.ensureOpen();
333
- const resolvedHandle = resolveHandle(this.namespace, handle);
334
407
  const request = buildCancelRequest(resolvedHandle, this.defaultIdentity);
335
408
  await this.executeRpc('cancelWorkflow', (rpcOptions) => this.workflowService.requestCancelWorkflowExecution(request, rpcOptions), callOptions);
336
- });
409
+ }, { workflowId: resolvedHandle.workflowId, runId: resolvedHandle.runId });
410
+ }
411
+ async getWorkflowResult(handle, callOptions) {
412
+ let resolvedHandle = resolveHandle(this.namespace, handle);
413
+ return this.#instrumentOperation('workflow.result', async () => {
414
+ this.ensureOpen();
415
+ while (true) {
416
+ const execution = create(WorkflowExecutionSchema, {
417
+ workflowId: resolvedHandle.workflowId,
418
+ ...(resolvedHandle.runId ? { runId: resolvedHandle.runId } : {}),
419
+ });
420
+ const request = create(GetWorkflowExecutionHistoryRequestSchema, {
421
+ namespace: resolvedHandle.namespace ?? this.namespace,
422
+ execution,
423
+ maximumPageSize: 1,
424
+ historyEventFilterType: HistoryEventFilterType.CLOSE_EVENT,
425
+ waitNewEvent: true,
426
+ skipArchival: true,
427
+ });
428
+ const response = await this.executeRpc('getWorkflowExecutionHistory', (rpcOptions) => this.workflowService.getWorkflowExecutionHistory(request, rpcOptions), callOptions);
429
+ const closeEvent = this.#extractCloseEvent(response);
430
+ if (!closeEvent || !closeEvent.attributes) {
431
+ throw new Error('Workflow close event not found');
432
+ }
433
+ const attributes = closeEvent.attributes;
434
+ switch (attributes.case) {
435
+ case 'workflowExecutionContinuedAsNewEventAttributes': {
436
+ const nextRunId = attributes.value.newExecutionRunId;
437
+ if (!nextRunId) {
438
+ throw new Error('Continue-as-new event missing newExecutionRunId');
439
+ }
440
+ resolvedHandle = { ...resolvedHandle, runId: nextRunId };
441
+ continue;
442
+ }
443
+ case 'workflowExecutionCompletedEventAttributes': {
444
+ const payloads = attributes.value.result?.payloads ?? [];
445
+ const decoded = await this.dataConverter.fromPayloads(payloads);
446
+ return (decoded.length <= 1 ? decoded[0] : decoded) ?? undefined;
447
+ }
448
+ case 'workflowExecutionFailedEventAttributes': {
449
+ const failure = await this.dataConverter.decodeFailurePayloads(attributes.value.failure);
450
+ const error = await this.dataConverter.failureToError(failure);
451
+ throw error ?? new Error('Workflow failed');
452
+ }
453
+ case 'workflowExecutionTimedOutEventAttributes':
454
+ throw new Error('Workflow timed out');
455
+ case 'workflowExecutionCanceledEventAttributes': {
456
+ const details = attributes.value.details?.payloads ?? [];
457
+ const decoded = await this.dataConverter.fromPayloads(details);
458
+ const detail = decoded.length <= 1 ? decoded[0] : decoded;
459
+ throw new Error(detail ? `Workflow canceled: ${JSON.stringify(detail)}` : 'Workflow canceled without details');
460
+ }
461
+ case 'workflowExecutionTerminatedEventAttributes': {
462
+ const reason = attributes.value.reason;
463
+ throw new Error(reason ? `Workflow terminated: ${reason}` : 'Workflow terminated');
464
+ }
465
+ default:
466
+ throw new Error(`Unsupported workflow close event type: ${attributes.case}`);
467
+ }
468
+ }
469
+ }, { workflowId: resolvedHandle.workflowId, runId: resolvedHandle.runId });
337
470
  }
338
471
  async signalWithStart(options, callOptions) {
339
- return this.#instrumentOperation('signalWithStart', async () => {
472
+ const startOptions = sanitizeStartWorkflowOptions(options);
473
+ return this.#instrumentOperation('workflow.signalWithStart', async () => {
340
474
  this.ensureOpen();
341
- const startOptions = sanitizeStartWorkflowOptions(options);
342
475
  const signalName = ensureNonEmptyString(options.signalName, 'signalName');
343
476
  const signalArgs = options.signalArgs ?? [];
344
477
  if (!Array.isArray(signalArgs)) {
@@ -362,17 +495,17 @@ class TemporalClientImpl {
362
495
  namespace: request.namespace,
363
496
  firstExecutionRunId: response.started ? response.runId : undefined,
364
497
  });
365
- });
498
+ }, { workflowId: startOptions.workflowId });
366
499
  }
367
500
  async updateWorkflow(handle, options, callOptions) {
368
- return this.#instrumentOperation('updateWorkflow', async () => {
369
- this.ensureOpen();
370
- const resolvedHandle = resolveHandle(this.namespace, handle);
371
- const parsedOptions = sanitizeWorkflowUpdateOptions(options);
372
- const updateId = parsedOptions.updateId ?? createUpdateRequestId();
373
- const waitStage = this.#stageToProto(parsedOptions.waitForStage);
501
+ this.ensureOpen();
502
+ const resolvedHandle = resolveHandle(this.namespace, handle);
503
+ const parsedOptions = sanitizeWorkflowUpdateOptions(options);
504
+ const updateId = parsedOptions.updateId ?? createUpdateRequestId();
505
+ const waitStage = this.#stageToProto(parsedOptions.waitForStage);
506
+ return this.#instrumentOperation('workflow.update', async () => {
374
507
  const updateKey = this.#makeUpdateKey(resolvedHandle, updateId);
375
- const { options: mergedCallOptions, controller, cleanup } = this.#prepareUpdateCallOptions(updateKey, callOptions);
508
+ const { options: mergedCallOptions, controller, cleanup, } = this.#prepareUpdateCallOptions(updateKey, callOptions);
376
509
  try {
377
510
  const request = await buildUpdateWorkflowRequest({
378
511
  handle: resolvedHandle,
@@ -407,21 +540,21 @@ class TemporalClientImpl {
407
540
  this.#releaseUpdateController(updateKey, controller);
408
541
  cleanup?.();
409
542
  }
410
- });
543
+ }, { workflowId: resolvedHandle.workflowId, runId: resolvedHandle.runId, updateId });
411
544
  }
412
545
  async awaitWorkflowUpdate(handle, options = {}, callOptions) {
413
- return this.#instrumentOperation('awaitWorkflowUpdate', async () => {
414
- this.ensureOpen();
415
- const resolvedHandle = resolveHandle(this.namespace, handle);
416
- const updateId = ensureNonEmptyString(handle.updateId, 'updateId');
417
- const waitStage = this.#stageToProto(options.waitForStage ?? 'completed');
418
- const firstExecutionRunIdOverride = ensureOptionalTrimmedString(options.firstExecutionRunId, 'firstExecutionRunId', 1);
419
- const pollHandle = {
420
- workflowId: resolvedHandle.workflowId,
421
- namespace: resolvedHandle.namespace,
422
- runId: resolvedHandle.runId,
423
- firstExecutionRunId: firstExecutionRunIdOverride ?? resolvedHandle.firstExecutionRunId,
424
- };
546
+ this.ensureOpen();
547
+ const resolvedHandle = resolveHandle(this.namespace, handle);
548
+ const updateId = ensureNonEmptyString(handle.updateId, 'updateId');
549
+ const waitStage = this.#stageToProto(options.waitForStage ?? 'completed');
550
+ const firstExecutionRunIdOverride = ensureOptionalTrimmedString(options.firstExecutionRunId, 'firstExecutionRunId', 1);
551
+ const pollHandle = {
552
+ workflowId: resolvedHandle.workflowId,
553
+ namespace: resolvedHandle.namespace,
554
+ runId: resolvedHandle.runId,
555
+ firstExecutionRunId: firstExecutionRunIdOverride ?? resolvedHandle.firstExecutionRunId,
556
+ };
557
+ return this.#instrumentOperation('workflow.awaitUpdate', async () => {
425
558
  const request = buildPollWorkflowUpdateRequest({
426
559
  handle: pollHandle,
427
560
  namespace: this.namespace,
@@ -430,7 +563,16 @@ class TemporalClientImpl {
430
563
  waitStage,
431
564
  });
432
565
  const updateKey = this.#makeUpdateKey(resolvedHandle, updateId);
433
- const { options: mergedCallOptions, controller, cleanup } = this.#prepareUpdateCallOptions(updateKey, callOptions);
566
+ const { options: mergedCallOptions, controller, cleanup, } = this.#prepareUpdateCallOptions(updateKey, callOptions);
567
+ const throwIfAborted = () => {
568
+ if (controller.signal.aborted || this.#abortedUpdates.has(updateKey)) {
569
+ this.#abortedUpdates.delete(updateKey);
570
+ const abortError = new Error('Workflow update polling aborted');
571
+ abortError.name = 'AbortError';
572
+ throw abortError;
573
+ }
574
+ };
575
+ throwIfAborted();
434
576
  try {
435
577
  const response = await this.executeRpc('pollWorkflowExecutionUpdate', (rpcOptions) => this.workflowService.pollWorkflowExecutionUpdate(request, rpcOptions), mergedCallOptions);
436
578
  const stage = this.#stageFromProto(response.stage);
@@ -447,11 +589,16 @@ class TemporalClientImpl {
447
589
  outcome,
448
590
  };
449
591
  }
592
+ catch (error) {
593
+ throwIfAborted();
594
+ throw error;
595
+ }
450
596
  finally {
597
+ this.#abortedUpdates.delete(updateKey);
451
598
  this.#releaseUpdateController(updateKey, controller);
452
599
  cleanup?.();
453
600
  }
454
- });
601
+ }, { workflowId: resolvedHandle.workflowId, runId: resolvedHandle.runId, updateId });
455
602
  }
456
603
  getWorkflowUpdateHandle(handle, updateId, firstExecutionRunId) {
457
604
  const resolvedHandle = resolveHandle(this.namespace, handle);
@@ -463,7 +610,7 @@ class TemporalClientImpl {
463
610
  });
464
611
  }
465
612
  async cancelWorkflowUpdate(handle) {
466
- return this.#instrumentOperation('cancelWorkflowUpdate', async () => {
613
+ return this.#instrumentOperation('workflow.update', async () => {
467
614
  this.ensureOpen();
468
615
  const resolvedHandle = resolveHandle(this.namespace, handle);
469
616
  const updateId = ensureNonEmptyString(handle.updateId, 'updateId');
@@ -475,17 +622,17 @@ class TemporalClientImpl {
475
622
  updateId,
476
623
  });
477
624
  }
478
- });
625
+ }, { workflowId: handle.workflowId, runId: handle.runId, updateId: handle.updateId });
479
626
  }
480
627
  async describeNamespace(targetNamespace, callOptions) {
481
- return this.#instrumentOperation('describeNamespace', async () => {
628
+ return this.#instrumentOperation('workflow.describe', async () => {
482
629
  this.ensureOpen();
483
630
  const request = create(DescribeNamespaceRequestSchema, {
484
631
  namespace: targetNamespace ?? this.namespace,
485
632
  });
486
633
  const response = await this.executeRpc('describeNamespace', (rpcOptions) => this.workflowService.describeNamespace(request, rpcOptions), callOptions);
487
634
  return toBinary(DescribeNamespaceResponseSchema, response);
488
- });
635
+ }, { namespace: targetNamespace ?? this.namespace });
489
636
  }
490
637
  async updateHeaders(headers) {
491
638
  if (this.closed) {
@@ -523,24 +670,66 @@ class TemporalClientImpl {
523
670
  void Effect.runPromise(this.#clientMetrics.operationErrors.inc());
524
671
  }
525
672
  }
526
- async #instrumentOperation(operation, action) {
673
+ async #instrumentOperation(operation, action, metadata = {}) {
674
+ const context = {
675
+ kind: operation,
676
+ namespace: this.namespace,
677
+ taskQueue: metadata.taskQueue ?? this.defaultTaskQueue,
678
+ identity: this.defaultIdentity,
679
+ workflowId: metadata.workflowId,
680
+ runId: metadata.runId,
681
+ updateId: metadata.updateId,
682
+ metadata,
683
+ };
527
684
  const start = Date.now();
685
+ const effect = runClientInterceptors(this.#clientInterceptors, context, () => Effect.tryPromise(action));
528
686
  try {
529
- const result = await action();
687
+ const result = await Effect.runPromise(effect);
530
688
  this.#log('debug', `temporal client ${operation} succeeded`, {
531
689
  operation,
532
690
  namespace: this.namespace,
691
+ ...metadata,
533
692
  });
534
693
  this.#recordMetrics(Date.now() - start, false);
535
694
  return result;
536
695
  }
537
696
  catch (error) {
697
+ const normalized = normalizeUnknownError(error);
698
+ const finalError = normalized instanceof Error &&
699
+ normalized.message.toLowerCase().includes('unknown error occurred in effect.trypromise')
700
+ ? (normalized.cause ??
701
+ normalized.error ??
702
+ normalized)
703
+ : normalized;
704
+ const message = finalError instanceof Error ? finalError.message.toLowerCase() : '';
705
+ const isUnknownPollAbort = operation === 'workflow.awaitUpdate' && message.includes('unknown error occurred in effect.trypromise');
706
+ if (isAbortLikeError(normalized)) {
707
+ this.#log('warn', `temporal client ${operation} aborted`, {
708
+ operation,
709
+ error: describeError(normalized),
710
+ ...metadata,
711
+ });
712
+ this.#recordMetrics(Date.now() - start, true);
713
+ throw finalError;
714
+ }
715
+ if (isUnknownPollAbort) {
716
+ const abortError = new Error('Workflow update polling aborted');
717
+ abortError.name = 'AbortError';
718
+ this.#log('warn', `temporal client ${operation} aborted`, {
719
+ operation,
720
+ error: describeError(abortError),
721
+ ...metadata,
722
+ });
723
+ this.#recordMetrics(Date.now() - start, true);
724
+ throw abortError;
725
+ }
538
726
  this.#log('error', `temporal client ${operation} failed`, {
539
727
  operation,
540
- error: describeError(error),
728
+ error: describeError(finalError),
729
+ ...metadata,
541
730
  });
542
731
  this.#recordMetrics(Date.now() - start, true);
543
- throw error;
732
+ throw finalError;
544
733
  }
545
734
  }
546
735
  #log(level, message, fields) {
@@ -587,6 +776,7 @@ class TemporalClientImpl {
587
776
  return { options: overrides, controller, cleanup };
588
777
  }
589
778
  #registerUpdateController(key, controller) {
779
+ this.#abortedUpdates.delete(key);
590
780
  const current = this.#pendingUpdateControllers.get(key);
591
781
  if (current) {
592
782
  current.add(controller);
@@ -607,12 +797,14 @@ class TemporalClientImpl {
607
797
  #abortUpdateControllers(key) {
608
798
  const current = this.#pendingUpdateControllers.get(key);
609
799
  if (!current || current.size === 0) {
800
+ this.#abortedUpdates.add(key);
610
801
  return false;
611
802
  }
612
803
  for (const controller of current) {
613
804
  controller.abort();
614
805
  }
615
806
  this.#pendingUpdateControllers.delete(key);
807
+ this.#abortedUpdates.add(key);
616
808
  return true;
617
809
  }
618
810
  #makeUpdateKey(handle, updateId) {
@@ -689,6 +881,13 @@ class TemporalClientImpl {
689
881
  retryableStatusCodes,
690
882
  };
691
883
  }
884
+ #extractCloseEvent(response) {
885
+ const events = response?.history?.events ?? [];
886
+ if (events.length === 0) {
887
+ return undefined;
888
+ }
889
+ return events[events.length - 1];
890
+ }
692
891
  #buildCallContext(overrides) {
693
892
  const userHeaders = overrides?.headers ? normalizeMetadataHeaders(overrides.headers) : undefined;
694
893
  const mergedHeaders = userHeaders ? mergeHeaders(this.headers, userHeaders) : { ...this.headers };
@@ -701,29 +900,37 @@ class TemporalClientImpl {
701
900
  signal,
702
901
  }),
703
902
  retryPolicy: this.#mergeRetryPolicy(overrides?.retryPolicy),
903
+ headers: mergedHeaders,
704
904
  };
705
905
  }
706
906
  async executeRpc(operation, rpc, overrides) {
707
- const { create, retryPolicy } = this.#buildCallContext(overrides);
708
- let attempt = 0;
709
- const effect = Effect.tryPromise({
907
+ const { create, retryPolicy, headers } = this.#buildCallContext(overrides);
908
+ const interceptorContext = {
909
+ kind: 'rpc',
910
+ namespace: this.namespace,
911
+ taskQueue: this.defaultTaskQueue,
912
+ identity: this.defaultIdentity,
913
+ headers,
914
+ metadata: { retryPolicy },
915
+ };
916
+ const baseEffect = () => Effect.tryPromise({
710
917
  try: () => {
711
- attempt += 1;
918
+ interceptorContext.attempt = (interceptorContext.attempt ?? 0) + 1;
712
919
  return rpc(create());
713
920
  },
714
921
  catch: (error) => wrapRpcError(error),
715
922
  }).pipe(Effect.tapError((error) => Effect.sync(() => {
716
923
  this.#log('warn', `temporal rpc ${operation} attempt failed`, {
717
924
  operation,
718
- attempt,
925
+ attempt: interceptorContext.attempt,
719
926
  error: describeError(error),
720
927
  });
721
928
  })));
722
- const result = await Effect.runPromise(withTemporalRetry(effect, retryPolicy));
723
- if (attempt > 1) {
724
- this.#log('info', `temporal rpc ${operation} succeeded after ${attempt} attempts`, {
929
+ const result = await Effect.runPromise(runClientInterceptors(this.#clientInterceptors, interceptorContext, baseEffect));
930
+ if ((interceptorContext.attempt ?? 1) > 1) {
931
+ this.#log('info', `temporal rpc ${operation} succeeded after ${interceptorContext.attempt} attempts`, {
725
932
  operation,
726
- attempts: attempt,
933
+ attempts: interceptorContext.attempt,
727
934
  });
728
935
  }
729
936
  return result;