Skip to content

Commit 3e6dfb5

Browse files
chore: centeralize @temporalio/workflow loading
1 parent fefe6bd commit 3e6dfb5

File tree

5 files changed

+62
-67
lines changed

5 files changed

+62
-67
lines changed

packages/interceptors-opentelemetry/src/workflow/context-manager.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,15 @@
11
import * as otel from '@opentelemetry/api';
2-
import type { AsyncLocalStorage as AsyncLocalStorageType } from '@temporalio/workflow';
2+
import { ensureWorkflowModuleLoaded, getWorkflowModuleIfAvailable } from './workflow-module-loader';
33

4-
// @temporalio/workflow is an optional peer dependency.
5-
// It can be missing as long as the user isn't attempting to construct a workflow interceptor.
6-
let AsyncLocalStorage: typeof AsyncLocalStorageType | undefined;
7-
let workflowModuleLoadError: any | undefined;
8-
try {
9-
AsyncLocalStorage = require('@temporalio/workflow').AsyncLocalStorage;
10-
} catch (err) {
11-
// Capture the module not found error to rethrow if an interceptor is constructed
12-
workflowModuleLoadError = err;
13-
}
4+
const AsyncLocalStorage = getWorkflowModuleIfAvailable()?.AsyncLocalStorage;
145

