@temporalio/interceptors-opentelemetry 1.13.1 → 1.14.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 → LICENSE} +6 -8
- package/lib/client/index.d.ts +9 -1
- package/lib/client/index.js +122 -3
- package/lib/client/index.js.map +1 -1
- package/lib/instrumentation.d.ts +21 -3
- package/lib/instrumentation.js +52 -12
- package/lib/instrumentation.js.map +1 -1
- package/lib/worker/index.d.ts +8 -7
- package/lib/worker/index.js +29 -4
- package/lib/worker/index.js.map +1 -1
- package/lib/workflow/context-manager.d.ts +2 -1
- package/lib/workflow/context-manager.js +8 -2
- package/lib/workflow/context-manager.js.map +1 -1
- package/lib/workflow/definitions.d.ts +45 -1
- package/lib/workflow/definitions.js +44 -0
- package/lib/workflow/definitions.js.map +1 -1
- package/lib/workflow/index.d.ts +12 -1
- package/lib/workflow/index.js +112 -12
- package/lib/workflow/index.js.map +1 -1
- package/lib/workflow/runtime.js +3 -2
- package/lib/workflow/runtime.js.map +1 -1
- package/lib/workflow/span-exporter.d.ts +1 -0
- package/lib/workflow/span-exporter.js +5 -25
- package/lib/workflow/span-exporter.js.map +1 -1
- package/lib/workflow/workflow-module-loader.d.ts +28 -0
- package/lib/workflow/workflow-module-loader.js +56 -0
- package/lib/workflow/workflow-module-loader.js.map +1 -0
- package/package.json +18 -16
- package/src/client/index.ts +178 -5
- package/src/instrumentation.ts +62 -11
- package/src/worker/index.ts +48 -13
- package/src/workflow/context-manager.ts +13 -5
- package/src/workflow/definitions.ts +54 -0
- package/src/workflow/index.ts +146 -12
- package/src/workflow/runtime.ts +4 -2
- package/src/workflow/span-exporter.ts +7 -3
- package/src/workflow/workflow-module-loader.ts +64 -0
package/src/workflow/index.ts
CHANGED
|
@@ -3,27 +3,40 @@
|
|
|
3
3
|
import './runtime'; // Patch the Workflow isolate runtime for opentelemetry
|
|
4
4
|
import * as otel from '@opentelemetry/api';
|
|
5
5
|
import * as tracing from '@opentelemetry/sdk-trace-base';
|
|
6
|
-
import {
|
|
6
|
+
import type {
|
|
7
7
|
ActivityInput,
|
|
8
|
-
ContinueAsNew,
|
|
9
8
|
ContinueAsNewInput,
|
|
10
9
|
DisposeInput,
|
|
11
10
|
GetLogAttributesInput,
|
|
11
|
+
GetMetricTagsInput,
|
|
12
12
|
LocalActivityInput,
|
|
13
13
|
Next,
|
|
14
|
+
QueryInput,
|
|
14
15
|
SignalInput,
|
|
15
16
|
SignalWorkflowInput,
|
|
16
17
|
StartChildWorkflowExecutionInput,
|
|
18
|
+
UpdateInput,
|
|
17
19
|
WorkflowExecuteInput,
|
|
18
20
|
WorkflowInboundCallsInterceptor,
|
|
19
|
-
workflowInfo,
|
|
20
21
|
WorkflowInternalsInterceptor,
|
|
21
22
|
WorkflowOutboundCallsInterceptor,
|
|
23
|
+
StartNexusOperationInput,
|
|
24
|
+
StartNexusOperationOutput,
|
|
22
25
|
} from '@temporalio/workflow';
|
|
23
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
instrument,
|
|
28
|
+
instrumentSync,
|
|
29
|
+
extractContextFromHeaders,
|
|
30
|
+
headersWithContext,
|
|
31
|
+
UPDATE_ID_ATTR_KEY,
|
|
32
|
+
NEXUS_SERVICE_ATTR_KEY,
|
|
33
|
+
NEXUS_OPERATION_ATTR_KEY,
|
|
34
|
+
NEXUS_ENDPOINT_ATTR_KEY,
|
|
35
|
+
} from '../instrumentation';
|
|
24
36
|
import { ContextManager } from './context-manager';
|
|
25
37
|
import { SpanName, SPAN_DELIMITER } from './definitions';
|
|
26
38
|
import { SpanExporter } from './span-exporter';
|
|
39
|
+
import { ensureWorkflowModuleLoaded, getWorkflowModule, hasSdkFlag } from './workflow-module-loader';
|
|
27
40
|
|
|
28
41
|
export * from './definitions';
|
|
29
42
|
|
|
@@ -48,15 +61,25 @@ function getTracer(): otel.Tracer {
|
|
|
48
61
|
*
|
|
49
62
|
* Wraps the operation in an opentelemetry Span and links it to a parent Span context if one is
|
|
50
63
|
* provided in the Workflow input headers.
|
|
64
|
+
*
|
|
65
|
+
* `@temporalio/workflow` must be provided by host package in order to function.
|
|
51
66
|
*/
|
|
52
67
|
export class OpenTelemetryInboundInterceptor implements WorkflowInboundCallsInterceptor {
|
|
53
68
|
protected readonly tracer = getTracer();
|
|
54
69
|
|
|
70
|
+
public constructor() {
|
|
71
|
+
ensureWorkflowModuleLoaded();
|
|
72
|
+
}
|
|
73
|
+
|
|
55
74
|
public async execute(
|
|
56
75
|
input: WorkflowExecuteInput,
|
|
57
76
|
next: Next<WorkflowInboundCallsInterceptor, 'execute'>
|
|
58
77
|
): Promise<unknown> {
|
|
59
|
-
const
|
|
78
|
+
const { workflowInfo, ContinueAsNew } = getWorkflowModule();
|
|
79
|
+
|
|
80
|
+
const context = extractContextFromHeaders(input.headers);
|
|
81
|
+
if (!hasSdkFlag('OpenTelemetryInterceporsAvoidsExtraYields')) await Promise.resolve();
|
|
82
|
+
|
|
60
83
|
return await instrument({
|
|
61
84
|
tracer: this.tracer,
|
|
62
85
|
spanName: `${SpanName.WORKFLOW_EXECUTE}${SPAN_DELIMITER}${workflowInfo().workflowType}`,
|
|
@@ -70,10 +93,65 @@ export class OpenTelemetryInboundInterceptor implements WorkflowInboundCallsInte
|
|
|
70
93
|
input: SignalInput,
|
|
71
94
|
next: Next<WorkflowInboundCallsInterceptor, 'handleSignal'>
|
|
72
95
|
): Promise<void> {
|
|
73
|
-
|
|
96
|
+
// Tracing of inbound signals was added in v1.11.5.
|
|
97
|
+
if (!hasSdkFlag('OpenTelemetryInterceptorsTracesInboundSignals')) return next(input);
|
|
98
|
+
|
|
99
|
+
const context = extractContextFromHeaders(input.headers);
|
|
100
|
+
if (!hasSdkFlag('OpenTelemetryInterceporsAvoidsExtraYields')) await Promise.resolve();
|
|
101
|
+
|
|
74
102
|
return await instrument({
|
|
75
103
|
tracer: this.tracer,
|
|
76
|
-
spanName: `${SpanName.
|
|
104
|
+
spanName: `${SpanName.WORKFLOW_HANDLE_SIGNAL}${SPAN_DELIMITER}${input.signalName}`,
|
|
105
|
+
fn: () => next(input),
|
|
106
|
+
context,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public async handleUpdate(
|
|
111
|
+
input: UpdateInput,
|
|
112
|
+
next: Next<WorkflowInboundCallsInterceptor, 'handleUpdate'>
|
|
113
|
+
): Promise<unknown> {
|
|
114
|
+
if (!hasSdkFlag('OpenTelemetryInterceptorsInstrumentsAllMethods')) return next(input);
|
|
115
|
+
|
|
116
|
+
const context = extractContextFromHeaders(input.headers);
|
|
117
|
+
|
|
118
|
+
return await instrument({
|
|
119
|
+
tracer: this.tracer,
|
|
120
|
+
spanName: `${SpanName.WORKFLOW_HANDLE_UPDATE}${SPAN_DELIMITER}${input.name}`,
|
|
121
|
+
fn: (span) => {
|
|
122
|
+
span.setAttribute(UPDATE_ID_ATTR_KEY, input.updateId);
|
|
123
|
+
return next(input);
|
|
124
|
+
},
|
|
125
|
+
context,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public validateUpdate(input: UpdateInput, next: Next<WorkflowInboundCallsInterceptor, 'validateUpdate'>): void {
|
|
130
|
+
if (!hasSdkFlag('OpenTelemetryInterceptorsInstrumentsAllMethods')) return next(input);
|
|
131
|
+
|
|
132
|
+
const context = extractContextFromHeaders(input.headers);
|
|
133
|
+
instrumentSync({
|
|
134
|
+
tracer: this.tracer,
|
|
135
|
+
spanName: `${SpanName.WORKFLOW_VALIDATE_UPDATE}${SPAN_DELIMITER}${input.name}`,
|
|
136
|
+
fn: (span) => {
|
|
137
|
+
span.setAttribute(UPDATE_ID_ATTR_KEY, input.updateId);
|
|
138
|
+
return next(input);
|
|
139
|
+
},
|
|
140
|
+
context,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
public async handleQuery(
|
|
145
|
+
input: QueryInput,
|
|
146
|
+
next: Next<WorkflowInboundCallsInterceptor, 'handleQuery'>
|
|
147
|
+
): Promise<unknown> {
|
|
148
|
+
if (!hasSdkFlag('OpenTelemetryInterceptorsInstrumentsAllMethods')) return next(input);
|
|
149
|
+
|
|
150
|
+
const context = extractContextFromHeaders(input.headers);
|
|
151
|
+
|
|
152
|
+
return await instrument({
|
|
153
|
+
tracer: this.tracer,
|
|
154
|
+
spanName: `${SpanName.WORKFLOW_HANDLE_QUERY}${SPAN_DELIMITER}${input.queryName}`,
|
|
77
155
|
fn: () => next(input),
|
|
78
156
|
context,
|
|
79
157
|
});
|
|
@@ -84,10 +162,16 @@ export class OpenTelemetryInboundInterceptor implements WorkflowInboundCallsInte
|
|
|
84
162
|
* Intercepts outbound calls to schedule an Activity
|
|
85
163
|
*
|
|
86
164
|
* Wraps the operation in an opentelemetry Span and passes it to the Activity via headers.
|
|
165
|
+
*
|
|
166
|
+
* `@temporalio/workflow` must be provided by host package in order to function.
|
|
87
167
|
*/
|
|
88
168
|
export class OpenTelemetryOutboundInterceptor implements WorkflowOutboundCallsInterceptor {
|
|
89
169
|
protected readonly tracer = getTracer();
|
|
90
170
|
|
|
171
|
+
public constructor() {
|
|
172
|
+
ensureWorkflowModuleLoaded();
|
|
173
|
+
}
|
|
174
|
+
|
|
91
175
|
public async scheduleActivity(
|
|
92
176
|
input: ActivityInput,
|
|
93
177
|
next: Next<WorkflowOutboundCallsInterceptor, 'scheduleActivity'>
|
|
@@ -96,7 +180,9 @@ export class OpenTelemetryOutboundInterceptor implements WorkflowOutboundCallsIn
|
|
|
96
180
|
tracer: this.tracer,
|
|
97
181
|
spanName: `${SpanName.ACTIVITY_START}${SPAN_DELIMITER}${input.activityType}`,
|
|
98
182
|
fn: async () => {
|
|
99
|
-
const headers =
|
|
183
|
+
const headers = headersWithContext(input.headers);
|
|
184
|
+
if (!hasSdkFlag('OpenTelemetryInterceporsAvoidsExtraYields')) await Promise.resolve();
|
|
185
|
+
|
|
100
186
|
return next({
|
|
101
187
|
...input,
|
|
102
188
|
headers,
|
|
@@ -109,11 +195,16 @@ export class OpenTelemetryOutboundInterceptor implements WorkflowOutboundCallsIn
|
|
|
109
195
|
input: LocalActivityInput,
|
|
110
196
|
next: Next<WorkflowOutboundCallsInterceptor, 'scheduleLocalActivity'>
|
|
111
197
|
): Promise<unknown> {
|
|
198
|
+
// Tracing of local activities was added in v1.11.6.
|
|
199
|
+
if (!hasSdkFlag('OpenTelemetryInterceptorsTracesLocalActivities')) return next(input);
|
|
200
|
+
|
|
112
201
|
return await instrument({
|
|
113
202
|
tracer: this.tracer,
|
|
114
203
|
spanName: `${SpanName.ACTIVITY_START}${SPAN_DELIMITER}${input.activityType}`,
|
|
115
204
|
fn: async () => {
|
|
116
|
-
const headers =
|
|
205
|
+
const headers = headersWithContext(input.headers);
|
|
206
|
+
if (!hasSdkFlag('OpenTelemetryInterceporsAvoidsExtraYields')) await Promise.resolve();
|
|
207
|
+
|
|
117
208
|
return next({
|
|
118
209
|
...input,
|
|
119
210
|
headers,
|
|
@@ -122,6 +213,24 @@ export class OpenTelemetryOutboundInterceptor implements WorkflowOutboundCallsIn
|
|
|
122
213
|
});
|
|
123
214
|
}
|
|
124
215
|
|
|
216
|
+
public async startNexusOperation(
|
|
217
|
+
input: StartNexusOperationInput,
|
|
218
|
+
next: Next<WorkflowOutboundCallsInterceptor, 'startNexusOperation'>
|
|
219
|
+
): Promise<StartNexusOperationOutput> {
|
|
220
|
+
if (!hasSdkFlag('OpenTelemetryInterceptorsInstrumentsAllMethods')) return next(input);
|
|
221
|
+
|
|
222
|
+
return await instrument({
|
|
223
|
+
tracer: this.tracer,
|
|
224
|
+
spanName: `${SpanName.NEXUS_OPERATION_START}${SPAN_DELIMITER}${input.service}${SPAN_DELIMITER}${input.operation}`,
|
|
225
|
+
fn: async (span) => {
|
|
226
|
+
span.setAttribute(NEXUS_SERVICE_ATTR_KEY, input.service);
|
|
227
|
+
span.setAttribute(NEXUS_OPERATION_ATTR_KEY, input.operation);
|
|
228
|
+
span.setAttribute(NEXUS_ENDPOINT_ATTR_KEY, input.endpoint);
|
|
229
|
+
return await next(input);
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
125
234
|
public async startChildWorkflowExecution(
|
|
126
235
|
input: StartChildWorkflowExecutionInput,
|
|
127
236
|
next: Next<WorkflowOutboundCallsInterceptor, 'startChildWorkflowExecution'>
|
|
@@ -130,7 +239,9 @@ export class OpenTelemetryOutboundInterceptor implements WorkflowOutboundCallsIn
|
|
|
130
239
|
tracer: this.tracer,
|
|
131
240
|
spanName: `${SpanName.CHILD_WORKFLOW_START}${SPAN_DELIMITER}${input.workflowType}`,
|
|
132
241
|
fn: async () => {
|
|
133
|
-
const headers =
|
|
242
|
+
const headers = headersWithContext(input.headers);
|
|
243
|
+
if (!hasSdkFlag('OpenTelemetryInterceporsAvoidsExtraYields')) await Promise.resolve();
|
|
244
|
+
|
|
134
245
|
return next({
|
|
135
246
|
...input,
|
|
136
247
|
headers,
|
|
@@ -143,11 +254,14 @@ export class OpenTelemetryOutboundInterceptor implements WorkflowOutboundCallsIn
|
|
|
143
254
|
input: ContinueAsNewInput,
|
|
144
255
|
next: Next<WorkflowOutboundCallsInterceptor, 'continueAsNew'>
|
|
145
256
|
): Promise<never> {
|
|
257
|
+
const { ContinueAsNew } = getWorkflowModule();
|
|
146
258
|
return await instrument({
|
|
147
259
|
tracer: this.tracer,
|
|
148
260
|
spanName: `${SpanName.CONTINUE_AS_NEW}${SPAN_DELIMITER}${input.options.workflowType}`,
|
|
149
261
|
fn: async () => {
|
|
150
|
-
const headers =
|
|
262
|
+
const headers = headersWithContext(input.headers);
|
|
263
|
+
if (!hasSdkFlag('OpenTelemetryInterceporsAvoidsExtraYields')) await Promise.resolve();
|
|
264
|
+
|
|
151
265
|
return next({
|
|
152
266
|
...input,
|
|
153
267
|
headers,
|
|
@@ -165,7 +279,9 @@ export class OpenTelemetryOutboundInterceptor implements WorkflowOutboundCallsIn
|
|
|
165
279
|
tracer: this.tracer,
|
|
166
280
|
spanName: `${SpanName.WORKFLOW_SIGNAL}${SPAN_DELIMITER}${input.signalName}`,
|
|
167
281
|
fn: async () => {
|
|
168
|
-
const headers =
|
|
282
|
+
const headers = headersWithContext(input.headers);
|
|
283
|
+
if (!hasSdkFlag('OpenTelemetryInterceporsAvoidsExtraYields')) await Promise.resolve();
|
|
284
|
+
|
|
169
285
|
return next({
|
|
170
286
|
...input,
|
|
171
287
|
headers,
|
|
@@ -191,6 +307,24 @@ export class OpenTelemetryOutboundInterceptor implements WorkflowOutboundCallsIn
|
|
|
191
307
|
return next(input);
|
|
192
308
|
}
|
|
193
309
|
}
|
|
310
|
+
|
|
311
|
+
public getMetricTags(
|
|
312
|
+
input: GetMetricTagsInput,
|
|
313
|
+
next: Next<WorkflowOutboundCallsInterceptor, 'getMetricTags'>
|
|
314
|
+
): GetMetricTagsInput {
|
|
315
|
+
const span = otel.trace.getSpan(otel.context.active());
|
|
316
|
+
const spanContext = span?.spanContext();
|
|
317
|
+
if (spanContext && otel.isSpanContextValid(spanContext)) {
|
|
318
|
+
return next({
|
|
319
|
+
trace_id: spanContext.traceId,
|
|
320
|
+
span_id: spanContext.spanId,
|
|
321
|
+
trace_flags: `0${spanContext.traceFlags.toString(16)}`,
|
|
322
|
+
...input,
|
|
323
|
+
});
|
|
324
|
+
} else {
|
|
325
|
+
return next(input);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
194
328
|
}
|
|
195
329
|
|
|
196
330
|
export class OpenTelemetryInternalsInterceptor implements WorkflowInternalsInterceptor {
|
package/src/workflow/runtime.ts
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
* Sets global variables required for importing opentelemetry in isolate
|
|
3
3
|
* @module
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { getWorkflowModuleIfAvailable } from './workflow-module-loader';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
const inWorkflowContext = getWorkflowModuleIfAvailable()?.inWorkflowContext;
|
|
8
|
+
|
|
9
|
+
if (inWorkflowContext?.()) {
|
|
8
10
|
// Required by opentelemetry (pretend to be a browser)
|
|
9
11
|
Object.assign(globalThis, {
|
|
10
12
|
performance: {
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import * as tracing from '@opentelemetry/sdk-trace-base';
|
|
2
2
|
import { ExportResult, ExportResultCode } from '@opentelemetry/core';
|
|
3
|
-
import * as wf from '@temporalio/workflow';
|
|
4
3
|
import { OpenTelemetrySinks, SerializableSpan } from './definitions';
|
|
4
|
+
import { ensureWorkflowModuleLoaded, getWorkflowModuleIfAvailable } from './workflow-module-loader';
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const exporter = getWorkflowModuleIfAvailable()?.proxySinks<OpenTelemetrySinks>()?.exporter;
|
|
7
7
|
|
|
8
8
|
export class SpanExporter implements tracing.SpanExporter {
|
|
9
|
+
public constructor() {
|
|
10
|
+
ensureWorkflowModuleLoaded();
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
public export(spans: tracing.ReadableSpan[], resultCallback: (result: ExportResult) => void): void {
|
|
10
|
-
exporter
|
|
14
|
+
exporter!.export(spans.map((span) => this.makeSerializable(span)));
|
|
11
15
|
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
12
16
|
}
|
|
13
17
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for working with a possibly missing `@temporalio/workflow` peer dependency
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
import type * as WorkflowModule from '@temporalio/workflow';
|
|
6
|
+
import type { SdkFlags as SdkFlagsT } from '@temporalio/workflow/lib/flags';
|
|
7
|
+
|
|
8
|
+
type SdkFlags = typeof SdkFlagsT;
|
|
9
|
+
|
|
10
|
+
// @temporalio/workflow is an optional peer dependency.
|
|
11
|
+
// It can be missing as long as the user isn't attempting to construct a workflow interceptor.
|
|
12
|
+
// If we start shipping ES modules alongside CJS, we will have to reconsider
|
|
13
|
+
// this dynamic import as `import` is async for ES modules.
|
|
14
|
+
let workflowModule: typeof WorkflowModule | undefined;
|
|
15
|
+
let workflowModuleLoadError: any | undefined;
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
19
|
+
workflowModule = require('@temporalio/workflow');
|
|
20
|
+
} catch (err) {
|
|
21
|
+
// Capture the module not found error to rethrow if the module is required
|
|
22
|
+
workflowModuleLoadError = err;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Returns `@temporalio/workflow` module if present.
|
|
27
|
+
* Throws if the module failed to load
|
|
28
|
+
*/
|
|
29
|
+
export function getWorkflowModule(): typeof WorkflowModule {
|
|
30
|
+
if (workflowModuleLoadError) {
|
|
31
|
+
throw workflowModuleLoadError;
|
|
32
|
+
}
|
|
33
|
+
return workflowModule!;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Returns if an SDK flag was set
|
|
38
|
+
*
|
|
39
|
+
* Expects to be called only after `ensureWorkflowModuleLoaded`.
|
|
40
|
+
* Will throw if `@temporalio/workflow` is not available
|
|
41
|
+
*/
|
|
42
|
+
export function hasSdkFlag(flag: keyof SdkFlags): boolean {
|
|
43
|
+
const { SdkFlags } = require('@temporalio/workflow/lib/flags') as typeof import('@temporalio/workflow/lib/flags'); // eslint-disable-line @typescript-eslint/no-require-imports
|
|
44
|
+
const { getActivator } =
|
|
45
|
+
require('@temporalio/workflow/lib/global-attributes') as typeof import('@temporalio/workflow/lib/global-attributes'); // eslint-disable-line @typescript-eslint/no-require-imports
|
|
46
|
+
|
|
47
|
+
return getActivator().hasFlag(SdkFlags[flag]);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Checks if the workflow module loaded successfully and throws if not.
|
|
52
|
+
*/
|
|
53
|
+
export function ensureWorkflowModuleLoaded(): void {
|
|
54
|
+
if (workflowModuleLoadError) {
|
|
55
|
+
throw workflowModuleLoadError;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Returns the workflow module if available, or undefined if it failed to load.
|
|
61
|
+
*/
|
|
62
|
+
export function getWorkflowModuleIfAvailable(): typeof WorkflowModule | undefined {
|
|
63
|
+
return workflowModule;
|
|
64
|
+
}
|