Skip to content

Commit fefe6bd

Browse files
make peer dependencies optional
1 parent 16c6fa3 commit fefe6bd

File tree

5 files changed

+100
-11
lines changed

5 files changed

+100
-11
lines changed

packages/interceptors-opentelemetry/package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@
3333
"@temporalio/worker": "file:../worker",
3434
"@temporalio/workflow": "file:../workflow"
3535
},
36+
"peerDependenciesMeta": {
37+
"@temporalio/activity": {
38+
"optional": true
39+
},
40+
"@temporalio/client": {
41+
"optional": true
42+
},
43+
"@temporalio/worker": {
44+
"optional": true
45+
},
46+
"@temporalio/workflow": {
47+
"optional": true
48+
}
49+
},
3650
"bugs": {
3751
"url": "https://github.com/temporalio/sdk-typescript/issues"
3852
},

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

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,28 @@
11
import * as otel from '@opentelemetry/api';
2-
import { AsyncLocalStorage } from '@temporalio/workflow';
2+
import type { AsyncLocalStorage as AsyncLocalStorageType } from '@temporalio/workflow';
3+
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+
}
314

415
export class ContextManager implements otel.ContextManager {
5-
protected storage = new AsyncLocalStorage<otel.Context>();
16+
protected storage = AsyncLocalStorage ? new AsyncLocalStorage<otel.Context>() : undefined;
17+
18+
public constructor() {
19+
if (workflowModuleLoadError) {
20+
throw workflowModuleLoadError;
21+
}
22+
}
623

724
active(): otel.Context {
8-
return this.storage.getStore() || otel.ROOT_CONTEXT;
25+
return this.storage!.getStore() || otel.ROOT_CONTEXT;
926
}
1027

1128
bind<T>(context: otel.Context, target: T): T {
@@ -36,7 +53,7 @@ export class ContextManager implements otel.ContextManager {
3653
}
3754

3855
disable(): this {
39-
this.storage.disable();
56+
this.storage!.disable();
4057
return this;
4158
}
4259

@@ -47,6 +64,6 @@ export class ContextManager implements otel.ContextManager {
4764
...args: A
4865
): ReturnType<F> {
4966
const cb = thisArg == null ? fn : fn.bind(thisArg);
50-
return this.storage.run(context, cb, ...args);
67+
return this.storage!.run(context, cb, ...args);
5168
}
5269
}

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,24 @@ import type {
1818
WorkflowInternalsInterceptor,
1919
WorkflowOutboundCallsInterceptor,
2020
} from '@temporalio/workflow';
21-
import { ContinueAsNew, workflowInfo } from '@temporalio/workflow';
2221
import { instrument, extractContextFromHeaders, headersWithContext } from '../instrumentation';
2322
import { ContextManager } from './context-manager';
2423
import { SpanName, SPAN_DELIMITER } from './definitions';
2524
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+
}
2639

2740
export * from './definitions';
2841

@@ -51,10 +64,18 @@ function getTracer(): otel.Tracer {
5164
export class OpenTelemetryInboundInterceptor implements WorkflowInboundCallsInterceptor {
5265
protected readonly tracer = getTracer();
5366

67+
public constructor() {
68+
if (workflowModuleLoadError) {
69+
throw workflowModuleLoadError;
70+
}
71+
}
72+
5473
public async execute(
5574
input: WorkflowExecuteInput,
5675
next: Next<WorkflowInboundCallsInterceptor, 'execute'>
5776
): Promise<unknown> {
77+
// workflowModule will be defined as module load error is checked in the constructor
78+
const { workflowInfo, ContinueAsNew } = workflowModule!;
5879
const context = await extractContextFromHeaders(input.headers);
5980
return await instrument({
6081
tracer: this.tracer,
@@ -87,6 +108,12 @@ export class OpenTelemetryInboundInterceptor implements WorkflowInboundCallsInte
87108
export class OpenTelemetryOutboundInterceptor implements WorkflowOutboundCallsInterceptor {
88109
protected readonly tracer = getTracer();
89110

111+
public constructor() {
112+
if (workflowModuleLoadError) {
113+
throw workflowModuleLoadError;
114+
}
115+
}
116+
90117
public async scheduleActivity(
91118
input: ActivityInput,
92119
next: Next<WorkflowOutboundCallsInterceptor, 'scheduleActivity'>
@@ -142,6 +169,8 @@ export class OpenTelemetryOutboundInterceptor implements WorkflowOutboundCallsIn
142169
input: ContinueAsNewInput,
143170
next: Next<WorkflowOutboundCallsInterceptor, 'continueAsNew'>
144171
): Promise<never> {
172+
// workflowModule will be defined as module load error is checked in the constructor
173+
const { ContinueAsNew } = workflowModule!;
145174
return await instrument({
146175
tracer: this.tracer,
147176
spanName: `${SpanName.CONTINUE_AS_NEW}${SPAN_DELIMITER}${input.options.workflowType}`,

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

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

7-
if (inWorkflowContext()) {
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+
}
15+
16+
if (inWorkflowContext && inWorkflowContext()) {
817
// Required by opentelemetry (pretend to be a browser)
918
Object.assign(globalThis, {
1019
performance: {

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

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,33 @@
11
import * as tracing from '@opentelemetry/sdk-trace-base';
22
import { ExportResult, ExportResultCode } from '@opentelemetry/core';
3-
import * as wf from '@temporalio/workflow';
43
import { OpenTelemetrySinks, SerializableSpan } from './definitions';
54

6-
const { exporter } = wf.proxySinks<OpenTelemetrySinks>();
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;
721

822
export class SpanExporter implements tracing.SpanExporter {
23+
public constructor() {
24+
if (workflowModuleLoadError) {
25+
throw workflowModuleLoadError;
26+
}
27+
}
28+
929
public export(spans: tracing.ReadableSpan[], resultCallback: (result: ExportResult) => void): void {
10-
exporter.export(spans.map((span) => this.makeSerializable(span)));
30+
exporter!.export(spans.map((span) => this.makeSerializable(span)));
1131
resultCallback({ code: ExportResultCode.SUCCESS });
1232
}
1333

0 commit comments

Comments
 (0)