@temporalio/common 0.23.0 → 1.0.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 (59) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +3 -3
  3. package/lib/converter/data-converter.d.ts +5 -5
  4. package/lib/converter/data-converter.js +1 -2
  5. package/lib/converter/data-converter.js.map +1 -1
  6. package/lib/converter/json-payload-converter.d.ts +10 -0
  7. package/lib/converter/json-payload-converter.js +39 -0
  8. package/lib/converter/json-payload-converter.js.map +1 -0
  9. package/lib/converter/payload-converter.d.ts +11 -15
  10. package/lib/converter/payload-converter.js +5 -14
  11. package/lib/converter/payload-converter.js.map +1 -1
  12. package/lib/converter/payload-converters.d.ts +19 -15
  13. package/lib/converter/payload-converters.js +10 -41
  14. package/lib/converter/payload-converters.js.map +1 -1
  15. package/lib/converter/protobuf-payload-converters.js +2 -1
  16. package/lib/converter/protobuf-payload-converters.js.map +1 -1
  17. package/lib/converter/search-attribute-payload-converter.d.ts +12 -0
  18. package/lib/converter/search-attribute-payload-converter.js +64 -0
  19. package/lib/converter/search-attribute-payload-converter.js.map +1 -0
  20. package/lib/converter/types.d.ts +1 -2
  21. package/lib/converter/types.js.map +1 -1
  22. package/lib/failure.d.ts +58 -46
  23. package/lib/failure.js +85 -56
  24. package/lib/failure.js.map +1 -1
  25. package/lib/index.d.ts +1 -0
  26. package/lib/index.js +1 -0
  27. package/lib/index.js.map +1 -1
  28. package/lib/otel.d.ts +26 -0
  29. package/lib/otel.js +87 -0
  30. package/lib/otel.js.map +1 -0
  31. package/lib/proto-utils.d.ts +28 -0
  32. package/lib/proto-utils.js +85 -0
  33. package/lib/proto-utils.js.map +1 -0
  34. package/lib/protobufs.d.ts +2 -2
  35. package/lib/protobufs.js +4 -2
  36. package/lib/protobufs.js.map +1 -1
  37. package/package.json +12 -9
  38. package/src/converter/data-converter.ts +6 -6
  39. package/src/converter/json-payload-converter.ts +37 -0
  40. package/src/converter/payload-converter.ts +15 -29
  41. package/src/converter/payload-converters.ts +24 -43
  42. package/src/converter/protobuf-payload-converters.ts +1 -1
  43. package/src/converter/search-attribute-payload-converter.ts +71 -0
  44. package/src/converter/types.ts +1 -2
  45. package/src/failure.ts +92 -62
  46. package/src/index.ts +1 -0
  47. package/src/otel.ts +63 -0
  48. package/src/proto-utils.ts +103 -0
  49. package/src/protobufs.ts +2 -2
  50. package/lib/converter/patch-protobuf-root.d.ts +0 -1
  51. package/lib/converter/patch-protobuf-root.js +0 -6
  52. package/lib/converter/patch-protobuf-root.js.map +0 -1
  53. package/lib/converter/wrapped-payload-converter.d.ts +0 -12
  54. package/lib/converter/wrapped-payload-converter.js +0 -28
  55. package/lib/converter/wrapped-payload-converter.js.map +0 -1
  56. package/src/converter/patch-protobuf-root.ts +0 -1
  57. package/src/converter/wrapped-payload-converter.ts +0 -31
  58. package/tsconfig.json +0 -9
  59. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,71 @@