156
export class ContextManager implements otel.ContextManager {
7+
// If `@temporalio/workflow` is not available, ignore for now.
8+
// When ContextManager is constructed module resolution error will be thrown.
169
protected storage = AsyncLocalStorage ? new AsyncLocalStorage<otel.Context>() : undefined;
1710

1811
public constructor() {
19-
if (workflowModuleLoadError) {
20-
throw workflowModuleLoadError;
21-
}
12+
ensureWorkflowModuleLoaded();
2213
}
2314

2415
active(): otel.Context {

packages/interceptors-opentelemetry/src/workflow/index.ts

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,7 @@ import { instrument, extractContextFromHeaders, headersWithContext } from '../in
2222
import { ContextManager } from './context-manager';
2323
import { SpanName, SPAN_DELIMITER } from './definitions';
2424
import { SpanExporter } from './span-exporter';
25-
import type * as WorkflowModule from '@temporalio/workflow';
26-
27-
// @temporalio/workflow is an optional peer dependency.
28-
// It can be missing as long as the user isn't attempting to construct a workflow interceptor.
29-
// If we start shipping ES modules alongside CJS, we will have to reconsider
30-
// this dynamic import as `import` is async for ES modules.
31-
let workflowModule: typeof WorkflowModule | undefined;
32-
let workflowModuleLoadError: any | undefined;
33-
try {
34-
workflowModule = require('@temporalio/workflow');
35-
} catch (err) {
36-
// Capture the module not found error to rethrow if an interceptor is constructed
37-
workflowModuleLoadError = err;
38-
}
25+
import { ensureWorkflowModuleLoaded, getWorkflowModule } from './workflow-module-loader';
3926

4027
export * from './definitions';
4128

@@ -65,17 +52,14 @@ export class OpenTelemetryInboundInterceptor implements WorkflowInboundCallsInte
6552
protected readonly tracer = getTracer();
6653

6754
public constructor() {
68-
if (workflowModuleLoadError) {
69-
throw workflowModuleLoadError;
70-
}
55+
ensureWorkflowModuleLoaded();
7156
}
7257

7358
public async execute(
7459
input: WorkflowExecuteInput,
7560
next: Next<WorkflowInboundCallsInterceptor, 'execute'>
7661
): Promise<unknown> {
77-
// workflowModule will be defined as module load error is checked in the constructor
78-
const { workflowInfo, ContinueAsNew } = workflowModule!;
62+
const { workflowInfo, ContinueAsNew } = getWorkflowModule();
7963
const context = await extractContextFromHeaders(input.headers);
8064
return await instrument({
8165
tracer: this.tracer,
@@ -109,9 +93,7 @@ export class OpenTelemetryOutboundInterceptor implements WorkflowOutboundCallsIn
10993
protected readonly tracer = getTracer();
11094

11195
public constructor() {
112-
if (workflowModuleLoadError) {
113-
throw workflowModuleLoadError;
114-
}
96+
ensureWorkflowModuleLoaded();
11597
}
11698

11799
public async scheduleActivity(
@@ -169,8 +151,7 @@ export class OpenTelemetryOutboundInterceptor implements WorkflowOutboundCallsIn
169151
input: ContinueAsNewInput,
170152
next: Next<WorkflowOutboundCallsInterceptor, 'continueAsNew'>
171153
): Promise<never> {
172-
// workflowModule will be defined as module load error is checked in the constructor
173-
const { ContinueAsNew } = workflowModule!;
154+
const { ContinueAsNew } = getWorkflowModule();
174155
return await instrument({
175156
tracer: this.tracer,
176157
spanName: `${SpanName.CONTINUE_AS_NEW}${SPAN_DELIMITER}${input.options.workflowType}`,

packages/interceptors-opentelemetry/src/workflow/runtime.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,11 @@
22
* Sets global variables required for importing opentelemetry in isolate
33
* @module
44
*/
5-
import type { inWorkflowContext as InWorkflowContext } from '@temporalio/workflow';
5+
import { getWorkflowModuleIfAvailable } from './workflow-module-loader';
66

7-
// @temporalio/workflow is an optional peer dependency and might not be available.
8-
// If it is not available, we can assume that we are not in a workflow context.
9-
let inWorkflowContext: typeof InWorkflowContext | undefined;
10-
try {
11-
inWorkflowContext = require('@temporalio/workflow').inWorkflowContext;
12-
} catch (_) {
13-
inWorkflowContext = undefined;
14-
}
7+
const inWorkflowContext = getWorkflowModuleIfAvailable()?.inWorkflowContext;
158

16-
if (inWorkflowContext && inWorkflowContext()) {
9+
if (inWorkflowContext?.()) {
1710
// Required by opentelemetry (pretend to be a browser)
1811
Object.assign(globalThis, {
1912
performance: {

packages/interceptors-opentelemetry/src/workflow/span-exporter.ts

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,13 @@
11
import * as tracing from '@opentelemetry/sdk-trace-base';
22
import { ExportResult, ExportResultCode } from '@opentelemetry/core';
33
import { OpenTelemetrySinks, SerializableSpan } from './definitions';
4+
import { ensureWorkflowModuleLoaded, getWorkflowModuleIfAvailable } from './workflow-module-loader';
45

5-
import type * as WorkflowModule from '@temporalio/workflow';
6-
7-
// @temporalio/workflow is an optional peer dependency.
8-
// It can be missing as long as the user isn't attempting to construct a workflow interceptor.
9-
// If we start shipping ES modules alongside CJS, we will have to reconsider
10-
// this dynamic import as `import` is async for ES modules.
11-
let wf: typeof WorkflowModule | undefined;
12-
let workflowModuleLoadError: any | undefined;
13-
try {
14-
wf = require('@temporalio/workflow');
15-
} catch (err) {
16-
// Capture the module not found error to rethrow if an interceptor is constructed
17-
workflowModuleLoadError = err;
18-
}
19-
20-
const exporter = wf?.proxySinks<OpenTelemetrySinks>()?.exporter;
6+
const exporter = getWorkflowModuleIfAvailable()?.proxySinks<OpenTelemetrySinks>()?.exporter;
217

228
export class SpanExporter implements tracing.SpanExporter {
239
public constructor() {
24-
if (workflowModuleLoadError) {
25-
throw workflowModuleLoadError;
26-
}
10+
ensureWorkflowModuleLoaded();
2711
}
2812

2913
public export(spans: tracing.ReadableSpan[], resultCallback: (result: ExportResult) => void): void {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
7+
// @temporalio/workflow is an optional peer dependency.
8+
// It can be missing as long as the user isn't attempting to construct a workflow interceptor.
9+
// If we start shipping ES modules alongside CJS, we will have to reconsider
10+
// this dynamic import as `import` is async for ES modules.
11+
let workflowModule: typeof WorkflowModule | undefined;
12+
let workflowModuleLoadError: any | undefined;
13+
14+
try {
15+
workflowModule = require('@temporalio/workflow');
16+
} catch (err) {
17+
// Capture the module not found error to rethrow if the module is required
18+
workflowModuleLoadError = err;
19+
}
20+
21+
/**
22+
* Returns `@temporalio/workflow` module if present.
23+
* Throws if the module failed to load
24+
*/
25+
export function getWorkflowModule(): typeof WorkflowModule {
26+
if (workflowModuleLoadError) {
27+
throw workflowModuleLoadError;
28+
}
29+
return workflowModule!;
30+
}
31+
32+
/**
33+
* Checks if the workflow module loaded successfully and throws if not.
34+
*/
35+
export function ensureWorkflowModuleLoaded(): void {
36+
if (workflowModuleLoadError) {
37+
throw workflowModuleLoadError;
38+
}
39+
}
40+
41+
/**
42+
* Returns the workflow module if available, or undefined if it failed to load.
43+
*/
44+
export function getWorkflowModuleIfAvailable(): typeof WorkflowModule | undefined {
45+
return workflowModule;
46+
}

0 commit comments

Comments
 (0)