@temporalio/nexus 1.17.3 → 1.17.4
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/lib/context.d.ts +6 -0
- package/lib/context.js +1 -0
- package/lib/context.js.map +1 -1
- package/package.json +7 -7
- package/src/context.ts +0 -19
- package/src/index.ts +0 -8
- package/src/link-converter.ts +3 -77
- package/src/token.ts +18 -67
- package/src/workflow-helpers.ts +11 -213
package/lib/context.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface HandlerContext {
|
|
|
15
15
|
client: Client;
|
|
16
16
|
namespace: string;
|
|
17
17
|
taskQueue: string;
|
|
18
|
+
endpoint: string;
|
|
18
19
|
}
|
|
19
20
|
/**
|
|
20
21
|
* Holds information about the current Nexus Operation Execution.
|
|
@@ -30,6 +31,11 @@ export interface OperationInfo {
|
|
|
30
31
|
* Task Queue this Nexus Operation is executing on
|
|
31
32
|
*/
|
|
32
33
|
readonly taskQueue: string;
|
|
34
|
+
/**
|
|
35
|
+
* Nexus Endpoint this Operation was routed through.
|
|
36
|
+
* Only available with server version 1.30.0 or later.
|
|
37
|
+
*/
|
|
38
|
+
readonly endpoint: string;
|
|
33
39
|
}
|
|
34
40
|
/**
|
|
35
41
|
* A logger for use in Nexus Handler scope.
|
package/lib/context.js
CHANGED
package/lib/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":";;;AAaA,8CAMC;
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@temporalio/nexus",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.4",
|
|
4
4
|
"description": "Temporal.io SDK Nexus sub-package",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "./lib/index.d.ts",
|
|
@@ -17,15 +17,15 @@
|
|
|
17
17
|
"concurrency": 1,
|
|
18
18
|
"workerThreads": false
|
|
19
19
|
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"ava": "^5.3.1"
|
|
22
|
+
},
|
|
20
23
|
"dependencies": {
|
|
21
24
|
"long": "^5.2.3",
|
|
22
25
|
"nexus-rpc": "^0.0.2",
|
|
23
|
-
"@temporalio/
|
|
24
|
-
"@temporalio/common": "1.17.
|
|
25
|
-
"@temporalio/
|
|
26
|
-
},
|
|
27
|
-
"devDependencies": {
|
|
28
|
-
"ava": "^5.3.1"
|
|
26
|
+
"@temporalio/client": "1.17.4",
|
|
27
|
+
"@temporalio/common": "1.17.4",
|
|
28
|
+
"@temporalio/proto": "1.17.4"
|
|
29
29
|
},
|
|
30
30
|
"bugs": {
|
|
31
31
|
"url": "https://github.com/temporalio/sdk-typescript/issues"
|
package/src/context.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
-
import type * as nexus from 'nexus-rpc';
|
|
3
2
|
import type { Logger, LogLevel, LogMetadata, MetricMeter } from '@temporalio/common';
|
|
4
3
|
import type { Client } from '@temporalio/client';
|
|
5
4
|
|
|
@@ -58,24 +57,6 @@ export interface OperationInfo {
|
|
|
58
57
|
readonly endpoint: string;
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
/**
|
|
62
|
-
* Context received by a {@link TemporalOperationHandler}'s start handler when a Nexus Operation is
|
|
63
|
-
* started.
|
|
64
|
-
*
|
|
65
|
-
* @experimental Nexus support in Temporal SDK is experimental.
|
|
66
|
-
*/
|
|
67
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
68
|
-
export interface TemporalStartOperationContext extends nexus.StartOperationContext {}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Context received by a {@link TemporalOperationHandler}'s cancel handler when a Nexus Operation is
|
|
72
|
-
* canceled.
|
|
73
|
-
*
|
|
74
|
-
* @experimental Nexus support in Temporal SDK is experimental.
|
|
75
|
-
*/
|
|
76
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
77
|
-
export interface TemporalCancelOperationContext extends nexus.CancelOperationContext {}
|
|
78
|
-
|
|
79
60
|
// Basic APIs //////////////////////////////////////////////////////////////////////////////////////
|
|
80
61
|
|
|
81
62
|
/**
|
package/src/index.ts
CHANGED
|
@@ -11,18 +11,10 @@ export {
|
|
|
11
11
|
metricMeter,
|
|
12
12
|
operationInfo,
|
|
13
13
|
type OperationInfo,
|
|
14
|
-
type TemporalCancelOperationContext,
|
|
15
|
-
type TemporalStartOperationContext,
|
|
16
14
|
} from './context';
|
|
17
15
|
|
|
18
16
|
export {
|
|
19
17
|
startWorkflow,
|
|
20
|
-
type CancelWorkflowRunOptions,
|
|
21
|
-
type TemporalOperationHandlerOptions,
|
|
22
|
-
TemporalOperationHandler,
|
|
23
|
-
TemporalOperationResult,
|
|
24
|
-
type TemporalNexusClient,
|
|
25
|
-
type TemporalOperationStartHandler,
|
|
26
18
|
WorkflowHandle,
|
|
27
19
|
WorkflowRunOperationHandler,
|
|
28
20
|
WorkflowRunOperationStartHandler,
|
package/src/link-converter.ts
CHANGED
|
@@ -3,9 +3,7 @@ import type { Link as NexusLink } from 'nexus-rpc';
|
|
|
3
3
|
import { temporal } from '@temporalio/proto';
|
|
4
4
|
|
|
5
5
|
const { EventType } = temporal.api.enums.v1;
|
|
6
|
-
type TemporalLink = temporal.api.common.v1.ILink;
|
|
7
6
|
type WorkflowEventLink = temporal.api.common.v1.Link.IWorkflowEvent;
|
|
8
|
-
type NexusOperationLink = temporal.api.common.v1.Link.INexusOperation;
|
|
9
7
|
type EventReference = temporal.api.common.v1.Link.WorkflowEvent.IEventReference;
|
|
10
8
|
type RequestIdReference = temporal.api.common.v1.Link.WorkflowEvent.IRequestIdReference;
|
|
11
9
|
|
|
@@ -13,46 +11,12 @@ const LINK_EVENT_ID_PARAM = 'eventID';
|
|
|
13
11
|
const LINK_EVENT_TYPE_PARAM = 'eventType';
|
|
14
12
|
const LINK_REQUEST_ID_PARAM = 'requestID';
|
|
15
13
|
const LINK_REFERENCE_TYPE_KEY = 'referenceType';
|
|
16
|
-
const LINK_RUN_ID_KEY = 'runID';
|
|
17
14
|
|
|
18
15
|
const EVENT_REFERENCE_TYPE = 'EventReference';
|
|
19
16
|
const REQUEST_ID_REFERENCE_TYPE = 'RequestIdReference';
|
|
20
17
|
|
|
21
18
|
// fullName isn't part of the generated typed unfortunately.
|
|
22
19
|
const WORKFLOW_EVENT_TYPE: string = (temporal.api.common.v1.Link.WorkflowEvent as any).fullName.slice(1);
|
|
23
|
-
const NEXUS_OPERATION_TYPE: string = (temporal.api.common.v1.Link.NexusOperation as any).fullName.slice(1);
|
|
24
|
-
|
|
25
|
-
export function convertTemporalLinkToNexusLink(link: TemporalLink): NexusLink {
|
|
26
|
-
if (link.workflowEvent != null) {
|
|
27
|
-
return convertWorkflowEventLinkToNexusLink(link.workflowEvent);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (link.nexusOperation != null) {
|
|
31
|
-
return convertNexusOperationLinkToNexusLink(link.nexusOperation);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
throw new TypeError('Invalid Temporal link: unknown variant');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function convertNexusLinkToTemporalLink(link: NexusLink): TemporalLink {
|
|
38
|
-
if (link.url.protocol !== 'temporal:') {
|
|
39
|
-
throw new TypeError(`Invalid URL scheme: ${link.url}, expected 'temporal:', got '${link.url.protocol}'`);
|
|
40
|
-
}
|
|
41
|
-
switch (link.type) {
|
|
42
|
-
case WORKFLOW_EVENT_TYPE:
|
|
43
|
-
return {
|
|
44
|
-
workflowEvent: convertNexusLinkToWorkflowEventLink(link),
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
case NEXUS_OPERATION_TYPE:
|
|
48
|
-
return {
|
|
49
|
-
nexusOperation: convertNexusLinkToNexusOperationLink(link),
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
default:
|
|
53
|
-
throw new TypeError(`Unknown link type: ${link.type}`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
20
|
|
|
57
21
|
export function convertWorkflowEventLinkToNexusLink(we: WorkflowEventLink): NexusLink {
|
|
58
22
|
if (!we.namespace || !we.workflowId || !we.runId) {
|
|
@@ -76,30 +40,11 @@ export function convertWorkflowEventLinkToNexusLink(we: WorkflowEventLink): Nexu
|
|
|
76
40
|
};
|
|
77
41
|
}
|
|
78
42
|
|
|
79
|
-
export function
|
|
80
|
-
if (
|
|
81
|
-
throw new TypeError(
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const url = new URL(
|
|
85
|
-
`temporal:///namespaces/${encodeURIComponent(opLink.namespace)}/nexus-operations/${encodeURIComponent(
|
|
86
|
-
opLink.operationId
|
|
87
|
-
)}`
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
if (opLink.runId != null) {
|
|
91
|
-
const searchParams = new URLSearchParams();
|
|
92
|
-
searchParams.set(LINK_RUN_ID_KEY, opLink.runId);
|
|
93
|
-
url.search = searchParams.toString();
|
|
43
|
+
export function convertNexusLinkToWorkflowEventLink(link: NexusLink): WorkflowEventLink {
|
|
44
|
+
if (link.url.protocol !== 'temporal:') {
|
|
45
|
+
throw new TypeError(`Invalid URL scheme: ${link.url}, expected 'temporal:', got '${link.url.protocol}'`);
|
|
94
46
|
}
|
|
95
47
|
|
|
96
|
-
return {
|
|
97
|
-
url,
|
|
98
|
-
type: NEXUS_OPERATION_TYPE,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function convertNexusLinkToWorkflowEventLink(link: NexusLink): WorkflowEventLink {
|
|
103
48
|
// /namespaces/:namespace/workflows/:workflowId/:runId/history
|
|
104
49
|
const parts = link.url.pathname.split('/');
|
|
105
50
|
if (parts.length !== 7 || parts[1] !== 'namespaces' || parts[3] !== 'workflows' || parts[6] !== 'history') {
|
|
@@ -131,25 +76,6 @@ export function convertNexusLinkToWorkflowEventLink(link: NexusLink): WorkflowEv
|
|
|
131
76
|
return workflowEventLink;
|
|
132
77
|
}
|
|
133
78
|
|
|
134
|
-
function convertNexusLinkToNexusOperationLink(link: NexusLink): NexusOperationLink {
|
|
135
|
-
// /namespaces/:namespace/nexus-operations/:operationId?runId=:runId
|
|
136
|
-
const parts = link.url.pathname.split('/');
|
|
137
|
-
if (parts.length !== 5 || parts[1] !== 'namespaces' || parts[3] !== 'nexus-operations') {
|
|
138
|
-
throw new TypeError(`Invalid URL path: ${link.url}`);
|
|
139
|
-
}
|
|
140
|
-
const namespace = decodeURIComponent(parts[2]!);
|
|
141
|
-
const operationId = decodeURIComponent(parts[4]!);
|
|
142
|
-
|
|
143
|
-
const query = link.url.searchParams;
|
|
144
|
-
const runId = query.get(LINK_RUN_ID_KEY);
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
namespace,
|
|
148
|
-
operationId,
|
|
149
|
-
runId,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
79
|
function convertLinkWorkflowEventEventReferenceToURLQuery(eventRef: EventReference): string {
|
|
154
80
|
const params = new URLSearchParams();
|
|
155
81
|
params.set(LINK_REFERENCE_TYPE_KEY, EVENT_REFERENCE_TYPE);
|
package/src/token.ts
CHANGED
|
@@ -1,57 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* OperationTokenType is used to identify the type of Operation token.
|
|
3
|
+
* Currently, we only have one type of Operation token: WorkflowRun.
|
|
3
4
|
*
|
|
4
5
|
* @internal
|
|
5
6
|
* @hidden
|
|
6
7
|
*/
|
|
7
|
-
export interface
|
|
8
|
+
export interface WorkflowRunOperationToken {
|
|
8
9
|
/**
|
|
9
|
-
* Version of the token, by default we assume we're on version
|
|
10
|
+
* Version of the token, by default we assume we're on version 1, this field is not emitted as part of the output,
|
|
10
11
|
* it's only used to reject newer token versions on load.
|
|
11
12
|
*/
|
|
12
13
|
v?: number;
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
|
-
* Type of the Operation.
|
|
16
|
+
* Type of the Operation. Must be OPERATION_TOKEN_TYPE_WORKFLOW_RUN.
|
|
16
17
|
*/
|
|
17
18
|
t: OperationTokenType;
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
|
-
* Namespace of the
|
|
21
|
+
* Namespace of the workflow.
|
|
21
22
|
*/
|
|
22
23
|
ns: string;
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* ID of the workflow.
|
|
26
27
|
*/
|
|
27
|
-
wid?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* An OperationToken that identifies a WorkflowRun operation.
|
|
32
|
-
*
|
|
33
|
-
* @internal
|
|
34
|
-
* @hidden
|
|
35
|
-
*/
|
|
36
|
-
export interface WorkflowRunOperationToken extends OperationToken {
|
|
37
|
-
t: typeof OperationTokenType.WORKFLOW_RUN;
|
|
38
28
|
wid: string;
|
|
39
29
|
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* OperationTokenType is used to identify the type of Operation token.
|
|
43
|
-
* Currently, we only have one type of Operation token: WorkflowRun.
|
|
44
|
-
*
|
|
45
|
-
* @internal
|
|
46
|
-
* @hidden
|
|
47
|
-
*/
|
|
48
|
-
export type OperationTokenType = (typeof OperationTokenType)[keyof typeof OperationTokenType];
|
|
30
|
+
type OperationTokenType = (typeof OperationTokenType)[keyof typeof OperationTokenType];
|
|
49
31
|
|
|
50
32
|
/**
|
|
51
33
|
* @internal
|
|
52
34
|
* @hidden
|
|
53
35
|
*/
|
|
54
|
-
|
|
36
|
+
const OperationTokenType = {
|
|
55
37
|
WORKFLOW_RUN: 1,
|
|
56
38
|
} as const;
|
|
57
39
|
|
|
@@ -68,11 +50,11 @@ export function generateWorkflowRunOperationToken(namespace: string, workflowId:
|
|
|
68
50
|
}
|
|
69
51
|
|
|
70
52
|
/**
|
|
71
|
-
* Load and validate
|
|
53
|
+
* Load and validate a workflow run Operation token.
|
|
72
54
|
*/
|
|
73
|
-
export function
|
|
55
|
+
export function loadWorkflowRunOperationToken(data: string): WorkflowRunOperationToken {
|
|
74
56
|
if (!data) {
|
|
75
|
-
throw new TypeError('invalid
|
|
57
|
+
throw new TypeError('invalid workflow run token: token is empty');
|
|
76
58
|
}
|
|
77
59
|
|
|
78
60
|
let decoded: string;
|
|
@@ -82,55 +64,24 @@ export function loadOperationToken(data: string): OperationToken {
|
|
|
82
64
|
throw new TypeError('failed to decode token', { cause: err });
|
|
83
65
|
}
|
|
84
66
|
|
|
85
|
-
let token:
|
|
67
|
+
let token: WorkflowRunOperationToken;
|
|
86
68
|
try {
|
|
87
69
|
token = JSON.parse(decoded);
|
|
88
70
|
} catch (err) {
|
|
89
|
-
throw new TypeError('failed to unmarshal Operation token', { cause: err });
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (typeof token !== 'object' || token == null) {
|
|
93
|
-
throw new TypeError(`invalid operation token: expected object, got ${typeof token}`);
|
|
94
|
-
}
|
|
95
|
-
if (token.v !== undefined && token.v !== 0) {
|
|
96
|
-
throw new TypeError('invalid operation token: "v" field should not be present');
|
|
97
|
-
}
|
|
98
|
-
if (typeof token.t !== 'number') {
|
|
99
|
-
throw new TypeError(`invalid operation token: expected token type to be a number, got ${typeof token.t}`);
|
|
100
|
-
}
|
|
101
|
-
if (!isOperationTokenType(token.t)) {
|
|
102
|
-
throw new TypeError(`invalid operation token: unknown token type: ${token.t}`);
|
|
103
|
-
}
|
|
104
|
-
if (typeof token.ns !== 'string') {
|
|
105
|
-
throw new TypeError(`invalid operation token: expected namespace to be a string, got ${typeof token.ns}`);
|
|
71
|
+
throw new TypeError('failed to unmarshal workflow run Operation token', { cause: err });
|
|
106
72
|
}
|
|
107
73
|
|
|
108
|
-
return token;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Load and validate a workflow run Operation token.
|
|
113
|
-
*/
|
|
114
|
-
export function loadWorkflowRunOperationToken(data: string): WorkflowRunOperationToken {
|
|
115
|
-
const token = loadOperationToken(data);
|
|
116
|
-
assertWorkflowRunOperationToken(token);
|
|
117
|
-
return token;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Assert that an OperationToken identifies a workflow run.
|
|
122
|
-
*/
|
|
123
|
-
export function assertWorkflowRunOperationToken(token: OperationToken): asserts token is WorkflowRunOperationToken {
|
|
124
74
|
if (token.t !== OperationTokenType.WORKFLOW_RUN) {
|
|
125
75
|
throw new TypeError(`invalid workflow token type: ${token.t}, expected: ${OperationTokenType.WORKFLOW_RUN}`);
|
|
126
76
|
}
|
|
127
|
-
if (
|
|
77
|
+
if (token.v !== undefined && token.v !== 0) {
|
|
78
|
+
throw new TypeError('invalid workflow run token: "v" field should not be present');
|
|
79
|
+
}
|
|
80
|
+
if (!token.wid) {
|
|
128
81
|
throw new TypeError('invalid workflow run token: missing workflow ID (wid)');
|
|
129
82
|
}
|
|
130
|
-
}
|
|
131
83
|
|
|
132
|
-
|
|
133
|
-
return Object.values(OperationTokenType).includes(value as OperationTokenType);
|
|
84
|
+
return token;
|
|
134
85
|
}
|
|
135
86
|
|
|
136
87
|
// Exported for use in tests.
|
package/src/workflow-helpers.ts
CHANGED
|
@@ -1,25 +1,13 @@
|
|
|
1
1
|
import * as nexus from 'nexus-rpc';
|
|
2
2
|
import type { Workflow, WorkflowResultType } from '@temporalio/common';
|
|
3
3
|
import type { Replace } from '@temporalio/common/lib/type-helpers';
|
|
4
|
-
import type {
|
|
4
|
+
import type { WorkflowStartOptions as ClientWorkflowStartOptions } from '@temporalio/client';
|
|
5
5
|
import { type temporal } from '@temporalio/proto';
|
|
6
6
|
import type { InternalWorkflowStartOptions } from '@temporalio/client/lib/internal';
|
|
7
7
|
import { InternalWorkflowStartOptionsSymbol } from '@temporalio/client/lib/internal';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
generateWorkflowRunOperationToken,
|
|
12
|
-
loadOperationToken,
|
|
13
|
-
loadWorkflowRunOperationToken,
|
|
14
|
-
OperationTokenType,
|
|
15
|
-
} from './token';
|
|
16
|
-
import {
|
|
17
|
-
getClient,
|
|
18
|
-
getHandlerContext,
|
|
19
|
-
log,
|
|
20
|
-
type TemporalCancelOperationContext,
|
|
21
|
-
type TemporalStartOperationContext,
|
|
22
|
-
} from './context';
|
|
8
|
+
import { generateWorkflowRunOperationToken, loadWorkflowRunOperationToken } from './token';
|
|
9
|
+
import { convertNexusLinkToWorkflowEventLink, convertWorkflowEventLinkToNexusLink } from './link-converter';
|
|
10
|
+
import { getClient, getHandlerContext, log } from './context';
|
|
23
11
|
|
|
24
12
|
declare const isNexusWorkflowHandle: unique symbol;
|
|
25
13
|
declare const workflowResultType: unique symbol;
|
|
@@ -89,7 +77,9 @@ export async function startWorkflow<T extends Workflow>(
|
|
|
89
77
|
if (ctx.inboundLinks?.length > 0) {
|
|
90
78
|
for (const l of ctx.inboundLinks) {
|
|
91
79
|
try {
|
|
92
|
-
links.push(
|
|
80
|
+
links.push({
|
|
81
|
+
workflowEvent: convertNexusLinkToWorkflowEventLink(l),
|
|
82
|
+
});
|
|
93
83
|
} catch (error) {
|
|
94
84
|
log.warn('failed to convert Nexus link to Workflow event link', { error });
|
|
95
85
|
}
|
|
@@ -106,17 +96,10 @@ export async function startWorkflow<T extends Workflow>(
|
|
|
106
96
|
attachRequestId: true,
|
|
107
97
|
};
|
|
108
98
|
|
|
109
|
-
// Add nexus-operation-token header to solve for race between Workflow completion
|
|
110
|
-
// and Nexus Operation start recording
|
|
111
|
-
const callbackHeaders = {
|
|
112
|
-
...ctx.callbackHeaders,
|
|
113
|
-
'nexus-operation-token': generateWorkflowRunOperationToken(client.options.namespace, workflowOptions.workflowId),
|
|
114
|
-
};
|
|
115
|
-
|
|
116
99
|
if (ctx.callbackUrl) {
|
|
117
100
|
internalOptions.completionCallbacks = [
|
|
118
101
|
{
|
|
119
|
-
nexus: { url: ctx.callbackUrl, header: callbackHeaders },
|
|
102
|
+
nexus: { url: ctx.callbackUrl, header: ctx.callbackHeaders },
|
|
120
103
|
links, // pass in links here as well for older servers, newer servers dedupe them.
|
|
121
104
|
},
|
|
122
105
|
];
|
|
@@ -130,11 +113,11 @@ export async function startWorkflow<T extends Workflow>(
|
|
|
130
113
|
};
|
|
131
114
|
|
|
132
115
|
const handle = await client.workflow.start(workflowTypeOrFunc, startOptions);
|
|
133
|
-
if (internalOptions.backLink != null) {
|
|
116
|
+
if (internalOptions.backLink?.workflowEvent != null) {
|
|
134
117
|
try {
|
|
135
|
-
ctx.outboundLinks.push(
|
|
118
|
+
ctx.outboundLinks.push(convertWorkflowEventLinkToNexusLink(internalOptions.backLink.workflowEvent));
|
|
136
119
|
} catch (error) {
|
|
137
|
-
log.warn('failed to convert
|
|
120
|
+
log.warn('failed to convert Workflow event link to Nexus link', { error });
|
|
138
121
|
}
|
|
139
122
|
}
|
|
140
123
|
|
|
@@ -173,188 +156,3 @@ export class WorkflowRunOperationHandler<I, O> implements nexus.OperationHandler
|
|
|
173
156
|
await getClient().workflow.getHandle(decoded.wid).cancel();
|
|
174
157
|
}
|
|
175
158
|
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Module-private brand and payload key for {@link TemporalOperationResult}.
|
|
179
|
-
*/
|
|
180
|
-
const operationResult: unique symbol = Symbol('temporal_nexus_TemporalOperationResult');
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* A result produced by a {@link TemporalOperationHandler}. Construct via
|
|
184
|
-
* {@link TemporalOperationResult.sync} or {@link TemporalOperationResult.async}.
|
|
185
|
-
|
|
186
|
-
* @experimental Nexus support in Temporal SDK is experimental.
|
|
187
|
-
*/
|
|
188
|
-
export interface TemporalOperationResult<T> {
|
|
189
|
-
readonly [operationResult]: nexus.HandlerStartOperationResult<T>;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export const TemporalOperationResult = {
|
|
193
|
-
sync<T>(value: T): TemporalOperationResult<T> {
|
|
194
|
-
return {
|
|
195
|
-
[operationResult]: nexus.HandlerStartOperationResult.sync(value),
|
|
196
|
-
};
|
|
197
|
-
},
|
|
198
|
-
|
|
199
|
-
async<T = unknown>(token: string): TemporalOperationResult<T> {
|
|
200
|
-
return {
|
|
201
|
-
[operationResult]: nexus.HandlerStartOperationResult.async(token),
|
|
202
|
-
};
|
|
203
|
-
},
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* A Nexus-aware Temporal Client for use inside {@link TemporalOperationHandler} implementations.
|
|
208
|
-
*
|
|
209
|
-
* @experimental Nexus support in Temporal SDK is experimental.
|
|
210
|
-
*/
|
|
211
|
-
export interface TemporalNexusClient {
|
|
212
|
-
/**
|
|
213
|
-
* The Temporal Client for the active Nexus Operation.
|
|
214
|
-
*
|
|
215
|
-
* @experimental Nexus support in Temporal SDK is experimental.
|
|
216
|
-
*/
|
|
217
|
-
readonly client: Client;
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Starts a workflow run as the asynchronous backing operation for the current Nexus Operation.
|
|
221
|
-
*
|
|
222
|
-
* @experimental Nexus support in Temporal SDK is experimental.
|
|
223
|
-
*/
|
|
224
|
-
startWorkflow<T extends Workflow>(
|
|
225
|
-
workflowTypeOrFunc: string | T,
|
|
226
|
-
workflowOptions: WorkflowStartOptions<T>
|
|
227
|
-
): Promise<TemporalOperationResult<WorkflowResultType<T>>>;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
class TemporalNexusClientImpl implements TemporalNexusClient {
|
|
231
|
-
private asyncOperationStarted = false;
|
|
232
|
-
|
|
233
|
-
constructor(private readonly startOperationContext: TemporalStartOperationContext) {}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* The Temporal Client for the active Nexus Operation.
|
|
237
|
-
*
|
|
238
|
-
* @experimental Nexus support in Temporal SDK is experimental.
|
|
239
|
-
*/
|
|
240
|
-
public get client(): Client {
|
|
241
|
-
return getClient();
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Starts a workflow run as the asynchronous backing operation for the current Nexus Operation.
|
|
246
|
-
*
|
|
247
|
-
* @experimental Nexus support in Temporal SDK is experimental.
|
|
248
|
-
*/
|
|
249
|
-
public async startWorkflow<T extends Workflow>(
|
|
250
|
-
workflowTypeOrFunc: string | T,
|
|
251
|
-
workflowOptions: WorkflowStartOptions<T>
|
|
252
|
-
): Promise<TemporalOperationResult<WorkflowResultType<T>>> {
|
|
253
|
-
return await this.withAsyncOperationStartReservation(async () => {
|
|
254
|
-
const handle = await startWorkflow(this.startOperationContext, workflowTypeOrFunc, workflowOptions);
|
|
255
|
-
const { namespace } = getHandlerContext();
|
|
256
|
-
return TemporalOperationResult.async(generateWorkflowRunOperationToken(namespace, handle.workflowId));
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
private async withAsyncOperationStartReservation<T>(fn: () => Promise<T>): Promise<T> {
|
|
261
|
-
if (this.asyncOperationStarted) {
|
|
262
|
-
throw new nexus.HandlerError(
|
|
263
|
-
'BAD_REQUEST',
|
|
264
|
-
'Only one async operation can be started per operation handler invocation. Use TemporalNexusClient.client for additional workflow interactions'
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
this.asyncOperationStarted = true;
|
|
269
|
-
try {
|
|
270
|
-
return await fn();
|
|
271
|
-
} catch (err) {
|
|
272
|
-
this.asyncOperationStarted = false;
|
|
273
|
-
throw err;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* A handler function for the {@link TemporalOperationHandler} constructor.
|
|
280
|
-
*
|
|
281
|
-
* @experimental Nexus support in Temporal SDK is experimental.
|
|
282
|
-
*/
|
|
283
|
-
export type TemporalOperationStartHandler<I, O> = (
|
|
284
|
-
ctx: TemporalStartOperationContext,
|
|
285
|
-
client: TemporalNexusClient,
|
|
286
|
-
input: I
|
|
287
|
-
) => Promise<TemporalOperationResult<O>>;
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Options passed to a {@link TemporalOperationHandlerOptions.cancelWorkflowRun} handler describing
|
|
291
|
-
* the workflow run to cancel.
|
|
292
|
-
*
|
|
293
|
-
* @experimental Nexus support in Temporal SDK is experimental.
|
|
294
|
-
*/
|
|
295
|
-
export interface CancelWorkflowRunOptions {
|
|
296
|
-
/**
|
|
297
|
-
* The ID of the workflow backing the Nexus Operation that is being canceled.
|
|
298
|
-
*/
|
|
299
|
-
readonly workflowId: string;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Options for customizing a {@link TemporalOperationHandler}.
|
|
304
|
-
*
|
|
305
|
-
* @experimental Nexus support in Temporal SDK is experimental.
|
|
306
|
-
*/
|
|
307
|
-
export interface TemporalOperationHandlerOptions {
|
|
308
|
-
cancelWorkflowRun?: (ctx: TemporalCancelOperationContext, options: CancelWorkflowRunOptions) => Promise<void>;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* A Nexus Operation implementation for operations that interact with Temporal.
|
|
313
|
-
*
|
|
314
|
-
* @experimental Nexus support in Temporal SDK is experimental.
|
|
315
|
-
*/
|
|
316
|
-
export class TemporalOperationHandler<I, O> implements nexus.OperationHandler<I, O> {
|
|
317
|
-
private readonly startHandler: TemporalOperationStartHandler<I, O>;
|
|
318
|
-
private readonly cancelWorkflowRunHandler: NonNullable<TemporalOperationHandlerOptions['cancelWorkflowRun']>;
|
|
319
|
-
|
|
320
|
-
constructor(options: { start: TemporalOperationStartHandler<I, O> } & TemporalOperationHandlerOptions) {
|
|
321
|
-
this.startHandler = options.start;
|
|
322
|
-
this.cancelWorkflowRunHandler = options.cancelWorkflowRun ?? defaultCancelWorkflowRun;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
async start(ctx: nexus.StartOperationContext, input: I): Promise<nexus.HandlerStartOperationResult<O>> {
|
|
326
|
-
const result = await this.startHandler(ctx, new TemporalNexusClientImpl(ctx), input);
|
|
327
|
-
return result[operationResult];
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
async cancel(ctx: nexus.CancelOperationContext, token: string): Promise<void> {
|
|
331
|
-
let opToken;
|
|
332
|
-
try {
|
|
333
|
-
opToken = loadOperationToken(token);
|
|
334
|
-
} catch (err) {
|
|
335
|
-
throw new nexus.HandlerError(nexus.HandlerErrorType.BAD_REQUEST, 'invalid operation token', { cause: err });
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
switch (opToken.t) {
|
|
339
|
-
case OperationTokenType.WORKFLOW_RUN:
|
|
340
|
-
try {
|
|
341
|
-
assertWorkflowRunOperationToken(opToken);
|
|
342
|
-
} catch (err) {
|
|
343
|
-
throw new nexus.HandlerError(nexus.HandlerErrorType.BAD_REQUEST, 'invalid workflow run operation token', {
|
|
344
|
-
cause: err,
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
await this.cancelWorkflowRunHandler(ctx, { workflowId: opToken.wid });
|
|
348
|
-
return;
|
|
349
|
-
default:
|
|
350
|
-
throw new nexus.HandlerError(
|
|
351
|
-
nexus.HandlerErrorType.BAD_REQUEST,
|
|
352
|
-
`Unsupported operation token type: ${opToken.t}`
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
async function defaultCancelWorkflowRun(_ctx: TemporalCancelOperationContext, options: CancelWorkflowRunOptions) {
|
|
359
|
-
await getClient().workflow.getHandle(options.workflowId).cancel();
|
|
360
|
-
}
|