1
+ import { IllegalStateError, ValueError } from '@temporalio/internal-workflow-common';
2
+ import { PayloadConverter } from './payload-converter';
3
+ import { JsonPayloadConverter } from './json-payload-converter';
4
+ import { Payload, str } from './types';
5
+
6
+ const jsonConverter = new JsonPayloadConverter();
7
+ const validNonDateTypes = ['string', 'number', 'boolean'];
8
+
9
+ /**
10
+ * Converts Search Attribute values using JsonPayloadConverter
11
+ */
12
+ export class SearchAttributePayloadConverter implements PayloadConverter {
13
+ public toPayload(values: unknown): Payload {
14
+ if (!(values instanceof Array)) {
15
+ throw new ValueError(`SearchAttribute value must be an array`);
16
+ }
17
+
18
+ if (values.length > 0) {
19
+ const firstValue = values[0];
20
+ const firstType = typeof firstValue;
21
+ if (firstType === 'object') {
22
+ for (const idx in values) {
23
+ const value = values[idx];
24
+ if (!(value instanceof Date)) {
25
+ throw new ValueError(
26
+ `SearchAttribute values must arrays of strings, numbers, booleans, or Dates. The value ${value} at index ${idx} is of type ${typeof value}`
27
+ );
28
+ }
29
+ }
30
+ } else {
31
+ if (!validNonDateTypes.includes(firstType)) {
32
+ throw new ValueError(`SearchAttribute array values must be: string | number | boolean | Date`);
33
+ }
34
+
35
+ for (const idx in values) {
36
+ const value = values[idx];
37
+ if (typeof value !== firstType) {
38
+ throw new ValueError(
39
+ `All SearchAttribute array values must be of the same type. The first value ${firstValue} of type ${firstType} doesn't match value ${value} of type ${typeof value} at index ${idx}`
40
+ );
41
+ }
42
+ }
43
+ }
44
+ }
45
+
46
+ // JSON.stringify takes care of converting Dates to ISO strings
47
+ const ret = jsonConverter.toPayload(values);
48
+ if (ret === undefined) {
49
+ throw new IllegalStateError('Could not convert search attributes to payloads');
50
+ }
51
+ return ret;
52
+ }
53
+
54
+ /**
55
+ * Datetime Search Attribute values are converted to `Date`s
56
+ */
57
+ public fromPayload<T>(payload: Payload): T {
58
+ if (payload.metadata === undefined || payload.metadata === null) {
59
+ throw new ValueError('Missing payload metadata');
60
+ }
61
+
62
+ const value = jsonConverter.fromPayload(payload);
63
+ let arrayWrappedValue = value instanceof Array ? value : [value];
64
+
65
+ const searchAttributeType = str(payload.metadata.type);
66
+ if (searchAttributeType === 'Datetime') {
67
+ arrayWrappedValue = arrayWrappedValue.map((dateString) => new Date(dateString));
68
+ }
69
+ return arrayWrappedValue as unknown as T;
70
+ }
71
+ }
@@ -1,7 +1,6 @@
1
- import type { coresdk } from '@temporalio/proto';
2
1
  import { TextDecoder, TextEncoder } from './encoding';
3
2
 
4
- export type Payload = coresdk.common.IPayload;
3
+ export { Payload } from '@temporalio/internal-workflow-common';
5
4
 
6
5
  /**
7
6
  * Transform an *ascii* string into a Uint8Array
package/src/failure.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { checkExtends, hasOwnProperties, isRecord } from '@temporalio/internal-workflow-common';
2
2
  import type { temporal } from '@temporalio/proto';
3
- import { arrayFromPayloads, fromPayloadsAtIndex, toPayloads } from './converter/payload-converter';
4
- import { WrappedPayloadConverter } from './converter/wrapped-payload-converter';
3
+ import { PayloadConverter, arrayFromPayloads, fromPayloadsAtIndex, toPayloads } from './converter/payload-converter';
5
4
 
6
5
  export const FAILURE_SOURCE = 'TypeScriptSDK';
7
6
  export type ProtoFailure = temporal.api.failure.v1.IFailure;
@@ -71,27 +70,28 @@ export class ServerFailure extends TemporalFailure {
71
70
  }
72
71
 
73
72
  /**
74
- * Application failure is used to communicate application specific failures between Workflows and
75
- * Activities.
73
+ * `ApplicationFailure`s are used to communicate application-specific failures in Workflows and Activities.
76
74
  *
77
- * Throw this exception to have full control over type and details if the exception delivered to
78
- * the caller workflow or client.
75
+ * The {@link type} property is matched against {@link RetryPolicy.nonRetryableErrorTypes} to determine if an instance
76
+ * of this error is retryable. Another way to avoid retrying is by setting the {@link nonRetryable} flag to `true`.
79
77
  *
80
- * Any unhandled exception which doesn't extend {@link TemporalFailure} is converted to an
81
- * instance of this class before being returned to a caller.
78
+ * In Workflows, if you throw a non-`ApplicationFailure`, the Workflow Task will fail and be retried. If you throw an
79
+ * `ApplicationFailure`, the Workflow Execution will fail.
82
80
  *
83
- * The {@link type} property is used by {@link temporal.common.RetryOptions} to determine if
84
- * an instance of this exception is non retryable. Another way to avoid retrying an exception of
85
- * this type is by setting {@link nonRetryable} flag to `true`.
81
+ * In Activities, you can either throw an `ApplicationFailure` or another `Error` to fail the Activity Task. In the
82
+ * latter case, the `Error` will be converted to an `ApplicationFailure`. If the
83
+ * {@link https://docs.temporal.io/concepts/what-is-an-activity-execution | Activity Execution} fails, the
84
+ * `ApplicationFailure` from the last Activity Task will be the `cause` of the {@link ActivityFailure} thrown in the
85
+ * Workflow.
86
86
  *
87
- * The conversion of an exception that doesn't extend {@link TemporalFailure} to an
88
- * ApplicationFailure is done as following:
87
+ * The conversion of an error that doesn't extend {@link TemporalFailure} to an `ApplicationFailure` is done as
88
+ * following:
89
89
  *
90
- * - type is set to the exception full type name.
91
- * - message is set to the exception message
92
- * - nonRetryable is set to false
93
- * - details are set to null
94
- * - stack trace is copied from the original exception
90
+ * - `type` is set to `error.constructor?.name ?? error.name`
91
+ * - `message` is set to `error.message`
92
+ * - `nonRetryable` is set to false
93
+ * - `details` are set to null
94
+ * - stack trace is copied from the original error
95
95
  */
