Skip to content

Commit a225163

Browse files
authored
feat: Add support for custom failure converters (#887)
1 parent 734e1d9 commit a225163

22 files changed

+849
-579
lines changed

docs/data-converter.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,17 @@ function workflowInclusiveInstanceOf(instance: unknown, type: Function): boolean
2525

2626
Given the possibility of switching or adding other isolation methods in future, we opted to convert to/from Payloads inside the vm (`PayloadConverter`). We also added another transformer layer called `PayloadCodec` that runs outside the vm, can use node async APIs (like `zlib.gzip` for compression or `crypto.scrypt` for encryption), and operates on Payloads. A `DataConverter` is a `PayloadConverter` and zero or more `PayloadCodec`s:
2727

28+
Later on (2022-09-22) we added a `FailureConverter` that is responsible to convert from proto `Failure` instances to JS
29+
`Error`s and back. The failure converter runs inside the Workflow vm and may place failure attributes in the
30+
[`Failure.encoded_attributes`][encoded_attributes] `Payload`. If that payload is present, it will be encoded / decoded
31+
with the configured set of `PayloadCodec`s.
32+
33+
The SDK contains a "default" `FailureConverter` that can be configured to encode error messages and stack traces.
34+
2835
```ts
2936
export interface DataConverter {
3037
payloadConverterPath?: string;
38+
failureConverterPath?: string;
3139
payloadCodecs?: PayloadCodec[];
3240
}
3341

@@ -36,6 +44,11 @@ export interface PayloadConverter {
3644
fromPayload<T>(payload: Payload): T;
3745
}
3846

47+
export interface FailureConverter {
48+
errorToFailure(err: unknown): ProtoFailure;
49+
failureToError(err: ProtoFailure): TemporalFailure;
50+
}
51+
3952
export interface PayloadCodec {
4053
encode(payloads: Payload[]): Promise<Payload[]>;
4154
decode(payloads: Payload[]): Promise<Payload[]>;
@@ -50,15 +63,22 @@ Temporal Server <--> Wire <--> `PayloadCodec` <--> `PayloadConverter` <--> User
5063

5164
`PayloadCodec` only runs in the main thread.
5265

53-
When `WorkerOptions.dataConverter.payloadConverterPath` is provided, the code at that location is loaded into the main thread and the webpack Workflow bundle.
66+
When `WorkerOptions.dataConverter.payloadConverterPath` (or `failureConverterPath`) is provided, the code at that
67+
location is loaded into the main thread and the webpack Workflow bundle.
5468

5569
`Worker.create`:
5670
_main thread_
5771

5872
- imports and validates `options.dataConverter.payloadConverterPath`
73+
- imports and validates `options.dataConverter.failureConverterPath`
5974
- passes `payloadConverterPath` to `WorkflowCodeBundler`
6075

6176
`worker-interface.ts#initRuntime`:
6277
_workflow vm_
6378

64-
- Imports `__temporal_custom_payload_converter`, which will either be the code bundled from `payloadConverterPath` or `undefined`. If it's defined, sets `state.payloadConverter`.
79+
- Imports `__temporal_custom_payload_converter`, which will either be the code bundled from `payloadConverterPath` or
80+
`undefined`. If it's defined, sets `state.payloadConverter`.
81+
- Imports `__temporal_custom_failure_converter`, which will either be the code bundled from `failureConverterPath` or
82+
`undefined`. If it's defined, sets `state.failureConverter`.
83+
84+
[encoded_attributes]: https://github.com/temporalio/api/blob/ddf07ab9933e8230309850e3c579e1ff34b03f53/temporal/api/failure/v1/message.proto#L102

packages/common/src/converter/data-converter.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import { DefaultFailureConverter, FailureConverter } from './failure-converter';
12
import { PayloadCodec } from './payload-codec';
2-
import { PayloadConverter } from './payload-converter';
3-
import { defaultPayloadConverter } from './payload-converters';
3+
import { defaultPayloadConverter, PayloadConverter } from './payload-converter';
44

55
/**
66
* When your data (arguments and return values) is sent over the wire and stored by Temporal Server, it is encoded in
@@ -29,11 +29,18 @@ import { defaultPayloadConverter } from './payload-converters';
2929
export interface DataConverter {
3030
/**
3131
* Path of a file that has a `payloadConverter` named export.
32-
* `payloadConverter` should be an instance of a class that implements {@link PayloadConverter}.
32+
* `payloadConverter` should be an object that implements {@link PayloadConverter}.
3333
* If no path is provided, {@link defaultPayloadConverter} is used.
3434
*/
3535
payloadConverterPath?: string;
3636

37+
/**
38+
* Path of a file that has a `failureConverter` named export.
39+
* `failureConverter` should be an object that implements {@link FailureConverter}.
40+
* If no path is provided, {@link defaultFailureConverter} is used.
41+
*/
42+
failureConverterPath?: string;
43+
3744
/**
3845
* An array of {@link PayloadCodec} instances.
3946
*
@@ -49,10 +56,22 @@ export interface DataConverter {
4956
*/
5057
export interface LoadedDataConverter {
5158
payloadConverter: PayloadConverter;
59+
failureConverter: FailureConverter;
5260
payloadCodecs: PayloadCodec[];
5361
}
5462

63+
/**
64+
* The default {@link FailureConverter} used by the SDK.
65+
*
66+
* Error messages and stack traces are serizalized as plain text.
67+
*/
68+
export const defaultFailureConverter: FailureConverter = new DefaultFailureConverter();
69+
70+
/**
71+
* A "loaded" data converter that uses the default set of failure and payload converters.
72+
*/
5573
export const defaultDataConverter: LoadedDataConverter = {
5674
payloadConverter: defaultPayloadConverter,
75+
failureConverter: defaultFailureConverter,
5776
payloadCodecs: [],
5877
};

0 commit comments

Comments
 (0)