Skip to content

Commit 24c1359

Browse files
committed
Nexus handler near complete implementation
- This PR is blocked on publishing an initial version of the `nexus-rpc` package. - There is a TODO to figure out HandlerError and OperationError message rehydration, pending discussion. - Interceptors not yet implemented. - `WorkflowRunOperation` and `getClient()` not implemented for the `@temporalio/nexus` package. - Tests use the HTTP API directly in lieu of a workflow caller or strongly typed client, we can refactor those later.
1 parent 9facf85 commit 24c1359

27 files changed

+1745
-65
lines changed

package-lock.json

Lines changed: 88 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"@temporalio/common": "file:packages/common",
4444
"@temporalio/create": "file:packages/create-project",
4545
"@temporalio/interceptors-opentelemetry": "file:packages/interceptors-opentelemetry",
46+
"@temporalio/nexus": "file:packages/nexus",
4647
"@temporalio/nyc-test-coverage": "file:packages/nyc-test-coverage",
4748
"@temporalio/proto": "file:packages/proto",
4849
"@temporalio/test": "file:packages/test",

packages/common/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@temporalio/proto": "file:../proto",
1616
"long": "^5.2.3",
1717
"ms": "^3.0.0-canary.1",
18+
"nexus-rpc": "file:../../../nexus-sdk-typescript",
1819
"proto3-json-serializer": "^2.0.0"
1920
},
2021
"devDependencies": {

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

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import * as nexus from 'nexus-rpc';
2+
import Long from 'long';
3+
import type { temporal } from '@temporalio/proto';
14
import {
25
ActivityFailure,
36
ApplicationFailure,
@@ -8,16 +11,43 @@ import {
811
encodeRetryState,
912
encodeTimeoutType,
1013
FAILURE_SOURCE,
14+
NexusOperationFailure,
1115
ProtoFailure,
1216
ServerFailure,
1317
TemporalFailure,
1418
TerminatedFailure,
1519
TimeoutFailure,
1620
} from '../failure';
21+
import { makeProtoEnumConverters } from '../internal-workflow';
1722
import { isError } from '../type-helpers';
1823
import { msOptionalToTs } from '../time';
1924
import { arrayFromPayloads, fromPayloadsAtIndex, PayloadConverter, toPayloads } from './payload-converter';
2025

26+
// Can't import enums into the workflow sandbox, use this helper type and enum converter instead.
27+
const NexusHandlerErrorRetryBehavior = {
28+
RETRYABLE: 'RETRYABLE',
29+
NON_RETRYABLE: 'NON_RETRYABLE',
30+
} as const;
31+
32+
type NexusHandlerErrorRetryBehavior =
33+
(typeof NexusHandlerErrorRetryBehavior)[keyof typeof NexusHandlerErrorRetryBehavior];
34+
35+
const [encodeNexusHandlerErrorRetryBehavior, decodeNexusHandlerErrorRetryBehavior] =
36+
makeProtoEnumConverters<
37+
temporal.api.enums.v1.NexusHandlerErrorRetryBehavior,
38+
typeof temporal.api.enums.v1.NexusHandlerErrorRetryBehavior,
39+
keyof typeof temporal.api.enums.v1.NexusHandlerErrorRetryBehavior,
40+
typeof NexusHandlerErrorRetryBehavior,
41+
'NEXUS_HANDLER_ERROR_RETRY_BEHAVIOR_'
42+
>(
43+
{
44+
UNSPECIFIED: 0,
45+
[NexusHandlerErrorRetryBehavior.RETRYABLE]: 1,
46+
[NexusHandlerErrorRetryBehavior.NON_RETRYABLE]: 2,
47+
} as const,
48+
'NEXUS_HANDLER_ERROR_RETRY_BEHAVIOR_'
49+
);
50+
2151
function combineRegExp(...regexps: RegExp[]): RegExp {
2252
return new RegExp(regexps.map((x) => `(?:${x.source})`).join('|'));
2353
}
@@ -28,6 +58,8 @@ function combineRegExp(...regexps: RegExp[]): RegExp {
2858
const CUTOFF_STACK_PATTERNS = combineRegExp(
2959
/** Activity execution */
3060
/\s+at Activity\.execute \(.*[\\/]worker[\\/](?:src|lib)[\\/]activity\.[jt]s:\d+:\d+\)/,
61+
/** Nexus execution */
62+
/\s+at NexusHandler\.invokeUserCode \(.*[\\/]worker[\\/](?:src|lib)[\\/]nexus\.[jt]s:\d+:\d+\)/,
3163
/** Workflow activation */
3264
/\s+at Activator\.\S+NextHandler \(.*[\\/]workflow[\\/](?:src|lib)[\\/]internals\.[jt]s:\d+:\d+\)/,
3365
/** Workflow run anything in context */
@@ -120,7 +152,7 @@ export class DefaultFailureConverter implements FailureConverter {
120152
*
121153
* Does not set common properties, that is done in {@link failureToError}.
122154
*/
123-
failureToErrorInner(failure: ProtoFailure, payloadConverter: PayloadConverter): TemporalFailure {
155+
failureToErrorInner(failure: ProtoFailure, payloadConverter: PayloadConverter): Error {
124156
if (failure.applicationFailureInfo) {
125157
return new ApplicationFailure(
126158
failure.message ?? undefined,
@@ -192,6 +224,38 @@ export class DefaultFailureConverter implements FailureConverter {
192224
this.optionalFailureToOptionalError(failure.cause, payloadConverter)
193225
);
194226
}
227+
if (failure.nexusHandlerFailureInfo) {
228+
if (failure.cause == null) {
229+
throw new TypeError('Missing failure cause on nexusHandlerFailureInfo');
230+
}
231+
let retryable: boolean | undefined = undefined;
232+
const retryBehavior = decodeNexusHandlerErrorRetryBehavior(failure.nexusHandlerFailureInfo.retryBehavior);
233+
switch (retryBehavior) {
234+
case 'RETRYABLE':
235+
retryable = true;
236+
break;
237+
case 'NON_RETRYABLE':
238+
retryable = false;
239+
break;
240+
}
241+
242+
return new nexus.HandlerError({
243+
type: (failure.nexusHandlerFailureInfo.type as nexus.HandlerErrorType) ?? 'INTERNAL',
244+
cause: this.failureToError(failure.cause, payloadConverter),
245+
retryable,
246+
});
247+
}
248+
if (failure.nexusOperationExecutionFailureInfo) {
249+
return new NexusOperationFailure(
250+
failure.nexusOperationExecutionFailureInfo.scheduledEventId?.toNumber(),
251+
// We assume these will always be set or gracefully set to empty strings.
252+
failure.nexusOperationExecutionFailureInfo.endpoint ?? '',
253+
failure.nexusOperationExecutionFailureInfo.service ?? '',
254+
failure.nexusOperationExecutionFailureInfo.operation ?? '',
255+
failure.nexusOperationExecutionFailureInfo.operationToken ?? undefined,
256+
this.optionalFailureToOptionalError(failure.cause, payloadConverter)
257+
);
258+
}
195259
return new TemporalFailure(
196260
failure.message ?? undefined,
197261
this.optionalFailureToOptionalError(failure.cause, payloadConverter)
@@ -216,7 +280,9 @@ export class DefaultFailureConverter implements FailureConverter {
216280
}
217281
const err = this.failureToErrorInner(failure, payloadConverter);
218282
err.stack = failure.stackTrace ?? '';
219-
err.failure = failure;
283+
if (err instanceof TemporalFailure) {
284+
err.failure = failure;
285+
}
220286
return err;
221287
}
222288

@@ -232,8 +298,8 @@ export class DefaultFailureConverter implements FailureConverter {
232298
}
233299

234300
errorToFailureInner(err: unknown, payloadConverter: PayloadConverter): ProtoFailure {
235-
if (err instanceof TemporalFailure) {
236-
if (err.failure) return err.failure;
301+
if (err instanceof TemporalFailure || err instanceof nexus.HandlerError) {
302+
if (err instanceof TemporalFailure && err.failure) return err.failure;
237303
const base = {
238304
message: err.message,
239305
stackTrace: cutoffStackTrace(err.stack),
@@ -310,6 +376,34 @@ export class DefaultFailureConverter implements FailureConverter {
310376
terminatedFailureInfo: {},
311377
};
312378
}
379+
if (err instanceof nexus.HandlerError) {
380+
let retryBehavior: temporal.api.enums.v1.NexusHandlerErrorRetryBehavior | undefined = undefined;
381+
if (err.retryable === true) {
382+
retryBehavior = encodeNexusHandlerErrorRetryBehavior("RETRYABLE");
383+
} else if (err.retryable === false) {
384+
retryBehavior = encodeNexusHandlerErrorRetryBehavior("NON_RETRYABLE");
385+
}
386+
387+
return {
388+
...base,
389+
nexusHandlerFailureInfo: {
390+
type: err.type,
391+
retryBehavior,
392+
},
393+
};
394+
}
395+
if (err instanceof NexusOperationFailure) {
396+
return {
397+
...base,
398+
nexusOperationExecutionFailureInfo: {
399+
scheduledEventId: err.scheduledEventId ? Long.fromNumber(err.scheduledEventId) : undefined,
400+
endpoint: err.endpoint,
401+
service: err.service,
402+
operation: err.operation,
403+
operationToken: err.operationToken,
404+
},
405+
};
406+
}
313407
// Just a TemporalFailure
314408
return base;
315409
}

0 commit comments

Comments
 (0)