96
96
  export class ApplicationFailure extends TemporalFailure {
97
97
  public readonly name: string = 'ApplicationFailure';
@@ -107,29 +107,26 @@ export class ApplicationFailure extends TemporalFailure {
107
107
  }
108
108
 
109
109
  /**
110
- * New ApplicationFailure with {@link nonRetryable} flag set to false. Note that this
111
- * exception still can be not retried by the service if its type is included into doNotRetry
112
- * property of the correspondent retry policy.
110
+ * Get a new `ApplicationFailure` with the {@link nonRetryable} flag set to false. Note that this error will still
111
+ * not be retried if its `type` is included in {@link RetryPolicy.nonRetryableErrorTypes}.
113
112
  *
114
- * @param message optional error message
115
- * @param type optional error type that is used by {@link RetryOptions.nonRetryableErrorTypes}.
116
- * @param details optional details about the failure. They are serialized using the same approach
117
- * as arguments and results.
113
+ * @param message Optional error message
114
+ * @param type Optional error type (used by {@link RetryPolicy.nonRetryableErrorTypes})
115
+ * @param details Optional details about the failure. Serialized by the Worker's {@link PayloadConverter}.
118
116
  */
119
117
  public static retryable(message: string | undefined, type?: string, ...details: unknown[]): ApplicationFailure {
120
118
  return new this(message, type ?? 'Error', false, details);
121
119
  }
122
120
 
123
121
  /**
124
- * New ApplicationFailure with {@link nonRetryable} flag set to true.
122
+ * Get a new `ApplicationFailure` with the {@link nonRetryable} flag set to true.
125
123
  *
126
- * It means that this exception is not going to be retried even if it is not included into
127
- * retry policy doNotRetry list.
124
+ * When thrown from an Activity or Workflow, the Activity or Workflow will not be retried (even if `type` is not
125
+ * listed in {@link RetryPolicy.nonRetryableErrorTypes}).
128
126
  *
129
- * @param message optional error message
130
- * @param type optional error type
131
- * @param details optional details about the failure. They are serialized using the same approach
132
- * as arguments and results.
127
+ * @param message Optional error message
128
+ * @param type Optional error type
129
+ * @param details Optional details about the failure. Serialized by the Worker's {@link PayloadConverter}.
133
130
  */
134
131
  public static nonRetryable(message: string | undefined, type?: string, ...details: unknown[]): ApplicationFailure {
135
132
  return new this(message, type ?? 'Error', true, details);
@@ -137,7 +134,11 @@ export class ApplicationFailure extends TemporalFailure {
137
134
  }
138
135
 
139
136
  /**
140
- * Used as the cause for when a Workflow or Activity has been cancelled
137
+ * This error is thrown when Cancellation has been requested. To allow Cancellation to happen, let it propagate. To
138
+ * ignore Cancellation, catch it and continue executing. Note that Cancellation can only be requested a single time, so
139
+ * your Workflow/Activity Execution will not receive further Cancellation requests.
140
+ *
141
+ * When a Workflow or Activity has been successfully cancelled, a `CancelledFailure` will be the `cause`.
141
142
  */
142
143
  export class CancelledFailure extends TemporalFailure {
143
144
  public readonly name: string = 'CancelledFailure';
@@ -148,7 +149,7 @@ export class CancelledFailure extends TemporalFailure {
148
149
  }
149
150
 
150
151
  /**
151
- * Used as the cause for when a Workflow has been terminated
152
+ * Used as the `cause` when a Workflow has been terminated
152
153
  */
153
154
  export class TerminatedFailure extends TemporalFailure {
154
155
  public readonly name: string = 'TerminatedFailure';
@@ -174,12 +175,14 @@ export class TimeoutFailure extends TemporalFailure {
174
175
  }
175
176
 
176
177
  /**
177
- * Contains information about an activity failure. Always contains the original reason for the
178
- * failure as its cause. For example if an activity timed out the cause is {@link TimeoutFailure}.
178
+ * Contains information about an Activity failure. Always contains the original reason for the failure as its `cause`.
179
+ * For example, if an Activity timed out, the cause will be a {@link TimeoutFailure}.
179
180
  *
180
181
  * This exception is expected to be thrown only by the framework code.
181
182
  */
182
183
  export class ActivityFailure extends TemporalFailure {
184
+ public readonly name: string = 'ActivityFailure';
185
+
183
186
  public constructor(
184
187
  public readonly activityType: string,
185
188
  public readonly activityId: string | undefined,
@@ -192,12 +195,14 @@ export class ActivityFailure extends TemporalFailure {
192
195
  }
193
196
 
194
197
  /**
195
- * Contains information about an child workflow failure. Always contains the original reason for the
196
- * failure as its cause. For example if a child workflow was terminated the cause is {@link TerminatedFailure}.
198
+ * Contains information about a Child Workflow failure. Always contains the reason for the failure as its {@link cause}.
199
+ * For example, if the Child was Terminated, the `cause` is a {@link TerminatedFailure}.
197
200
  *
198
201
  * This exception is expected to be thrown only by the framework code.
199
202
  */
200
203
  export class ChildWorkflowFailure extends TemporalFailure {
204
+ public readonly name: string = 'ChildWorkflowFailure';
205
+
201
206
  public constructor(
202
207
  public readonly namespace: string | undefined,
203
208
  public readonly execution: WorkflowExecution,
@@ -214,7 +219,7 @@ export class ChildWorkflowFailure extends TemporalFailure {
214
219
  */
215
220
  export function optionalErrorToOptionalFailure(
216
221
  err: unknown,
217
- payloadConverter: WrappedPayloadConverter
222
+ payloadConverter: PayloadConverter
218
223
  ): ProtoFailure | undefined {
219
224
  return err ? errorToFailure(err, payloadConverter) : undefined;
220
225
  }
@@ -226,9 +231,9 @@ const CUTOFF_STACK_PATTERNS = [
226
231
  /** Activity execution */
227
232
  /\s+at Activity\.execute \(.*[\\/]worker[\\/](?:src|lib)[\\/]activity\.[jt]s:\d+:\d+\)/,
228
233
  /** Workflow activation */
229
- /\s+at Activator\.\S+NextHandler \(webpack-internal:\/\/\/.*\/internals\.[jt]s:\d+:\d+\)/,
234
+ /\s+at Activator\.\S+NextHandler \(.*[\\/]workflow[\\/](?:src|lib)[\\/]internals\.[jt]s:\d+:\d+\)/,
230
235
  /** Workflow run anything in context */
231
- /\s+at Script\.runInContext/,
236
+ /\s+at Script\.runInContext \((?:node:vm|vm\.js):\d+:\d+\)/,
232
237
  ];
233
238
 
234
239
  /**
@@ -249,7 +254,7 @@ export function cutoffStackTrace(stack?: string): string {
249
254
  /**
250
255
  * Converts a caught error to a Failure proto message
251
256
  */
252
- export function errorToFailure(err: unknown, payloadConverter: WrappedPayloadConverter): ProtoFailure {
257
+ export function errorToFailure(err: unknown, payloadConverter: PayloadConverter): ProtoFailure {
253
258
  if (err instanceof TemporalFailure) {
254
259
  if (err.failure) return err.failure;
255
260
 
@@ -338,27 +343,38 @@ export function errorToFailure(err: unknown, payloadConverter: WrappedPayloadCon
338
343
  };
339
344
  }
340
345
 
341
- const recommendation = ` [A non-Error value was thrown from your code. We recommend throwing Error objects so that we can provide a stack trace.]`;
346
+ const recommendation = ` [A non-Error value was thrown from your code. We recommend throwing Error objects so that we can provide a stack trace]`;
342
347
 
343
348
  if (typeof err === 'string') {
344
349
  return { ...base, message: err + recommendation };
345
350
  }
351
+ if (typeof err === 'object') {
352
+ let message = '';
353
+ try {
354
+ message = JSON.stringify(err);
355
+ } catch (_err) {
356
+ message = String(err);
357
+ }
358
+ return { ...base, message: message + recommendation };
359
+ }
346
360
 
347
- return { ...base, message: JSON.stringify(err) + recommendation };
361
+ return { ...base, message: String(err) + recommendation };
348
362
  }
349
363
 
350
364
  /**
351
365
  * If `err` is an Error it is turned into an `ApplicationFailure`.
352
366
  *
353
- * If `err` was already a `TemporalFailure`, returns the original error.
367
+ * If `err` was already a `ApplicationFailure`, returns the original error.
354
368
  *
355
369
  * Otherwise returns an `ApplicationFailure` with `String(err)` as the message.
356
370
  */
357
- export function ensureTemporalFailure(err: unknown): TemporalFailure {
358
- if (err instanceof TemporalFailure) {
371
+ export function ensureApplicationFailure(err: unknown): ApplicationFailure {
372
+ if (err instanceof ApplicationFailure) {
359
373
  return err;
360
- } else if (err instanceof Error) {
361
- const failure = new ApplicationFailure(err.message, err.name, false);
374
+ }
375
+ if (err instanceof Error) {
376
+ const name = err.constructor?.name ?? err.name;
377
+ const failure = new ApplicationFailure(err.message, name, false);
362
378
  failure.stack = err.stack;
363
379
  return failure;
364
380
  } else {
@@ -368,12 +384,26 @@ export function ensureTemporalFailure(err: unknown): TemporalFailure {
368
384
  }
369
385
  }
370
386
 
387
+ /**
388
+ * If `err` is an Error it is turned into an `ApplicationFailure`.
389
+ *
390
+ * If `err` was already a `TemporalFailure`, returns the original error.
391
+ *
392
+ * Otherwise returns an `ApplicationFailure` with `String(err)` as the message.
393
+ */
394
+ export function ensureTemporalFailure(err: unknown): TemporalFailure {
395
+ if (err instanceof TemporalFailure) {
396
+ return err;
397
+ }
398
+ return ensureApplicationFailure(err);
399
+ }
400
+
371
401
  /**
372
402
  * Converts a Failure proto message to a JS Error object if defined or returns undefined.
373
403
  */
374
404
  export function optionalFailureToOptionalError(
375
405
  failure: ProtoFailure | undefined | null,
376
- payloadConverter: WrappedPayloadConverter
406
+ payloadConverter: PayloadConverter
377
407
  ): TemporalFailure | undefined {
378
408
  return failure ? failureToError(failure, payloadConverter) : undefined;
379
409
  }
@@ -383,7 +413,7 @@ export function optionalFailureToOptionalError(
383
413
  *
384
414
  * Does not set common properties, that is done in {@link failureToError}.
385
415
  */
386
- export function failureToErrorInner(failure: ProtoFailure, payloadConverter: WrappedPayloadConverter): TemporalFailure {
416
+ export function failureToErrorInner(failure: ProtoFailure, payloadConverter: PayloadConverter): TemporalFailure {
387
417
  if (failure.applicationFailureInfo) {
388
418
  return new ApplicationFailure(
389
419
  failure.message ?? undefined,
@@ -463,7 +493,7 @@ export function failureToErrorInner(failure: ProtoFailure, payloadConverter: Wra
463
493
  /**
464
494
  * Converts a Failure proto message to a JS Error object.
465
495
  */
466
- export function failureToError(failure: ProtoFailure, payloadConverter: WrappedPayloadConverter): TemporalFailure {
496
+ export function failureToError(failure: ProtoFailure, payloadConverter: PayloadConverter): TemporalFailure {
467
497
  const err = failureToErrorInner(failure, payloadConverter);
468
498
  err.stack = failure.stackTrace ?? '';
469
499
  err.failure = failure;
@@ -471,20 +501,20 @@ export function failureToError(failure: ProtoFailure, payloadConverter: WrappedP
471
501
  }
472
502
 
473
503
  /**
474
- * Get the root cause (string) of given error `err`.
504
+ * Get the root cause message of given `error`.
475
505
  *
476
- * In case `err` is a {@link TemporalFailure}, recurse the cause chain and return the root's message.
477
- * Otherwise, return `err.message`.
506
+ * In case `error` is a {@link TemporalFailure}, recurse the `cause` chain and return the root `cause.message`.
507
+ * Otherwise, return `error.message`.
478
508
  */
479
- export function rootCause(err: unknown): string | undefined {
480
- if (err instanceof TemporalFailure) {
481
- return err.cause ? rootCause(err.cause) : err.message;
509
+ export function rootCause(error: unknown): string | undefined {
510
+ if (error instanceof TemporalFailure) {
511
+ return error.cause ? rootCause(error.cause) : error.message;
482
512
  }
483
- if (err instanceof Error) {
484
- return err.message;
513
+ if (error instanceof Error) {
514
+ return error.message;
485
515
  }
486
- if (typeof err === 'string') {
487
- return err;
516
+ if (typeof error === 'string') {
517
+ return error;
488
518
  }
489
519
  return undefined;
490
520
  }
package/src/index.ts CHANGED
@@ -13,5 +13,6 @@ export * from './converter/data-converter';
13
13
  export * from './converter/payload-codec';
14
14
  export * from './converter/payload-converter';
15
15
  export * from './converter/payload-converters';
16
+ export * from './converter/json-payload-converter';
16
17
  export * from './converter/types';
17
18
  export * from './failure';
package/src/otel.ts ADDED
@@ -0,0 +1,63 @@
1
+ import * as otel from '@opentelemetry/api';
2
+ import { Headers } from '@temporalio/internal-workflow-common';
3
+ import { defaultPayloadConverter } from './converter/payload-converters';
4
+
5
+ /** Default trace header for opentelemetry interceptors */
6
+ export const TRACE_HEADER = '_tracer-data';
7
+ /** As in workflow run id */
8
+ export const RUN_ID_ATTR_KEY = 'run_id';
9
+ /** For a workflow or activity task */
10
+ export const TASK_TOKEN_ATTR_KEY = 'task_token';
11
+ /** Number of jobs in a workflow activation */
12
+ export const NUM_JOBS_ATTR_KEY = 'num_jobs';
13
+
14
+ const payloadConverter = defaultPayloadConverter;
15
+
16
+ /**
17
+ * If found, return an otel Context deserialized from the provided headers
18
+ */
19
+ export async function extractContextFromHeaders(headers: Headers): Promise<otel.Context | undefined> {
20
+ const encodedSpanContext = headers[TRACE_HEADER];
21
+ if (encodedSpanContext === undefined) {
22
+ return undefined;
23
+ }
24
+ const textMap: Record<string, string> = payloadConverter.fromPayload(encodedSpanContext);
25
+ return otel.propagation.extract(otel.context.active(), textMap, otel.defaultTextMapGetter);
26
+ }
27
+
28
+ /**
29
+ * If found, return an otel SpanContext deserialized from the provided headers
30
+ */
31
+ export async function extractSpanContextFromHeaders(headers: Headers): Promise<otel.SpanContext | undefined> {
32
+ const context = await extractContextFromHeaders(headers);
33
+ if (context === undefined) {
34
+ return undefined;
35
+ }
36
+
37
+ return otel.trace.getSpanContext(context);
38
+ }
39
+
40
+ /**
41
+ * Given headers, return new headers with the current otel context inserted
42
+ */
43
+ export async function headersWithContext(headers: Headers): Promise<Headers> {
44
+ const carrier = {};
45
+ otel.propagation.inject(otel.context.active(), carrier, otel.defaultTextMapSetter);
46
+ return { ...headers, [TRACE_HEADER]: payloadConverter.toPayload(carrier) };
47
+ }
48
+
49
+ /**
50
+ * Link a span to an maybe-existing span context
51
+ */
52
+ export function linkSpans(fromSpan: otel.Span, toContext?: otel.SpanContext): void {
53
+ if (toContext !== undefined) {
54
+ // TODO: I have to go around typescript because otel api 😢
55
+ // See https://github.com/open-telemetry/opentelemetry-js-api/issues/124
56
+ const links = (fromSpan as any).links;
57
+ if (links === undefined) {
58
+ (fromSpan as any).links = [{ context: toContext }];
59
+ } else {
60
+ links.push({ context: toContext });
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,103 @@
1
+ import proto from '@temporalio/proto';
2
+ import { fromProto3JSON, toProto3JSON } from 'proto3-json-serializer';
3
+ import { patchProtobufRoot } from '@temporalio/proto/lib/patch-protobuf-root';
4
+
5
+ export type History = proto.temporal.api.history.v1.IHistory;
6
+ export type Payload = proto.temporal.api.common.v1.IPayload;
7
+
8
+ // Cast to any because the generated proto module types are missing the lookupType method
9
+ const patched = patchProtobufRoot(proto) as any;
10
+ const historyType = patched.lookupType('temporal.api.history.v1.History');
11
+ const payloadType = patched.lookupType('temporal.api.common.v1.Payload');
12
+
13
+ function pascalCaseToConstantCase(s: string) {
14
+ return s.replace(/[^\b][A-Z]/g, (m) => `${m[0]}_${m[1]}`).toUpperCase();
15
+ }
16
+
17
+ function fixEnumValue<O extends Record<string, any>>(obj: O, attr: keyof O, prefix: string) {
18
+ return (
19
+ obj[attr] && {
20
+ [attr]: `${prefix}_${pascalCaseToConstantCase(obj[attr])}`,
21
+ }
22
+ );
23
+ }
24
+
25
+ function fixHistoryEvent(e: Record<string, any>) {
26
+ const type = Object.keys(e).find((k) => k.endsWith('EventAttributes'));
27
+ if (!type) {
28
+ throw new TypeError(`Missing attributes in history event: ${JSON.stringify(e)}`);
29
+ }
30
+
31
+ return {
32
+ ...e,
33
+ ...fixEnumValue(e, 'eventType', 'EVENT_TYPE'),
34
+ [type]: {
35
+ ...e[type],
36
+ ...(e[type].taskQueue && {
37
+ taskQueue: { ...e[type].taskQueue, ...fixEnumValue(e[type].taskQueue, 'kind', 'TASK_QUEUE_KIND') },
38
+ }),
39
+ ...fixEnumValue(e[type], 'parentClosePolicy', 'PARENT_CLOSE_POLICY'),
40
+ ...fixEnumValue(e[type], 'workflowIdReusePolicy', 'WORKFLOW_ID_REUSE_POLICY'),
41
+ ...fixEnumValue(e[type], 'initiator', 'CONTINUE_AS_NEW_INITIATOR'),
42
+ ...fixEnumValue(e[type], 'retryState', 'RETRY_STATE'),
43
+ ...(e[type].childWorkflowExecutionFailureInfo && {
44
+ childWorkflowExecutionFailureInfo: {
45
+ ...e[type].childWorkflowExecutionFailureInfo,
46
+ ...fixEnumValue(e[type].childWorkflowExecutionFailureInfo, 'retryState', 'RETRY_STATE'),
47
+ },
48
+ }),
49
+ },
50
+ };
51
+ }
52
+
53
+ function fixHistory(h: Record<string, any>) {
54
+ return {
55
+ events: h.events.map(fixHistoryEvent),
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Convert a proto JSON representation of History to a valid History object
61
+ */
62
+ export function historyFromJSON(history: unknown): History {
63
+ if (typeof history !== 'object' || history == null || !Array.isArray((history as any).events)) {
64
+ throw new TypeError('Invalid history, expected an object with an array of events');
65
+ }
66
+ const loaded = fromProto3JSON(historyType, fixHistory(history));
67
+ if (loaded === null) {
68
+ throw new TypeError('Invalid history');
69
+ }
70
+ return loaded as any;
71
+ }
72
+
73
+ /**
74
+ * JSON representation of Temporal's {@link Payload} protobuf object
75
+ */
76
+ export interface JSONPayload {
77
+ /**
78
+ * Mapping of key to base64 encoded value
79
+ */
80
+ metadata?: Record<string, string> | null;
81
+ /**
82
+ * base64 encoded value
83
+ */
84
+ data?: string | null;
85
+ }
86
+
87
+ /**
88
+ * Convert from protobuf payload to JSON
89
+ */
90
+ export function payloadToJSON(payload: Payload): JSONPayload {
91
+ return toProto3JSON(patched.temporal.api.common.v1.Payload.create(payload)) as any;
92
+ }
93
+
94
+ /**
95
+ * Convert from JSON to protobuf payload
96
+ */
97
+ export function JSONToPayload(json: JSONPayload): Payload {
98
+ const loaded = fromProto3JSON(payloadType, json as any);
99
+ if (loaded === null) {
100
+ throw new TypeError('Invalid payload');
101
+ }
102
+ return loaded as any;
103
+ }
package/src/protobufs.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Entry point for classes and utilities related to using
3
- * [Protobufs](https://docs.temporal.io/docs/typescript/data-converters#protobufs) for serialization.
3
+ * {@link https://docs.temporal.io/typescript/data-converters#protobufs | Protobufs} for serialization.
4
4
  *
5
5
  * Import from `@temporalio/common/lib/protobufs`, for example:
6
6
  *
@@ -12,4 +12,4 @@
12
12
 
13
13
  // Don't export from index, so we save space in Workflow bundles of users who don't use Protobufs
14
14
  export * from './converter/protobuf-payload-converters';
15
- export * from './converter/patch-protobuf-root';
15
+ export { patchProtobufRoot } from '@temporalio/proto/lib/patch-protobuf-root';
@@ -1 +0,0 @@
1
- export { patchProtobufRoot } from '@temporalio/proto/lib/patch-protobuf-root';
@@ -1,6 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.patchProtobufRoot = void 0;
4
- var patch_protobuf_root_1 = require("@temporalio/proto/lib/patch-protobuf-root");
5
- Object.defineProperty(exports, "patchProtobufRoot", { enumerable: true, get: function () { return patch_protobuf_root_1.patchProtobufRoot; } });
6
- //# sourceMappingURL=patch-protobuf-root.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"patch-protobuf-root.js","sourceRoot":"","sources":["../../src/converter/patch-protobuf-root.ts"],"names":[],"mappings":";;;AAAA,iFAA8E;AAArE,wHAAA,iBAAiB,OAAA"}
@@ -1,12 +0,0 @@
1
- import { PayloadConverter } from './payload-converter';
2
- import { Payload } from './types';
3
- /**
4
- * When we call {@link PayloadConverter.toPayload}, we want it to either throw or return a Payload, so we wrap Payload
5
- * Converters with this class before using them.
6
- */
7
- export declare class WrappedPayloadConverter implements PayloadConverter {
8
- private readonly payloadConverter;
9
- constructor(payloadConverter: PayloadConverter);
10
- toPayload(value: unknown): Payload;
11
- fromPayload<T>(payload: Payload): T;
12
- }
@@ -1,28 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WrappedPayloadConverter = void 0;
4
- const internal_workflow_common_1 = require("@temporalio/internal-workflow-common");
5
- /**
6
- * When we call {@link PayloadConverter.toPayload}, we want it to either throw or return a Payload, so we wrap Payload
7
- * Converters with this class before using them.
8
- */
9
- class WrappedPayloadConverter {
10
- constructor(payloadConverter) {
11
- this.payloadConverter = payloadConverter;
12
- }
13
- toPayload(value) {
14
- const result = this.payloadConverter.toPayload(value);
15
- if (!isPayload(result)) {
16
- throw new internal_workflow_common_1.ValueError(`The Payload Converter method ${Object.getPrototypeOf(this.payloadConverter).constructor.name}.toPayload must return a Payload. Received \`${result}\` of type \`${typeof result}\` when trying to convert \`${value}\` of type \`${typeof value}\`.`);
17
- }
18
- return result;
19
- }
20
- fromPayload(payload) {
21
- return this.payloadConverter.fromPayload(payload);
22
- }
23
- }
24
- exports.WrappedPayloadConverter = WrappedPayloadConverter;
25
- function isPayload(payload) {
26
- return (0, internal_workflow_common_1.isRecord)(payload) && ((0, internal_workflow_common_1.hasOwnProperty)(payload, 'metadata') || (0, internal_workflow_common_1.hasOwnProperty)(payload, 'data'));
27
- }
28
- //# sourceMappingURL=wrapped-payload-converter.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"wrapped-payload-converter.js","sourceRoot":"","sources":["../../src/converter/wrapped-payload-converter.ts"],"names":[],"mappings":";;;AAAA,mFAA4F;AAI5F;;;GAGG;AACH,MAAa,uBAAuB;IAClC,YAA6B,gBAAkC;QAAlC,qBAAgB,GAAhB,gBAAgB,CAAkB;IAAG,CAAC;IAE5D,SAAS,CAAC,KAAc;QAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACtD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE;YACtB,MAAM,IAAI,qCAAU,CAClB,gCACE,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,WAAW,CAAC,IAC3D,gDAAgD,MAAM,gBAAgB,OAAO,MAAM,+BAA+B,KAAK,gBAAgB,OAAO,KAAK,KAAK,CACzJ,CAAC;SACH;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAEM,WAAW,CAAI,OAAgB;QACpC,OAAO,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACpD,CAAC;CACF;AAlBD,0DAkBC;AAED,SAAS,SAAS,CAAC,OAAgB;IACjC,OAAO,IAAA,mCAAQ,EAAC,OAAO,CAAC,IAAI,CAAC,IAAA,yCAAc,EAAC,OAAO,EAAE,UAAU,CAAC,IAAI,IAAA,yCAAc,EAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;AACvG,CAAC"}
@@ -1 +0,0 @@
1
- export { patchProtobufRoot } from '@temporalio/proto/lib/patch-protobuf-root';