Skip to content

Commit 9e2bfad

Browse files
committed
addressing pr feedback:
- symbol usage to mark if connection supports ews - separate file for interceptor adapters, adapt wf client interceptors on wf client creation - deprecate `start` wf client interceptor in favor of `startWithDetails`
1 parent bf336fb commit 9e2bfad

File tree

6 files changed

+83
-47
lines changed

6 files changed

+83
-47
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { WorkflowClientInterceptor, WorkflowStartInput, WorkflowStartOutput } from './interceptors';
2+
3+
// Apply adapters to workflow client interceptor.
4+
export function adaptWorkflowClientInterceptor(i: WorkflowClientInterceptor): WorkflowClientInterceptor {
5+
return adaptLegacyStartInterceptor(i);
6+
}
7+
8+
// Adapt legacy `start` interceptors to the new `startWithDetails` interceptor.
9+
function adaptLegacyStartInterceptor(i: WorkflowClientInterceptor): WorkflowClientInterceptor {
10+
// If it already has the new method, or doesn't have the legacy one, no adaptation is needed.
11+
// eslint-disable-next-line deprecation/deprecation
12+
if (i.startWithDetails || !i.start) {
13+
return i;
14+
}
15+
16+
// This interceptor has a legacy `start` but not `startWithDetails`. We'll adapt it.
17+
return {
18+
...i,
19+
startWithDetails: async (input, next): Promise<WorkflowStartOutput> => {
20+
let downstreamOut: WorkflowStartOutput | undefined;
21+
22+
// Patched `next` for legacy `start` interceptors.
23+
// Captures the full `WorkflowStartOutput` while returning `runId` as a string.
24+
const patchedNext = async (patchedInput: WorkflowStartInput): Promise<string> => {
25+
downstreamOut = await next(patchedInput);
26+
return downstreamOut.runId;
27+
};
28+
29+
const runIdFromLegacyInterceptor = await i.start!(input, patchedNext); // eslint-disable-line deprecation/deprecation
30+
31+
// If the interceptor short-circuited (didn't call `next`), `downstreamOut` will be undefined.
32+
// In that case, we can't have an eager start.
33+
if (downstreamOut === undefined) {
34+
return { runId: runIdFromLegacyInterceptor, eagerlyStarted: false };
35+
}
36+
37+
// If `next` was called, honor the `runId` from the legacy interceptor but preserve
38+
// the `eagerlyStarted` status from the actual downstream call.
39+
return { ...downstreamOut, runId: runIdFromLegacyInterceptor };
40+
},
41+
};
42+
}

