@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.
- package/LICENSE.md +1 -1
- package/README.md +3 -3
- package/lib/converter/data-converter.d.ts +5 -5
- package/lib/converter/data-converter.js +1 -2
- package/lib/converter/data-converter.js.map +1 -1
- package/lib/converter/json-payload-converter.d.ts +10 -0
- package/lib/converter/json-payload-converter.js +39 -0
- package/lib/converter/json-payload-converter.js.map +1 -0
- package/lib/converter/payload-converter.d.ts +11 -15
- package/lib/converter/payload-converter.js +5 -14
- package/lib/converter/payload-converter.js.map +1 -1
- package/lib/converter/payload-converters.d.ts +19 -15
- package/lib/converter/payload-converters.js +10 -41
- package/lib/converter/payload-converters.js.map +1 -1
- package/lib/converter/protobuf-payload-converters.js +2 -1
- package/lib/converter/protobuf-payload-converters.js.map +1 -1
- package/lib/converter/search-attribute-payload-converter.d.ts +12 -0
- package/lib/converter/search-attribute-payload-converter.js +64 -0
- package/lib/converter/search-attribute-payload-converter.js.map +1 -0
- package/lib/converter/types.d.ts +1 -2
- package/lib/converter/types.js.map +1 -1
- package/lib/failure.d.ts +58 -46
- package/lib/failure.js +85 -56
- package/lib/failure.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/otel.d.ts +26 -0
- package/lib/otel.js +87 -0
- package/lib/otel.js.map +1 -0
- package/lib/proto-utils.d.ts +28 -0
- package/lib/proto-utils.js +85 -0
- package/lib/proto-utils.js.map +1 -0
- package/lib/protobufs.d.ts +2 -2
- package/lib/protobufs.js +4 -2
- package/lib/protobufs.js.map +1 -1
- package/package.json +12 -9
- package/src/converter/data-converter.ts +6 -6
- package/src/converter/json-payload-converter.ts +37 -0
- package/src/converter/payload-converter.ts +15 -29
- package/src/converter/payload-converters.ts +24 -43
- package/src/converter/protobuf-payload-converters.ts +1 -1
- package/src/converter/search-attribute-payload-converter.ts +71 -0
- package/src/converter/types.ts +1 -2
- package/src/failure.ts +92 -62
- package/src/index.ts +1 -0
- package/src/otel.ts +63 -0
- package/src/proto-utils.ts +103 -0
- package/src/protobufs.ts +2 -2
- package/lib/converter/patch-protobuf-root.d.ts +0 -1
- package/lib/converter/patch-protobuf-root.js +0 -6
- package/lib/converter/patch-protobuf-root.js.map +0 -1
- package/lib/converter/wrapped-payload-converter.d.ts +0 -12
- package/lib/converter/wrapped-payload-converter.js +0 -28
- package/lib/converter/wrapped-payload-converter.js.map +0 -1
- package/src/converter/patch-protobuf-root.ts +0 -1
- package/src/converter/wrapped-payload-converter.ts +0 -31
- package/tsconfig.json +0 -9
- 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
|
+
}
|
package/src/converter/types.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type { coresdk } from '@temporalio/proto';
|
|
2
1
|
import { TextDecoder, TextEncoder } from './encoding';
|
|
3
2
|
|
|
4
|
-
export
|
|
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
|
-
*
|
|
75
|
-
* Activities.
|
|
73
|
+
* `ApplicationFailure`s are used to communicate application-specific failures in Workflows and Activities.
|
|
76
74
|
*
|
|
77
|
-
*
|
|
78
|
-
* the
|
|
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
|
-
*
|
|
81
|
-
*
|
|
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
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
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
|
|
88
|
-
*
|
|
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
|
|
91
|
-
* - message is set to
|
|
92
|
-
* - nonRetryable is set to false
|
|
93
|
-
* - details are set to null
|
|
94
|
-
* - stack trace is copied from the original
|
|
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
|
-
*
|
|
111
|
-
*
|
|
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
|
|
115
|
-
* @param type
|
|
116
|
-
* @param details
|
|
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
|
-
*
|
|
122
|
+
* Get a new `ApplicationFailure` with the {@link nonRetryable} flag set to true.
|
|
125
123
|
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
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
|
|
130
|
-
* @param type
|
|
131
|
-
* @param details
|
|
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
|
-
*
|
|
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
|
|
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
|
|
178
|
-
*
|
|
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
|
|
196
|
-
*
|
|
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:
|
|
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 \(
|
|
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:
|
|
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:
|
|
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 `
|
|
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
|
|
358
|
-
if (err instanceof
|
|
371
|
+
export function ensureApplicationFailure(err: unknown): ApplicationFailure {
|
|
372
|
+
if (err instanceof ApplicationFailure) {
|
|
359
373
|
return err;
|
|
360
|
-
}
|
|
361
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
504
|
+
* Get the root cause message of given `error`.
|
|
475
505
|
*
|
|
476
|
-
* In case `
|
|
477
|
-
* Otherwise, return `
|
|
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(
|
|
480
|
-
if (
|
|
481
|
-
return
|
|
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 (
|
|
484
|
-
return
|
|
513
|
+
if (error instanceof Error) {
|
|
514
|
+
return error.message;
|
|
485
515
|
}
|
|
486
|
-
if (typeof
|
|
487
|
-
return
|
|
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
|
-
*
|
|
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
|
|
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';
|