@temporalio/nexus 1.13.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 ADDED
@@ -0,0 +1,23 @@
1
+ Temporal TypeScript SDK
2
+
3
+ MIT License
4
+
5
+ Copyright (c) 2021 Temporal Technologies Inc. All Rights Reserved
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,8 @@
1
+ # `@temporalio/nexus`
2
+
3
+ [![NPM](https://img.shields.io/npm/v/@temporalio/nexus?style=for-the-badge)](https://www.npmjs.com/package/@temporalio/nexus)
4
+
5
+ Part of [Temporal](https://temporal.io)'s [TypeScript SDK](https://docs.temporal.io/typescript/introduction/).
6
+
7
+ - [API reference](https://typescript.temporal.io/api/namespaces/nexus)
8
+ - [Sample projects](https://github.com/temporalio/samples-typescript)
@@ -0,0 +1,48 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { Logger, MetricMeter } from '@temporalio/common';
3
+ import { Client } from '@temporalio/client';
4
+ export declare const asyncLocalStorage: AsyncLocalStorage<HandlerContext>;
5
+ export declare function getHandlerContext(): HandlerContext;
6
+ /**
7
+ * Context used internally in the SDK to propagate information from the worker to the Temporal Nexus helpers.
8
+ *
9
+ * @internal
10
+ * @hidden
11
+ */
12
+ export interface HandlerContext {
13
+ log: Logger;
14
+ metrics: MetricMeter;
15
+ client: Client;
16
+ namespace: string;
17
+ taskQueue: string;
18
+ }
19
+ /**
20
+ * A logger for use in Nexus Handler scope.
21
+ *
22
+ * This defaults to the `Runtime`'s Logger (see {@link Runtime.logger}). Attributes from the
23
+ * current Nexus handler context are automatically included as metadata on every log entries. An
24
+ * extra `sdkComponent` metadata attribute is also added, with value `nexus`; this can be used
25
+ * for fine-grained filtering of log entries further downstream.
26
+ *
27
+ * To customize log attributes, register a {@link nexus.NexusOutboundCallsInterceptor} that
28
+ * intercepts the `getLogAttributes()` method.
29
+ *
30
+ * @experimental Nexus support in Temporal SDK is experimental.
31
+ */
32
+ export declare const log: Logger;
33
+ /**
34
+ * A metric meter for use in Nexus handler scope, with Nexus handler-specific tags.
35
+ *
36
+ * To add custom tags, register a {@link nexus.NexusOutboundCallsInterceptor} that
37
+ * intercepts the `getMetricTags()` method.
38
+ *
39
+ * @experimental Nexus support in Temporal SDK is experimental.
40
+ */
41
+ export declare const metricMeter: MetricMeter;
42
+ /**
43
+ * Returns a client to be used in a Nexus Operation's context, this Client is powered by the same
44
+ * NativeConnection that the worker was created with.
45
+ *
46
+ * @experimental Nexus support in Temporal SDK is experimental.
47
+ */
48
+ export declare function getClient(): Client;
package/lib/context.js ADDED
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.metricMeter = exports.log = exports.asyncLocalStorage = void 0;
4
+ exports.getHandlerContext = getHandlerContext;
5
+ exports.getClient = getClient;
6
+ const node_async_hooks_1 = require("node:async_hooks");
7
+ // Context Storage /////////////////////////////////////////////////////////////////////////////////
8
+ // Make it safe to use @temporalio/nexus with multiple versions installed.
9
+ const asyncLocalStorageSymbol = Symbol.for('__temporal_nexus_context_storage__');
10
+ if (!globalThis[asyncLocalStorageSymbol]) {
11
+ globalThis[asyncLocalStorageSymbol] = new node_async_hooks_1.AsyncLocalStorage();
12
+ }
13
+ exports.asyncLocalStorage = globalThis[asyncLocalStorageSymbol];
14
+ function getHandlerContext() {
15
+ const ctx = exports.asyncLocalStorage.getStore();
16
+ if (ctx == null) {
17
+ throw new ReferenceError('Not in a Nexus handler context');
18
+ }
19
+ return ctx;
20
+ }
21
+ // Basic APIs //////////////////////////////////////////////////////////////////////////////////////
22
+ /**
23
+ * A logger for use in Nexus Handler scope.
24
+ *
25
+ * This defaults to the `Runtime`'s Logger (see {@link Runtime.logger}). Attributes from the
26
+ * current Nexus handler context are automatically included as metadata on every log entries. An
27
+ * extra `sdkComponent` metadata attribute is also added, with value `nexus`; this can be used
28
+ * for fine-grained filtering of log entries further downstream.
29
+ *
30
+ * To customize log attributes, register a {@link nexus.NexusOutboundCallsInterceptor} that
31
+ * intercepts the `getLogAttributes()` method.
32
+ *
33
+ * @experimental Nexus support in Temporal SDK is experimental.
34
+ */
35
+ exports.log = {
36
+ log(level, message, meta) {
37
+ return getHandlerContext().log.log(level, message, meta);
38
+ },
39
+ trace(message, meta) {
40
+ return getHandlerContext().log.trace(message, meta);
41
+ },
42
+ debug(message, meta) {
43
+ return getHandlerContext().log.debug(message, meta);
44
+ },
45
+ info(message, meta) {
46
+ return getHandlerContext().log.info(message, meta);
47
+ },
48
+ warn(message, meta) {
49
+ return getHandlerContext().log.warn(message, meta);
50
+ },
51
+ error(message, meta) {
52
+ return getHandlerContext().log.error(message, meta);
53
+ },
54
+ };
55
+ /**
56
+ * A metric meter for use in Nexus handler scope, with Nexus handler-specific tags.
57
+ *
58
+ * To add custom tags, register a {@link nexus.NexusOutboundCallsInterceptor} that
59
+ * intercepts the `getMetricTags()` method.
60
+ *
61
+ * @experimental Nexus support in Temporal SDK is experimental.
62
+ */
63
+ exports.metricMeter = {
64
+ createCounter(name, unit, description) {
65
+ return getHandlerContext().metrics.createCounter(name, unit, description);
66
+ },
67
+ createHistogram(name, valueType = 'int', unit, description) {
68
+ return getHandlerContext().metrics.createHistogram(name, valueType, unit, description);
69
+ },
70
+ createGauge(name, valueType = 'int', unit, description) {
71
+ return getHandlerContext().metrics.createGauge(name, valueType, unit, description);
72
+ },
73
+ withTags(tags) {
74
+ return getHandlerContext().metrics.withTags(tags);
75
+ },
76
+ };
77
+ /**
78
+ * Returns a client to be used in a Nexus Operation's context, this Client is powered by the same
79
+ * NativeConnection that the worker was created with.
80
+ *
81
+ * @experimental Nexus support in Temporal SDK is experimental.
82
+ */
83
+ function getClient() {
84
+ return getHandlerContext().client;
85
+ }
86
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":";;;AAaA,8CAMC;AAiFD,8BAEC;AAtGD,uDAAqD;AAIrD,oGAAoG;AAEpG,0EAA0E;AAC1E,MAAM,uBAAuB,GAAG,MAAM,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;AACjF,IAAI,CAAE,UAAkB,CAAC,uBAAuB,CAAC,EAAE,CAAC;IACjD,UAAkB,CAAC,uBAAuB,CAAC,GAAG,IAAI,oCAAiB,EAAkB,CAAC;AACzF,CAAC;AACY,QAAA,iBAAiB,GAAuC,UAAkB,CAAC,uBAAuB,CAAC,CAAC;AAEjH,SAAgB,iBAAiB;IAC/B,MAAM,GAAG,GAAG,yBAAiB,CAAC,QAAQ,EAAE,CAAC;IACzC,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;QAChB,MAAM,IAAI,cAAc,CAAC,gCAAgC,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAgBD,oGAAoG;AAEpG;;;;;;;;;;;;GAYG;AACU,QAAA,GAAG,GAAW;IACzB,GAAG,CAAC,KAAe,EAAE,OAAe,EAAE,IAAkB;QACtD,OAAO,iBAAiB,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAC3D,CAAC;IACD,KAAK,CAAC,OAAe,EAAE,IAAkB;QACvC,OAAO,iBAAiB,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACtD,CAAC;IACD,KAAK,CAAC,OAAe,EAAE,IAAkB;QACvC,OAAO,iBAAiB,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACtD,CAAC;IACD,IAAI,CAAC,OAAe,EAAE,IAAkB;QACtC,OAAO,iBAAiB,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACrD,CAAC;IACD,IAAI,CAAC,OAAe,EAAE,IAAkB;QACtC,OAAO,iBAAiB,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACrD,CAAC;IACD,KAAK,CAAC,OAAe,EAAE,IAAkB;QACvC,OAAO,iBAAiB,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACtD,CAAC;CACF,CAAC;AAEF;;;;;;;GAOG;AACU,QAAA,WAAW,GAAgB;IACtC,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW;QACnC,OAAO,iBAAiB,EAAE,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;IAC5E,CAAC;IACD,eAAe,CAAC,IAAI,EAAE,SAAS,GAAG,KAAK,EAAE,IAAI,EAAE,WAAW;QACxD,OAAO,iBAAiB,EAAE,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;IACzF,CAAC;IACD,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,KAAK,EAAE,IAAI,EAAE,WAAW;QACpD,OAAO,iBAAiB,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;IACrF,CAAC;IACD,QAAQ,CAAC,IAAI;QACX,OAAO,iBAAiB,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACpD,CAAC;CACF,CAAC;AAEF;;;;;GAKG;AACH,SAAgB,SAAS;IACvB,OAAO,iBAAiB,EAAE,CAAC,MAAM,CAAC;AACpC,CAAC"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { log, getClient, metricMeter, } from './context';
2
+ export { startWorkflow, WorkflowHandle, WorkflowRunOperationHandler, WorkflowRunOperationStartHandler, WorkflowStartOptions, } from './workflow-helpers';
package/lib/index.js ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WorkflowRunOperationHandler = exports.startWorkflow = exports.metricMeter = exports.getClient = exports.log = void 0;
4
+ var context_1 = require("./context");
5
+ //
6
+ Object.defineProperty(exports, "log", { enumerable: true, get: function () { return context_1.log; } });
7
+ Object.defineProperty(exports, "getClient", { enumerable: true, get: function () { return context_1.getClient; } });
8
+ Object.defineProperty(exports, "metricMeter", { enumerable: true, get: function () { return context_1.metricMeter; } });
9
+ var workflow_helpers_1 = require("./workflow-helpers");
10
+ Object.defineProperty(exports, "startWorkflow", { enumerable: true, get: function () { return workflow_helpers_1.startWorkflow; } });
11
+ Object.defineProperty(exports, "WorkflowRunOperationHandler", { enumerable: true, get: function () { return workflow_helpers_1.WorkflowRunOperationHandler; } });
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,qCAKmB;AAJjB,EAAE;AACF,8FAAA,GAAG,OAAA;AACH,oGAAA,SAAS,OAAA;AACT,sGAAA,WAAW,OAAA;AAGb,uDAM4B;AAL1B,iHAAA,aAAa,OAAA;AAEb,+HAAA,2BAA2B,OAAA"}
@@ -0,0 +1,6 @@
1
+ import { Link as NexusLink } from 'nexus-rpc';
2
+ import { temporal } from '@temporalio/proto';
3
+ type WorkflowEventLink = temporal.api.common.v1.Link.IWorkflowEvent;
4
+ export declare function convertWorkflowEventLinkToNexusLink(we: WorkflowEventLink): NexusLink;
5
+ export declare function convertNexusLinkToWorkflowEventLink(link: NexusLink): WorkflowEventLink;
6
+ export {};
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.convertWorkflowEventLinkToNexusLink = convertWorkflowEventLinkToNexusLink;
7
+ exports.convertNexusLinkToWorkflowEventLink = convertNexusLinkToWorkflowEventLink;
8
+ const long_1 = __importDefault(require("long"));
9
+ const proto_1 = require("@temporalio/proto");
10
+ const { EventType } = proto_1.temporal.api.enums.v1;
11
+ const LINK_EVENT_ID_PARAM = 'eventID';
12
+ const LINK_EVENT_TYPE_PARAM = 'eventType';
13
+ const LINK_REQUEST_ID_PARAM = 'requestID';
14
+ const LINK_REFERENCE_TYPE_KEY = 'referenceType';
15
+ const EVENT_REFERENCE_TYPE = 'EventReference';
16
+ const REQUEST_ID_REFERENCE_TYPE = 'RequestIdReference';
17
+ // fullName isn't part of the generated typed unfortunately.
18
+ const WORKFLOW_EVENT_TYPE = proto_1.temporal.api.common.v1.Link.WorkflowEvent.fullName.slice(1);
19
+ function convertWorkflowEventLinkToNexusLink(we) {
20
+ if (!we.namespace || !we.workflowId || !we.runId) {
21
+ throw new TypeError('Missing required fields: namespace, workflowId, or runId');
22
+ }
23
+ const url = new URL(`temporal:///namespaces/${encodeURIComponent(we.namespace)}/workflows/${encodeURIComponent(we.workflowId)}/${encodeURIComponent(we.runId)}/history`);
24
+ if (we.eventRef != null) {
25
+ url.search = convertLinkWorkflowEventEventReferenceToURLQuery(we.eventRef);
26
+ }
27
+ else if (we.requestIdRef != null) {
28
+ url.search = convertLinkWorkflowEventRequestIdReferenceToURLQuery(we.requestIdRef);
29
+ }
30
+ return {
31
+ url,
32
+ type: WORKFLOW_EVENT_TYPE,
33
+ };
34
+ }
35
+ function convertNexusLinkToWorkflowEventLink(link) {
36
+ if (link.url.protocol !== 'temporal:') {
37
+ throw new TypeError(`Invalid URL scheme: ${link.url}, expected 'temporal:', got '${link.url.protocol}'`);
38
+ }
39
+ // /namespaces/:namespace/workflows/:workflowId/:runId/history
40
+ const parts = link.url.pathname.split('/');
41
+ if (parts.length !== 7 || parts[1] !== 'namespaces' || parts[3] !== 'workflows' || parts[6] !== 'history') {
42
+ throw new TypeError(`Invalid URL path: ${link.url}`);
43
+ }
44
+ const namespace = decodeURIComponent(parts[2]);
45
+ const workflowId = decodeURIComponent(parts[4]);
46
+ const runId = decodeURIComponent(parts[5]);
47
+ const query = link.url.searchParams;
48
+ const refType = query.get(LINK_REFERENCE_TYPE_KEY);
49
+ const workflowEventLink = {
50
+ namespace,
51
+ workflowId,
52
+ runId,
53
+ };
54
+ switch (refType) {
55
+ case EVENT_REFERENCE_TYPE:
56
+ workflowEventLink.eventRef = convertURLQueryToLinkWorkflowEventEventReference(query);
57
+ break;
58
+ case REQUEST_ID_REFERENCE_TYPE:
59
+ workflowEventLink.requestIdRef = convertURLQueryToLinkWorkflowEventRequestIdReference(query);
60
+ break;
61
+ default:
62
+ throw new TypeError(`Unknown reference type: ${refType}`);
63
+ }
64
+ return workflowEventLink;
65
+ }
66
+ function convertLinkWorkflowEventEventReferenceToURLQuery(eventRef) {
67
+ const params = new URLSearchParams();
68
+ params.set(LINK_REFERENCE_TYPE_KEY, EVENT_REFERENCE_TYPE);
69
+ if (eventRef.eventId != null) {
70
+ const eventId = eventRef.eventId.toNumber();
71
+ if (eventId > 0) {
72
+ params.set(LINK_EVENT_ID_PARAM, `${eventId}`);
73
+ }
74
+ }
75
+ if (eventRef.eventType != null) {
76
+ const eventType = constantCaseToPascalCase(EventType[eventRef.eventType].replace('EVENT_TYPE_', ''));
77
+ params.set(LINK_EVENT_TYPE_PARAM, eventType);
78
+ }
79
+ return params.toString();
80
+ }
81
+ function convertURLQueryToLinkWorkflowEventEventReference(query) {
82
+ let eventId = 0;
83
+ const eventIdParam = query.get(LINK_EVENT_ID_PARAM);
84
+ if (eventIdParam && /^\d+$/.test(eventIdParam)) {
85
+ eventId = parseInt(eventIdParam, 10);
86
+ }
87
+ const eventTypeParam = query.get(LINK_EVENT_TYPE_PARAM);
88
+ if (!eventTypeParam) {
89
+ throw new TypeError(`Missing eventType parameter`);
90
+ }
91
+ const eventType = EventType[normalizeEnumValue(eventTypeParam, 'EVENT_TYPE')];
92
+ if (eventType == null) {
93
+ throw new TypeError(`Unknown eventType parameter: ${eventTypeParam}`);
94
+ }
95
+ return { eventId: long_1.default.fromNumber(eventId), eventType };
96
+ }
97
+ function convertLinkWorkflowEventRequestIdReferenceToURLQuery(requestIdRef) {
98
+ const params = new URLSearchParams();
99
+ params.set(LINK_REFERENCE_TYPE_KEY, REQUEST_ID_REFERENCE_TYPE);
100
+ if (requestIdRef.requestId != null) {
101
+ params.set(LINK_REQUEST_ID_PARAM, requestIdRef.requestId);
102
+ }
103
+ if (requestIdRef.eventType != null) {
104
+ const eventType = constantCaseToPascalCase(EventType[requestIdRef.eventType].replace('EVENT_TYPE_', ''));
105
+ params.set(LINK_EVENT_TYPE_PARAM, eventType);
106
+ }
107
+ return params.toString();
108
+ }
109
+ function convertURLQueryToLinkWorkflowEventRequestIdReference(query) {
110
+ const requestId = query.get(LINK_REQUEST_ID_PARAM);
111
+ const eventTypeParam = query.get(LINK_EVENT_TYPE_PARAM);
112
+ if (!eventTypeParam) {
113
+ throw new TypeError(`Missing eventType parameter`);
114
+ }
115
+ const eventType = EventType[normalizeEnumValue(eventTypeParam, 'EVENT_TYPE')];
116
+ if (eventType == null) {
117
+ throw new TypeError(`Unknown eventType parameter: ${eventTypeParam}`);
118
+ }
119
+ return { requestId, eventType };
120
+ }
121
+ function normalizeEnumValue(value, prefix) {
122
+ value = pascalCaseToConstantCase(value);
123
+ if (!value.startsWith(prefix)) {
124
+ value = `${prefix}_${value}`;
125
+ }
126
+ return value;
127
+ }
128
+ function pascalCaseToConstantCase(s) {
129
+ return s.replace(/[^\b][A-Z]/g, (m) => `${m[0]}_${m[1]}`).toUpperCase();
130
+ }
131
+ function constantCaseToPascalCase(s) {
132
+ return s.replace(/[A-Z]+_?/g, (m) => `${m[0]}${m.slice(1).toLocaleLowerCase()}`.replace(/_/, ''));
133
+ }
134
+ //# sourceMappingURL=link-converter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-converter.js","sourceRoot":"","sources":["../src/link-converter.ts"],"names":[],"mappings":"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"}
package/lib/token.d.ts ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @internal
3
+ * @hidden
4
+ */
5
+ export interface WorkflowRunOperationToken {
6
+ /**
7
+ * Version of the token, by default we assume we're on version 1, this field is not emitted as part of the output,
8
+ * it's only used to reject newer token versions on load.
9
+ */
10
+ v?: number;
11
+ /**
12
+ * Type of the Operation. Must be OPERATION_TOKEN_TYPE_WORKFLOW_RUN.
13
+ */
14
+ t: OperationTokenType;
15
+ /**
16
+ * Namespace of the workflow.
17
+ */
18
+ ns: string;
19
+ /**
20
+ * ID of the workflow.
21
+ */
22
+ wid: string;
23
+ }
24
+ /**
25
+ * OperationTokenType is used to identify the type of Operation token.
26
+ * Currently, we only have one type of Operation token: WorkflowRun.
27
+ */
28
+ type OperationTokenType = (typeof OperationTokenType)[keyof typeof OperationTokenType];
29
+ /**
30
+ * @internal
31
+ * @hidden
32
+ */
33
+ declare const OperationTokenType: {
34
+ readonly WORKFLOW_RUN: 1;
35
+ };
36
+ /**
37
+ * Generate a workflow run Operation token.
38
+ */
39
+ export declare function generateWorkflowRunOperationToken(namespace: string, workflowId: string): string;
40
+ /**
41
+ * Load and validate a workflow run Operation token.
42
+ */
43
+ export declare function loadWorkflowRunOperationToken(data: string): WorkflowRunOperationToken;
44
+ export declare function base64URLEncodeNoPadding(str: string): string;
45
+ export {};
package/lib/token.js ADDED
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateWorkflowRunOperationToken = generateWorkflowRunOperationToken;
4
+ exports.loadWorkflowRunOperationToken = loadWorkflowRunOperationToken;
5
+ exports.base64URLEncodeNoPadding = base64URLEncodeNoPadding;
6
+ /**
7
+ * @internal
8
+ * @hidden
9
+ */
10
+ const OperationTokenType = {
11
+ WORKFLOW_RUN: 1,
12
+ };
13
+ /**
14
+ * Generate a workflow run Operation token.
15
+ */
16
+ function generateWorkflowRunOperationToken(namespace, workflowId) {
17
+ const token = {
18
+ t: OperationTokenType.WORKFLOW_RUN,
19
+ ns: namespace,
20
+ wid: workflowId,
21
+ };
22
+ return base64URLEncodeNoPadding(JSON.stringify(token));
23
+ }
24
+ /**
25
+ * Load and validate a workflow run Operation token.
26
+ */
27
+ function loadWorkflowRunOperationToken(data) {
28
+ if (!data) {
29
+ throw new TypeError('invalid workflow run token: token is empty');
30
+ }
31
+ let decoded;
32
+ try {
33
+ decoded = base64URLDecodeNoPadding(data);
34
+ }
35
+ catch (err) {
36
+ throw new TypeError('failed to decode token', { cause: err });
37
+ }
38
+ let token;
39
+ try {
40
+ token = JSON.parse(decoded);
41
+ }
42
+ catch (err) {
43
+ throw new TypeError('failed to unmarshal workflow run Operation token', { cause: err });
44
+ }
45
+ if (token.t !== OperationTokenType.WORKFLOW_RUN) {
46
+ throw new TypeError(`invalid workflow token type: ${token.t}, expected: ${OperationTokenType.WORKFLOW_RUN}`);
47
+ }
48
+ if (token.v !== undefined && token.v !== 0) {
49
+ throw new TypeError('invalid workflow run token: "v" field should not be present');
50
+ }
51
+ if (!token.wid) {
52
+ throw new TypeError('invalid workflow run token: missing workflow ID (wid)');
53
+ }
54
+ return token;
55
+ }
56
+ // Exported for use in tests.
57
+ function base64URLEncodeNoPadding(str) {
58
+ const base64 = Buffer.from(str).toString('base64url');
59
+ return base64.replace(/[=]+$/, '');
60
+ }
61
+ function base64URLDecodeNoPadding(str) {
62
+ // Validate the string contains only valid base64URL characters
63
+ if (!/^[A-Za-z0-9_-]*$/.test(str)) {
64
+ throw new TypeError('invalid base64URL encoded string: contains invalid characters');
65
+ }
66
+ const paddingLength = str.length % 4;
67
+ if (paddingLength > 0) {
68
+ str += '='.repeat(4 - paddingLength);
69
+ }
70
+ return Buffer.from(str, 'base64url').toString('utf-8');
71
+ }
72
+ //# sourceMappingURL=token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.js","sourceRoot":"","sources":["../src/token.ts"],"names":[],"mappings":";;AA4CA,8EAOC;AAKD,sEA8BC;AAGD,4DAGC;AA3DD;;;GAGG;AACH,MAAM,kBAAkB,GAAG;IACzB,YAAY,EAAE,CAAC;CACP,CAAC;AAEX;;GAEG;AACH,SAAgB,iCAAiC,CAAC,SAAiB,EAAE,UAAkB;IACrF,MAAM,KAAK,GAA8B;QACvC,CAAC,EAAE,kBAAkB,CAAC,YAAY;QAClC,EAAE,EAAE,SAAS;QACb,GAAG,EAAE,UAAU;KAChB,CAAC;IACF,OAAO,wBAAwB,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;AACzD,CAAC;AAED;;GAEG;AACH,SAAgB,6BAA6B,CAAC,IAAY;IACxD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,SAAS,CAAC,4CAA4C,CAAC,CAAC;IACpE,CAAC;IAED,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,wBAAwB,CAAC,IAAI,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,SAAS,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;IAChE,CAAC;IAED,IAAI,KAAgC,CAAC;IACrC,IAAI,CAAC;QACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,SAAS,CAAC,kDAAkD,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;IAC1F,CAAC;IAED,IAAI,KAAK,CAAC,CAAC,KAAK,kBAAkB,CAAC,YAAY,EAAE,CAAC;QAChD,MAAM,IAAI,SAAS,CAAC,gCAAgC,KAAK,CAAC,CAAC,eAAe,kBAAkB,CAAC,YAAY,EAAE,CAAC,CAAC;IAC/G,CAAC;IACD,IAAI,KAAK,CAAC,CAAC,KAAK,SAAS,IAAI,KAAK,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3C,MAAM,IAAI,SAAS,CAAC,6DAA6D,CAAC,CAAC;IACrF,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QACf,MAAM,IAAI,SAAS,CAAC,uDAAuD,CAAC,CAAC;IAC/E,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,6BAA6B;AAC7B,SAAgB,wBAAwB,CAAC,GAAW;IAClD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACtD,OAAO,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,wBAAwB,CAAC,GAAW;IAC3C,+DAA+D;IAC/D,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,SAAS,CAAC,+DAA+D,CAAC,CAAC;IACvF,CAAC;IAED,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;IACrC,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;QACtB,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC;IACvC,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AACzD,CAAC"}
@@ -0,0 +1,65 @@
1
+ import * as nexus from 'nexus-rpc';
2
+ import { Workflow } from '@temporalio/common';
3
+ import { Replace } from '@temporalio/common/lib/type-helpers';
4
+ import { WorkflowStartOptions as ClientWorkflowStartOptions } from '@temporalio/client';
5
+ declare const isNexusWorkflowHandle: unique symbol;
6
+ /**
7
+ * A handle to a running workflow that is returned by the {@link startWorkflow} helper.
8
+ * This handle should be returned by {@link WorkflowRunOperationStartHandler} implementations.
9
+ *
10
+ * @experimental Nexus support in Temporal SDK is experimental.
11
+ */
12
+ export interface WorkflowHandle<_T> {
13
+ readonly workflowId: string;
14
+ readonly runId: string;
15
+ /**
16
+ * Virtual type brand to maintain a distinction between {@link WorkflowHandle} provided by the
17
+ * {@link startWorkflow} helper (which will have attached links, request ID, completion URL, etc)
18
+ * and the `WorkflowHandle` type returned by the {@link WorkflowClient.start}.
19
+ *
20
+ * @internal
21
+ * @hidden
22
+ *
23
+ * @experimental Nexus support in Temporal SDK is experimental.
24
+ */
25
+ readonly [isNexusWorkflowHandle]: typeof isNexusWorkflowHandle;
26
+ }
27
+ /**
28
+ * Options for starting a workflow using {@link startWorkflow}, this type is identical to the
29
+ * client's `WorkflowStartOptions` with the exception that `taskQueue` is optional and defaults
30
+ * to the current worker's task queue.
31
+ *
32
+ * @experimental Nexus support in Temporal SDK is experimental.
33
+ */
34
+ export type WorkflowStartOptions<T extends Workflow> = Replace<ClientWorkflowStartOptions<T>, {
35
+ taskQueue?: string;
36
+ }>;
37
+ /**
38
+ * Starts a workflow run for a {@link WorkflowRunOperationStartHandler}, linking the execution chain
39
+ * to a Nexus Operation (subsequent runs started from continue-as-new and retries). Automatically
40
+ * propagates the callback, request ID, and back and forward links from the Nexus options to the
41
+ * Workflow.
42
+ *
43
+ * @experimental Nexus support in Temporal SDK is experimental.
44
+ */
45
+ export declare function startWorkflow<T extends Workflow>(ctx: nexus.StartOperationContext, workflowTypeOrFunc: string | T, workflowOptions: WorkflowStartOptions<T>): Promise<WorkflowHandle<T>>;
46
+ /**
47
+ * A handler function for the {@link WorkflowRunOperationHandler} constructor.
48
+ *
49
+ * @experimental Nexus support in Temporal SDK is experimental.
50
+ */
51
+ export type WorkflowRunOperationStartHandler<I, O> = (ctx: nexus.StartOperationContext, input: I) => Promise<WorkflowHandle<O>>;
52
+ /**
53
+ * A Nexus Operation implementation that is backed by a Workflow run.
54
+ *
55
+ * @experimental Nexus support in Temporal SDK is experimental.
56
+ */
57
+ export declare class WorkflowRunOperationHandler<I, O> implements nexus.OperationHandler<I, O> {
58
+ readonly handler: WorkflowRunOperationStartHandler<I, O>;
59
+ constructor(handler: WorkflowRunOperationStartHandler<I, O>);
60
+ start(ctx: nexus.StartOperationContext, input: I): Promise<nexus.HandlerStartOperationResult<O>>;
61
+ getInfo(_ctx: nexus.GetOperationInfoContext, _token: string): Promise<nexus.OperationInfo>;
62
+ getResult(_ctx: nexus.GetOperationResultContext, _token: string): Promise<O>;
63
+ cancel(_ctx: nexus.CancelOperationContext, token: string): Promise<void>;
64
+ }
65
+ export {};
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.WorkflowRunOperationHandler = void 0;
27
+ exports.startWorkflow = startWorkflow;
28
+ const nexus = __importStar(require("nexus-rpc"));
29
+ const internal_1 = require("@temporalio/client/lib/internal");
30
+ const token_1 = require("./token");
31
+ const link_converter_1 = require("./link-converter");
32
+ const context_1 = require("./context");
33
+ /**
34
+ * Starts a workflow run for a {@link WorkflowRunOperationStartHandler}, linking the execution chain
35
+ * to a Nexus Operation (subsequent runs started from continue-as-new and retries). Automatically
36
+ * propagates the callback, request ID, and back and forward links from the Nexus options to the
37
+ * Workflow.
38
+ *
39
+ * @experimental Nexus support in Temporal SDK is experimental.
40
+ */
41
+ async function startWorkflow(ctx, workflowTypeOrFunc, workflowOptions) {
42
+ const { client, taskQueue } = (0, context_1.getHandlerContext)();
43
+ const links = Array();
44
+ if (ctx.inboundLinks?.length > 0) {
45
+ for (const l of ctx.inboundLinks) {
46
+ try {
47
+ links.push({
48
+ workflowEvent: (0, link_converter_1.convertNexusLinkToWorkflowEventLink)(l),
49
+ });
50
+ }
51
+ catch (error) {
52
+ context_1.log.warn('failed to convert Nexus link to Workflow event link', { error });
53
+ }
54
+ }
55
+ }
56
+ const internalOptions = {
57
+ links,
58
+ requestId: ctx.requestId,
59
+ };
60
+ internalOptions.onConflictOptions = {
61
+ attachLinks: true,
62
+ attachCompletionCallbacks: true,
63
+ attachRequestId: true,
64
+ };
65
+ if (ctx.callbackUrl) {
66
+ internalOptions.completionCallbacks = [
67
+ {
68
+ nexus: { url: ctx.callbackUrl, header: ctx.callbackHeaders },
69
+ links, // pass in links here as well for older servers, newer servers dedupe them.
70
+ },
71
+ ];
72
+ }
73
+ const { taskQueue: userSpecifiedTaskQueue, ...rest } = workflowOptions;
74
+ const startOptions = {
75
+ ...rest,
76
+ taskQueue: userSpecifiedTaskQueue || taskQueue,
77
+ [internal_1.InternalWorkflowStartOptionsSymbol]: internalOptions,
78
+ };
79
+ const handle = await client.workflow.start(workflowTypeOrFunc, startOptions);
80
+ if (internalOptions.backLink?.workflowEvent != null) {
81
+ try {
82
+ ctx.outboundLinks.push((0, link_converter_1.convertWorkflowEventLinkToNexusLink)(internalOptions.backLink.workflowEvent));
83
+ }
84
+ catch (error) {
85
+ context_1.log.warn('failed to convert Workflow event link to Nexus link', { error });
86
+ }
87
+ }
88
+ return {
89
+ workflowId: handle.workflowId,
90
+ runId: handle.firstExecutionRunId,
91
+ };
92
+ }
93
+ /**
94
+ * A Nexus Operation implementation that is backed by a Workflow run.
95
+ *
96
+ * @experimental Nexus support in Temporal SDK is experimental.
97
+ */
98
+ class WorkflowRunOperationHandler {
99
+ handler;
100
+ constructor(handler) {
101
+ this.handler = handler;
102
+ }
103
+ async start(ctx, input) {
104
+ const { namespace } = (0, context_1.getHandlerContext)();
105
+ const handle = await this.handler(ctx, input);
106
+ return nexus.HandlerStartOperationResult.async((0, token_1.generateWorkflowRunOperationToken)(namespace, handle.workflowId));
107
+ }
108
+ getInfo(_ctx, _token) {
109
+ // Not implemented in Temporal yet.
110
+ throw new nexus.HandlerError('NOT_IMPLEMENTED', 'Method not implemented');
111
+ }
112
+ getResult(_ctx, _token) {
113
+ // Not implemented in Temporal yet.
114
+ throw new nexus.HandlerError('NOT_IMPLEMENTED', 'Method not implemented');
115
+ }
116
+ async cancel(_ctx, token) {
117
+ const decoded = (0, token_1.loadWorkflowRunOperationToken)(token);
118
+ await (0, context_1.getClient)().workflow.getHandle(decoded.wid).cancel();
119
+ }
120
+ }
121
+ exports.WorkflowRunOperationHandler = WorkflowRunOperationHandler;
122
+ //# sourceMappingURL=workflow-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-helpers.js","sourceRoot":"","sources":["../src/workflow-helpers.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@temporalio/nexus",
3
+ "version": "1.13.0",
4
+ "description": "Temporal.io SDK Nexus sub-package",
5
+ "main": "lib/index.js",
6
+ "types": "./lib/index.d.ts",
7
+ "keywords": [
8
+ "temporal",
9
+ "workflow",
10
+ "worker",
11
+ "nexus"
12
+ ],
13
+ "author": "Temporal Technologies Inc. <sdk@temporal.io>",
14
+ "license": "MIT",
15
+ "dependencies": {
16
+ "@temporalio/client": "1.13.0",
17
+ "@temporalio/common": "1.13.0",
18
+ "@temporalio/proto": "1.13.0",
19
+ "long": "^5.2.3",
20
+ "nexus-rpc": "^0.0.1"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/temporalio/sdk-typescript/issues"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/temporalio/sdk-typescript.git",
28
+ "directory": "packages/nexus"
29
+ },
30
+ "homepage": "https://github.com/temporalio/sdk-typescript/tree/main/packages/nexus",
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "engines": {
35
+ "node": ">= 18.0.0"
36
+ },
37
+ "files": [
38
+ "src",
39
+ "lib"
40
+ ],
41
+ "gitHead": "cf7c1e7d70f0c775f1382fd335b8bd721495f8cc"
42
+ }
package/src/context.ts ADDED
@@ -0,0 +1,103 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { Logger, LogLevel, LogMetadata, MetricMeter } from '@temporalio/common';
3
+ import { Client } from '@temporalio/client';
4
+
5
+ // Context Storage /////////////////////////////////////////////////////////////////////////////////
6
+
7
+ // Make it safe to use @temporalio/nexus with multiple versions installed.
8
+ const asyncLocalStorageSymbol = Symbol.for('__temporal_nexus_context_storage__');
9
+ if (!(globalThis as any)[asyncLocalStorageSymbol]) {
10
+ (globalThis as any)[asyncLocalStorageSymbol] = new AsyncLocalStorage<HandlerContext>();
11
+ }
12
+ export const asyncLocalStorage: AsyncLocalStorage<HandlerContext> = (globalThis as any)[asyncLocalStorageSymbol];
13
+
14
+ export function getHandlerContext(): HandlerContext {
15
+ const ctx = asyncLocalStorage.getStore();
16
+ if (ctx == null) {
17
+ throw new ReferenceError('Not in a Nexus handler context');
18
+ }
19
+ return ctx;
20
+ }
21
+
22
+ /**
23
+ * Context used internally in the SDK to propagate information from the worker to the Temporal Nexus helpers.
24
+ *
25
+ * @internal
26
+ * @hidden
27
+ */
28
+ export interface HandlerContext {
29
+ log: Logger;
30
+ metrics: MetricMeter;
31
+ client: Client;
32
+ namespace: string;
33
+ taskQueue: string;
34
+ }
35
+
36
+ // Basic APIs //////////////////////////////////////////////////////////////////////////////////////
37
+
38
+ /**
39
+ * A logger for use in Nexus Handler scope.
40
+ *
41
+ * This defaults to the `Runtime`'s Logger (see {@link Runtime.logger}). Attributes from the
42
+ * current Nexus handler context are automatically included as metadata on every log entries. An
43
+ * extra `sdkComponent` metadata attribute is also added, with value `nexus`; this can be used
44
+ * for fine-grained filtering of log entries further downstream.
45
+ *
46
+ * To customize log attributes, register a {@link nexus.NexusOutboundCallsInterceptor} that
47
+ * intercepts the `getLogAttributes()` method.
48
+ *
49
+ * @experimental Nexus support in Temporal SDK is experimental.
50
+ */
51
+ export const log: Logger = {
52
+ log(level: LogLevel, message: string, meta?: LogMetadata): any {
53
+ return getHandlerContext().log.log(level, message, meta);
54
+ },
55
+ trace(message: string, meta?: LogMetadata): any {
56
+ return getHandlerContext().log.trace(message, meta);
57
+ },
58
+ debug(message: string, meta?: LogMetadata): any {
59
+ return getHandlerContext().log.debug(message, meta);
60
+ },
61
+ info(message: string, meta?: LogMetadata): any {
62
+ return getHandlerContext().log.info(message, meta);
63
+ },
64
+ warn(message: string, meta?: LogMetadata): any {
65
+ return getHandlerContext().log.warn(message, meta);
66
+ },
67
+ error(message: string, meta?: LogMetadata): any {
68
+ return getHandlerContext().log.error(message, meta);
69
+ },
70
+ };
71
+
72
+ /**
73
+ * A metric meter for use in Nexus handler scope, with Nexus handler-specific tags.
74
+ *
75
+ * To add custom tags, register a {@link nexus.NexusOutboundCallsInterceptor} that
76
+ * intercepts the `getMetricTags()` method.
77
+ *
78
+ * @experimental Nexus support in Temporal SDK is experimental.
79
+ */
80
+ export const metricMeter: MetricMeter = {
81
+ createCounter(name, unit, description) {
82
+ return getHandlerContext().metrics.createCounter(name, unit, description);
83
+ },
84
+ createHistogram(name, valueType = 'int', unit, description) {
85
+ return getHandlerContext().metrics.createHistogram(name, valueType, unit, description);
86
+ },
87
+ createGauge(name, valueType = 'int', unit, description) {
88
+ return getHandlerContext().metrics.createGauge(name, valueType, unit, description);
89
+ },
90
+ withTags(tags) {
91
+ return getHandlerContext().metrics.withTags(tags);
92
+ },
93
+ };
94
+
95
+ /**
96
+ * Returns a client to be used in a Nexus Operation's context, this Client is powered by the same
97
+ * NativeConnection that the worker was created with.
98
+ *
99
+ * @experimental Nexus support in Temporal SDK is experimental.
100
+ */
101
+ export function getClient(): Client {
102
+ return getHandlerContext().client;
103
+ }
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ export {
2
+ //
3
+ log,
4
+ getClient,
5
+ metricMeter,
6
+ } from './context';
7
+
8
+ export {
9
+ startWorkflow,
10
+ WorkflowHandle,
11
+ WorkflowRunOperationHandler,
12
+ WorkflowRunOperationStartHandler,
13
+ WorkflowStartOptions,
14
+ } from './workflow-helpers';
@@ -0,0 +1,152 @@
1
+ import Long from 'long';
2
+ import { Link as NexusLink } from 'nexus-rpc';
3
+ import { temporal } from '@temporalio/proto';
4
+
5
+ const { EventType } = temporal.api.enums.v1;
6
+ type WorkflowEventLink = temporal.api.common.v1.Link.IWorkflowEvent;
7
+ type EventReference = temporal.api.common.v1.Link.WorkflowEvent.IEventReference;
8
+ type RequestIdReference = temporal.api.common.v1.Link.WorkflowEvent.IRequestIdReference;
9
+
10
+ const LINK_EVENT_ID_PARAM = 'eventID';
11
+ const LINK_EVENT_TYPE_PARAM = 'eventType';
12
+ const LINK_REQUEST_ID_PARAM = 'requestID';
13
+ const LINK_REFERENCE_TYPE_KEY = 'referenceType';
14
+
15
+ const EVENT_REFERENCE_TYPE = 'EventReference';
16
+ const REQUEST_ID_REFERENCE_TYPE = 'RequestIdReference';
17
+
18
+ // fullName isn't part of the generated typed unfortunately.
19
+ const WORKFLOW_EVENT_TYPE: string = (temporal.api.common.v1.Link.WorkflowEvent as any).fullName.slice(1);
20
+
21
+ export function convertWorkflowEventLinkToNexusLink(we: WorkflowEventLink): NexusLink {
22
+ if (!we.namespace || !we.workflowId || !we.runId) {
23
+ throw new TypeError('Missing required fields: namespace, workflowId, or runId');
24
+ }
25
+ const url = new URL(
26
+ `temporal:///namespaces/${encodeURIComponent(we.namespace)}/workflows/${encodeURIComponent(
27
+ we.workflowId
28
+ )}/${encodeURIComponent(we.runId)}/history`
29
+ );
30
+
31
+ if (we.eventRef != null) {
32
+ url.search = convertLinkWorkflowEventEventReferenceToURLQuery(we.eventRef);
33
+ } else if (we.requestIdRef != null) {
34
+ url.search = convertLinkWorkflowEventRequestIdReferenceToURLQuery(we.requestIdRef);
35
+ }
36
+
37
+ return {
38
+ url,
39
+ type: WORKFLOW_EVENT_TYPE,
40
+ };
41
+ }
42
+
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}'`);
46
+ }
47
+
48
+ // /namespaces/:namespace/workflows/:workflowId/:runId/history
49
+ const parts = link.url.pathname.split('/');
50
+ if (parts.length !== 7 || parts[1] !== 'namespaces' || parts[3] !== 'workflows' || parts[6] !== 'history') {
51
+ throw new TypeError(`Invalid URL path: ${link.url}`);
52
+ }
53
+ const namespace = decodeURIComponent(parts[2]);
54
+ const workflowId = decodeURIComponent(parts[4]);
55
+ const runId = decodeURIComponent(parts[5]);
56
+
57
+ const query = link.url.searchParams;
58
+ const refType = query.get(LINK_REFERENCE_TYPE_KEY);
59
+
60
+ const workflowEventLink: WorkflowEventLink = {
61
+ namespace,
62
+ workflowId,
63
+ runId,
64
+ };
65
+
66
+ switch (refType) {
67
+ case EVENT_REFERENCE_TYPE:
68
+ workflowEventLink.eventRef = convertURLQueryToLinkWorkflowEventEventReference(query);
69
+ break;
70
+ case REQUEST_ID_REFERENCE_TYPE:
71
+ workflowEventLink.requestIdRef = convertURLQueryToLinkWorkflowEventRequestIdReference(query);
72
+ break;
73
+ default:
74
+ throw new TypeError(`Unknown reference type: ${refType}`);
75
+ }
76
+ return workflowEventLink;
77
+ }
78
+
79
+ function convertLinkWorkflowEventEventReferenceToURLQuery(eventRef: EventReference): string {
80
+ const params = new URLSearchParams();
81
+ params.set(LINK_REFERENCE_TYPE_KEY, EVENT_REFERENCE_TYPE);
82
+ if (eventRef.eventId != null) {
83
+ const eventId = eventRef.eventId.toNumber();
84
+ if (eventId > 0) {
85
+ params.set(LINK_EVENT_ID_PARAM, `${eventId}`);
86
+ }
87
+ }
88
+ if (eventRef.eventType != null) {
89
+ const eventType = constantCaseToPascalCase(EventType[eventRef.eventType].replace('EVENT_TYPE_', ''));
90
+ params.set(LINK_EVENT_TYPE_PARAM, eventType);
91
+ }
92
+ return params.toString();
93
+ }
94
+
95
+ function convertURLQueryToLinkWorkflowEventEventReference(query: URLSearchParams): EventReference {
96
+ let eventId = 0;
97
+ const eventIdParam = query.get(LINK_EVENT_ID_PARAM);
98
+ if (eventIdParam && /^\d+$/.test(eventIdParam)) {
99
+ eventId = parseInt(eventIdParam, 10);
100
+ }
101
+ const eventTypeParam = query.get(LINK_EVENT_TYPE_PARAM);
102
+ if (!eventTypeParam) {
103
+ throw new TypeError(`Missing eventType parameter`);
104
+ }
105
+ const eventType = EventType[normalizeEnumValue(eventTypeParam, 'EVENT_TYPE') as keyof typeof EventType];
106
+ if (eventType == null) {
107
+ throw new TypeError(`Unknown eventType parameter: ${eventTypeParam}`);
108
+ }
109
+ return { eventId: Long.fromNumber(eventId), eventType };
110
+ }
111
+
112
+ function convertLinkWorkflowEventRequestIdReferenceToURLQuery(requestIdRef: RequestIdReference): string {
113
+ const params = new URLSearchParams();
114
+ params.set(LINK_REFERENCE_TYPE_KEY, REQUEST_ID_REFERENCE_TYPE);
115
+ if (requestIdRef.requestId != null) {
116
+ params.set(LINK_REQUEST_ID_PARAM, requestIdRef.requestId);
117
+ }
118
+ if (requestIdRef.eventType != null) {
119
+ const eventType = constantCaseToPascalCase(EventType[requestIdRef.eventType].replace('EVENT_TYPE_', ''));
120
+ params.set(LINK_EVENT_TYPE_PARAM, eventType);
121
+ }
122
+ return params.toString();
123
+ }
124
+
125
+ function convertURLQueryToLinkWorkflowEventRequestIdReference(query: URLSearchParams): RequestIdReference {
126
+ const requestId = query.get(LINK_REQUEST_ID_PARAM);
127
+ const eventTypeParam = query.get(LINK_EVENT_TYPE_PARAM);
128
+ if (!eventTypeParam) {
129
+ throw new TypeError(`Missing eventType parameter`);
130
+ }
131
+ const eventType = EventType[normalizeEnumValue(eventTypeParam, 'EVENT_TYPE') as keyof typeof EventType];
132
+ if (eventType == null) {
133
+ throw new TypeError(`Unknown eventType parameter: ${eventTypeParam}`);
134
+ }
135
+ return { requestId, eventType };
136
+ }
137
+
138
+ function normalizeEnumValue(value: string, prefix: string) {
139
+ value = pascalCaseToConstantCase(value);
140
+ if (!value.startsWith(prefix)) {
141
+ value = `${prefix}_${value}`;
142
+ }
143
+ return value;
144
+ }
145
+
146
+ function pascalCaseToConstantCase(s: string) {
147
+ return s.replace(/[^\b][A-Z]/g, (m) => `${m[0]}_${m[1]}`).toUpperCase();
148
+ }
149
+
150
+ function constantCaseToPascalCase(s: string) {
151
+ return s.replace(/[A-Z]+_?/g, (m) => `${m[0]}${m.slice(1).toLocaleLowerCase()}`.replace(/_/, ''));
152
+ }
package/src/token.ts ADDED
@@ -0,0 +1,107 @@
1
+ /**
2
+ * @internal
3
+ * @hidden
4
+ */
5
+ export interface WorkflowRunOperationToken {
6
+ /**
7
+ * Version of the token, by default we assume we're on version 1, this field is not emitted as part of the output,
8
+ * it's only used to reject newer token versions on load.
9
+ */
10
+ v?: number;
11
+
12
+ /**
13
+ * Type of the Operation. Must be OPERATION_TOKEN_TYPE_WORKFLOW_RUN.
14
+ */
15
+ t: OperationTokenType;
16
+
17
+ /**
18
+ * Namespace of the workflow.
19
+ */
20
+ ns: string;
21
+
22
+ /**
23
+ * ID of the workflow.
24
+ */
25
+ wid: string;
26
+ }
27
+
28
+ /**
29
+ * OperationTokenType is used to identify the type of Operation token.
30
+ * Currently, we only have one type of Operation token: WorkflowRun.
31
+ */
32
+ type OperationTokenType = (typeof OperationTokenType)[keyof typeof OperationTokenType];
33
+
34
+ /**
35
+ * @internal
36
+ * @hidden
37
+ */
38
+ const OperationTokenType = {
39
+ WORKFLOW_RUN: 1,
40
+ } as const;
41
+
42
+ /**
43
+ * Generate a workflow run Operation token.
44
+ */
45
+ export function generateWorkflowRunOperationToken(namespace: string, workflowId: string): string {
46
+ const token: WorkflowRunOperationToken = {
47
+ t: OperationTokenType.WORKFLOW_RUN,
48
+ ns: namespace,
49
+ wid: workflowId,
50
+ };
51
+ return base64URLEncodeNoPadding(JSON.stringify(token));
52
+ }
53
+
54
+ /**
55
+ * Load and validate a workflow run Operation token.
56
+ */
57
+ export function loadWorkflowRunOperationToken(data: string): WorkflowRunOperationToken {
58
+ if (!data) {
59
+ throw new TypeError('invalid workflow run token: token is empty');
60
+ }
61
+
62
+ let decoded: string;
63
+ try {
64
+ decoded = base64URLDecodeNoPadding(data);
65
+ } catch (err) {
66
+ throw new TypeError('failed to decode token', { cause: err });
67
+ }
68
+
69
+ let token: WorkflowRunOperationToken;
70
+ try {
71
+ token = JSON.parse(decoded);
72
+ } catch (err) {
73
+ throw new TypeError('failed to unmarshal workflow run Operation token', { cause: err });
74
+ }
75
+
76
+ if (token.t !== OperationTokenType.WORKFLOW_RUN) {
77
+ throw new TypeError(`invalid workflow token type: ${token.t}, expected: ${OperationTokenType.WORKFLOW_RUN}`);
78
+ }
79
+ if (token.v !== undefined && token.v !== 0) {
80
+ throw new TypeError('invalid workflow run token: "v" field should not be present');
81
+ }
82
+ if (!token.wid) {
83
+ throw new TypeError('invalid workflow run token: missing workflow ID (wid)');
84
+ }
85
+
86
+ return token;
87
+ }
88
+
89
+ // Exported for use in tests.
90
+ export function base64URLEncodeNoPadding(str: string): string {
91
+ const base64 = Buffer.from(str).toString('base64url');
92
+ return base64.replace(/[=]+$/, '');
93
+ }
94
+
95
+ function base64URLDecodeNoPadding(str: string): string {
96
+ // Validate the string contains only valid base64URL characters
97
+ if (!/^[A-Za-z0-9_-]*$/.test(str)) {
98
+ throw new TypeError('invalid base64URL encoded string: contains invalid characters');
99
+ }
100
+
101
+ const paddingLength = str.length % 4;
102
+ if (paddingLength > 0) {
103
+ str += '='.repeat(4 - paddingLength);
104
+ }
105
+
106
+ return Buffer.from(str, 'base64url').toString('utf-8');
107
+ }
@@ -0,0 +1,151 @@
1
+ import * as nexus from 'nexus-rpc';
2
+ import { Workflow } from '@temporalio/common';
3
+ import { Replace } from '@temporalio/common/lib/type-helpers';
4
+ import { WorkflowStartOptions as ClientWorkflowStartOptions } from '@temporalio/client';
5
+ import { type temporal } from '@temporalio/proto';
6
+ import { InternalWorkflowStartOptionsSymbol, InternalWorkflowStartOptions } from '@temporalio/client/lib/internal';
7
+ import { generateWorkflowRunOperationToken, loadWorkflowRunOperationToken } from './token';
8
+ import { convertNexusLinkToWorkflowEventLink, convertWorkflowEventLinkToNexusLink } from './link-converter';
9
+ import { getClient, getHandlerContext, log } from './context';
10
+
11
+ declare const isNexusWorkflowHandle: unique symbol;
12
+
13
+ /**
14
+ * A handle to a running workflow that is returned by the {@link startWorkflow} helper.
15
+ * This handle should be returned by {@link WorkflowRunOperationStartHandler} implementations.
16
+ *
17
+ * @experimental Nexus support in Temporal SDK is experimental.
18
+ */
19
+ export interface WorkflowHandle<_T> {
20
+ readonly workflowId: string;
21
+ readonly runId: string;
22
+
23
+ /**
24
+ * Virtual type brand to maintain a distinction between {@link WorkflowHandle} provided by the
25
+ * {@link startWorkflow} helper (which will have attached links, request ID, completion URL, etc)
26
+ * and the `WorkflowHandle` type returned by the {@link WorkflowClient.start}.
27
+ *
28
+ * @internal
29
+ * @hidden
30
+ *
31
+ * @experimental Nexus support in Temporal SDK is experimental.
32
+ */
33
+ readonly [isNexusWorkflowHandle]: typeof isNexusWorkflowHandle;
34
+ }
35
+
36
+ /**
37
+ * Options for starting a workflow using {@link startWorkflow}, this type is identical to the
38
+ * client's `WorkflowStartOptions` with the exception that `taskQueue` is optional and defaults
39
+ * to the current worker's task queue.
40
+ *
41
+ * @experimental Nexus support in Temporal SDK is experimental.
42
+ */
43
+ export type WorkflowStartOptions<T extends Workflow> = Replace<ClientWorkflowStartOptions<T>, { taskQueue?: string }>;
44
+
45
+ /**
46
+ * Starts a workflow run for a {@link WorkflowRunOperationStartHandler}, linking the execution chain
47
+ * to a Nexus Operation (subsequent runs started from continue-as-new and retries). Automatically
48
+ * propagates the callback, request ID, and back and forward links from the Nexus options to the
49
+ * Workflow.
50
+ *
51
+ * @experimental Nexus support in Temporal SDK is experimental.
52
+ */
53
+ export async function startWorkflow<T extends Workflow>(
54
+ ctx: nexus.StartOperationContext,
55
+ workflowTypeOrFunc: string | T,
56
+ workflowOptions: WorkflowStartOptions<T>
57
+ ): Promise<WorkflowHandle<T>> {
58
+ const { client, taskQueue } = getHandlerContext();
59
+ const links = Array<temporal.api.common.v1.ILink>();
60
+ if (ctx.inboundLinks?.length > 0) {
61
+ for (const l of ctx.inboundLinks) {
62
+ try {
63
+ links.push({
64
+ workflowEvent: convertNexusLinkToWorkflowEventLink(l),
65
+ });
66
+ } catch (error) {
67
+ log.warn('failed to convert Nexus link to Workflow event link', { error });
68
+ }
69
+ }
70
+ }
71
+ const internalOptions: InternalWorkflowStartOptions[typeof InternalWorkflowStartOptionsSymbol] = {
72
+ links,
73
+ requestId: ctx.requestId,
74
+ };
75
+
76
+ internalOptions.onConflictOptions = {
77
+ attachLinks: true,
78
+ attachCompletionCallbacks: true,
79
+ attachRequestId: true,
80
+ };
81
+
82
+ if (ctx.callbackUrl) {
83
+ internalOptions.completionCallbacks = [
84
+ {
85
+ nexus: { url: ctx.callbackUrl, header: ctx.callbackHeaders },
86
+ links, // pass in links here as well for older servers, newer servers dedupe them.
87
+ },
88
+ ];
89
+ }
90
+
91
+ const { taskQueue: userSpecifiedTaskQueue, ...rest } = workflowOptions;
92
+ const startOptions: ClientWorkflowStartOptions = {
93
+ ...rest,
94
+ taskQueue: userSpecifiedTaskQueue || taskQueue,
95
+ [InternalWorkflowStartOptionsSymbol]: internalOptions,
96
+ };
97
+
98
+ const handle = await client.workflow.start(workflowTypeOrFunc, startOptions);
99
+ if (internalOptions.backLink?.workflowEvent != null) {
100
+ try {
101
+ ctx.outboundLinks.push(convertWorkflowEventLinkToNexusLink(internalOptions.backLink.workflowEvent));
102
+ } catch (error) {
103
+ log.warn('failed to convert Workflow event link to Nexus link', { error });
104
+ }
105
+ }
106
+
107
+ return {
108
+ workflowId: handle.workflowId,
109
+ runId: handle.firstExecutionRunId,
110
+ } as WorkflowHandle<T>;
111
+ }
112
+
113
+ /**
114
+ * A handler function for the {@link WorkflowRunOperationHandler} constructor.
115
+ *
116
+ * @experimental Nexus support in Temporal SDK is experimental.
117
+ */
118
+ export type WorkflowRunOperationStartHandler<I, O> = (
119
+ ctx: nexus.StartOperationContext,
120
+ input: I
121
+ ) => Promise<WorkflowHandle<O>>;
122
+
123
+ /**
124
+ * A Nexus Operation implementation that is backed by a Workflow run.
125
+ *
126
+ * @experimental Nexus support in Temporal SDK is experimental.
127
+ */
128
+ export class WorkflowRunOperationHandler<I, O> implements nexus.OperationHandler<I, O> {
129
+ constructor(readonly handler: WorkflowRunOperationStartHandler<I, O>) {}
130
+
131
+ async start(ctx: nexus.StartOperationContext, input: I): Promise<nexus.HandlerStartOperationResult<O>> {
132
+ const { namespace } = getHandlerContext();
133
+ const handle = await this.handler(ctx, input);
134
+ return nexus.HandlerStartOperationResult.async(generateWorkflowRunOperationToken(namespace, handle.workflowId));
135
+ }
136
+
137
+ getInfo(_ctx: nexus.GetOperationInfoContext, _token: string): Promise<nexus.OperationInfo> {
138
+ // Not implemented in Temporal yet.
139
+ throw new nexus.HandlerError('NOT_IMPLEMENTED', 'Method not implemented');
140
+ }
141
+
142
+ getResult(_ctx: nexus.GetOperationResultContext, _token: string): Promise<O> {
143
+ // Not implemented in Temporal yet.
144
+ throw new nexus.HandlerError('NOT_IMPLEMENTED', 'Method not implemented');
145
+ }
146
+
147
+ async cancel(_ctx: nexus.CancelOperationContext, token: string): Promise<void> {
148
+ const decoded = loadWorkflowRunOperationToken(token);
149
+ await getClient().workflow.getHandle(decoded.wid).cancel();
150
+ }
151
+ }