packages/client/src/interceptors.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ export interface WorkflowClientInterceptor {
124124
*
125125
* If you implement this method,
126126
* {@link signalWithStart} most likely needs to be implemented too
127+
*
128+
* @deprecated in favour of {@link startWithDetails}
127129
*/
128130
start?: (input: WorkflowStartInput, next: Next<this, 'start'>) => Promise<string /* runId */>;
129131

packages/client/src/types.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,14 +163,19 @@ export interface ConnectionLike {
163163
* @see https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
164164
*/
165165
withAbortSignal<R>(abortSignal: AbortSignal, fn: () => Promise<R>): Promise<R>;
166-
167-
/**
168-
* Capability flag that determines whether the connection supports eager workflow start.
169-
* This will only be true if the underlying connection is a {@link NativeConnection}.
170-
*/
171-
readonly supportsEagerStart?: boolean;
172166
}
173167

168+
export const InternalConnectionLikeSymbol = Symbol('__temporal_internal_connection_like');
169+
export type InternalConnectionLike = ConnectionLike & {
170+
[InternalConnectionLikeSymbol]?: {
171+
/**
172+
* Capability flag that determines whether the connection supports eager workflow start.
173+
* This will only be true if the underlying connection is a {@link NativeConnection}.
174+
*/
175+
readonly supportsEagerStart?: boolean;
176+
};
177+
};
178+
174179
export const QueryRejectCondition = {
175180
NONE: 'NONE',
176181
NOT_OPEN: 'NOT_OPEN',

packages/client/src/workflow-client.ts

Lines changed: 12 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ import {
6868
DescribeWorkflowExecutionResponse,
6969
encodeQueryRejectCondition,
7070
GetWorkflowExecutionHistoryRequest,
71+
InternalConnectionLike,
72+
InternalConnectionLikeSymbol,
7173
QueryRejectCondition,
7274
RequestCancelWorkflowExecutionResponse,
7375
StartWorkflowExecutionRequest,
@@ -94,6 +96,7 @@ import {
9496
} from './base-client';
9597
import { mapAsyncIterable } from './iterators-utils';
9698
import { WorkflowUpdateStage, encodeWorkflowUpdateStage } from './workflow-update-stage';
99+
import { adaptWorkflowClientInterceptor } from './interceptor-adapters';
97100

98101
const UpdateWorkflowExecutionLifecycleStage = temporal.api.enums.v1.UpdateWorkflowExecutionLifecycleStage;
99102

@@ -528,43 +531,8 @@ export class WorkflowClient extends BaseClient {
528531
assertRequiredWorkflowOptions(options);
529532
const compiledOptions = compileWorkflowOptions(ensureArgs(options));
530533

531-
// Adapt legacy `start` interceptors to the new `startWithDetails` interface.
532-
const adaptedInterceptors: WorkflowClientInterceptor[] = interceptors.map((i) => {
533-
// If it already has the new method, or doesn't have the legacy one, no adaptation is needed.
534-
if (i.startWithDetails || !i.start) {
535-
return i;
536-
}
537-
538-
// This interceptor has a legacy `start` but not `startWithDetails`. We'll adapt it.
539-
return {
540-
...i,
541-
startWithDetails: async (input, next): Promise<WorkflowStartOutput> => {
542-
let downstreamOut: WorkflowStartOutput | undefined;
543-
544-
// Patched `next` for legacy `start` interceptors.
545-
// Captures the full `WorkflowStartOutput` while returning `runId` as a string.
546-
const patchedNext = async (patchedInput: WorkflowStartInput): Promise<string> => {
547-
downstreamOut = await next(patchedInput);
548-
return downstreamOut.runId;
549-
};
550-
551-
const runIdFromLegacyInterceptor = await i.start!(input, patchedNext);
552-
553-
// If the interceptor short-circuited (didn't call `next`), `downstreamOut` will be undefined.
554-
// In that case, we can't have an eager start.
555-
if (downstreamOut === undefined) {
556-
return { runId: runIdFromLegacyInterceptor, eagerlyStarted: false };
557-
}
558-
559-
// If `next` was called, honor the `runId` from the legacy interceptor but preserve
560-
// the `eagerlyStarted` status from the actual downstream call.
561-
return { ...downstreamOut, runId: runIdFromLegacyInterceptor };
562-
},
563-
};
564-
});
565-
566534
const startWithDetails = composeInterceptors(
567-
adaptedInterceptors,
535+
interceptors,
568536
'startWithDetails',
569537
this._startWorkflowHandler.bind(this)
570538
);
@@ -1320,8 +1288,10 @@ export class WorkflowClient extends BaseClient {
13201288
protected async createStartWorkflowRequest(input: WorkflowStartInput): Promise<StartWorkflowExecutionRequest> {
13211289
const { options: opts, workflowType, headers } = input;
13221290
const { identity, namespace } = this.options;
1291+
const supportsEagerStart = (this.connection as InternalConnectionLike)?.[InternalConnectionLikeSymbol]
1292+
?.supportsEagerStart;
13231293

1324-
if (opts.requestEagerStart && !('supportsEagerStart' in this.connection && this.connection.supportsEagerStart)) {
1294+
if (opts.requestEagerStart && !supportsEagerStart) {
13251295
throw new Error(
13261296
'Eager workflow start requires a NativeConnection shared between client and worker. ' +
13271297
'Pass a NativeConnection via ClientOptions.connection, or disable requestEagerStart.'
@@ -1674,7 +1644,11 @@ export class WorkflowClient extends BaseClient {
16741644
const factories = (this.options.interceptors as WorkflowClientInterceptors).calls ?? [];
16751645
return factories.map((ctor) => ctor({ workflowId, runId }));
16761646
}
1677-
return Array.isArray(this.options.interceptors) ? (this.options.interceptors as WorkflowClientInterceptor[]) : [];
1647+
const interceptors = Array.isArray(this.options.interceptors)
1648+
? (this.options.interceptors as WorkflowClientInterceptor[])
1649+
: [];
1650+
// Apply adapters to workflow client interceptors.
1651+
return interceptors.map((i) => adaptWorkflowClientInterceptor(i));
16781652
}
16791653
}
16801654

packages/test/src/test-integration-workflows.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { randomUUID } from 'crypto';
33
import asyncRetry from 'async-retry';
44
import { ExecutionContext } from 'ava';
55
import { firstValueFrom, Subject } from 'rxjs';
6-
import { Client, Connection, WorkflowClient, WorkflowFailedError, WorkflowHandle } from '@temporalio/client';
6+
import { Client, WorkflowClient, WorkflowFailedError, WorkflowHandle } from '@temporalio/client';
77
import * as activity from '@temporalio/activity';
88
import { msToNumber, tsToMs } from '@temporalio/common/lib/time';
99
import { TestWorkflowEnvironment } from '@temporalio/testing';

packages/worker/src/connection.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ import * as grpc from '@grpc/grpc-js';
33
import * as proto from 'protobufjs';
44
import { IllegalStateError } from '@temporalio/common';
55
import { native } from '@temporalio/core-bridge';
6-
import { ConnectionLike, Metadata, CallContext, WorkflowService } from '@temporalio/client';
6+
import {
7+
ConnectionLike,
8+
Metadata,
9+
CallContext,
10+
WorkflowService,
11+
InternalConnectionLikeSymbol,
12+
} from '@temporalio/client';
713
import { TransportError } from './errors';
814
import { NativeConnectionOptions } from './connection-options';
915
import { Runtime } from './runtime';
@@ -16,7 +22,6 @@ import { Runtime } from './runtime';
1622
* This class can be used to power `@temporalio/client`'s Client objects.
1723
*/
1824
export class NativeConnection implements ConnectionLike {
19-
public readonly supportsEagerStart = true;
2025
/**
2126
* referenceHolders is used internally by the framework, it can be accessed with `extractReferenceHolders` (below)
2227
*/
@@ -33,6 +38,14 @@ export class NativeConnection implements ConnectionLike {
3338
private readonly nativeClient: native.Client
3439
) {
3540
this.workflowService = WorkflowService.create(this.sendRequest.bind(this), false, false);
41+
42+
// Set internal capability flag - not part of public API
43+
Object.defineProperty(this, InternalConnectionLikeSymbol, {
44+
value: { supportsEagerStart: true },
45+
writable: false,
46+
enumerable: false,
47+
configurable: false,
48+
});
3649
}
3750

3851
/**

0 commit comments

Comments
 (0)