Skip to content

Commit 9a9c0a9

Browse files
authored
feat: Normalize construction of user facing enums (#1534)
1 parent 89be276 commit 9a9c0a9

26 files changed

+1395
-293
lines changed

packages/client/src/client.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { filterNullAndUndefined } from '@temporalio/common/lib/internal-non-workflow';
2-
import { temporal } from '@temporalio/proto';
32
import { AsyncCompletionClient } from './async-completion-client';
43
import { BaseClient, BaseClientOptions, defaultBaseClientOptions, LoadedWithDefaults } from './base-client';
54
import { ClientInterceptors } from './interceptors';
65
import { ScheduleClient } from './schedule-client';
7-
import { WorkflowService } from './types';
6+
import { QueryRejectCondition, WorkflowService } from './types';
87
import { WorkflowClient } from './workflow-client';
98
import { TaskQueueClient } from './task-queue-client';
109

@@ -20,9 +19,9 @@ export interface ClientOptions extends BaseClientOptions {
2019
/**
2120
* Should a query be rejected by closed and failed workflows
2221
*
23-
* @default QUERY_REJECT_CONDITION_UNSPECIFIED which means that closed and failed workflows are still queryable
22+
* @default `undefined`, which means that closed and failed workflows are still queryable
2423
*/
25-
queryRejectCondition?: temporal.api.enums.v1.QueryRejectCondition;
24+
queryRejectCondition?: QueryRejectCondition;
2625
};
2726
}
2827

@@ -63,6 +62,7 @@ export class Client extends BaseClient {
6362
connection: this.connection,
6463
dataConverter: this.dataConverter,
6564
interceptors: interceptors?.workflow,
65+
queryRejectCondition: workflow?.queryRejectCondition,
6666
});
6767

6868
this.activity = new AsyncCompletionClient({

packages/client/src/schedule-client.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,17 @@ import {
2929
ScheduleUpdateOptions,
3030
ScheduleOptionsAction,
3131
ScheduleOptionsStartWorkflowAction,
32+
encodeScheduleOverlapPolicy,
33+
decodeScheduleOverlapPolicy,
3234
} from './schedule-types';
3335
import {
3436
compileScheduleOptions,
3537
compileUpdatedScheduleOptions,
36-
decodeOverlapPolicy,
3738
decodeScheduleAction,
3839
decodeScheduleRecentActions,
3940
decodeScheduleRunningActions,
4041
decodeScheduleSpec,
4142
decodeSearchAttributes,
42-
encodeOverlapPolicy,
4343
encodeScheduleAction,
4444
encodeSchedulePolicies,
4545
encodeScheduleSpec,
@@ -251,7 +251,7 @@ export class ScheduleClient extends BaseClient {
251251
? opts.state.backfill.map((x) => ({
252252
startTime: optionalDateToTs(x.start),
253253
endTime: optionalDateToTs(x.end),
254-
overlapPolicy: x.overlap ? encodeOverlapPolicy(x.overlap) : undefined,
254+
overlapPolicy: x.overlap ? encodeScheduleOverlapPolicy(x.overlap) : undefined,
255255
}))
256256
: undefined,
257257
},
@@ -427,7 +427,8 @@ export class ScheduleClient extends BaseClient {
427427
memo: await decodeMapFromPayloads(this.client.dataConverter, raw.memo?.fields),
428428
searchAttributes: decodeSearchAttributes(raw.searchAttributes),
429429
policies: {
430-
overlap: decodeOverlapPolicy(raw.schedule.policies?.overlapPolicy),
430+
// 'overlap' should never be missing on describe, as the server will replace UNSPECIFIED by an actual value
431+
overlap: decodeScheduleOverlapPolicy(raw.schedule.policies?.overlapPolicy) ?? ScheduleOverlapPolicy.SKIP,
431432
catchupWindow: optionalTsToMs(raw.schedule.policies?.catchupWindow) ?? 60_000,
432433
pauseOnFailure: raw.schedule.policies?.pauseOnFailure === true,
433434
},
@@ -485,7 +486,7 @@ export class ScheduleClient extends BaseClient {
485486
await this.client._patchSchedule(this.scheduleId, {
486487
triggerImmediately: {
487488
overlapPolicy: overlap
488-
? encodeOverlapPolicy(overlap)
489+
? encodeScheduleOverlapPolicy(overlap)
489490
: temporal.api.enums.v1.ScheduleOverlapPolicy.SCHEDULE_OVERLAP_POLICY_ALLOW_ALL,
490491
},
491492
});
@@ -497,7 +498,7 @@ export class ScheduleClient extends BaseClient {
497498
backfillRequest: backfills.map((x) => ({
498499
startTime: optionalDateToTs(x.start),
499500
endTime: optionalDateToTs(x.end),
500-
overlapPolicy: x.overlap ? encodeOverlapPolicy(x.overlap) : undefined,
501+
overlapPolicy: x.overlap ? encodeScheduleOverlapPolicy(x.overlap) : undefined,
501502
})),
502503
});
503504
},

packages/client/src/schedule-helpers.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import {
3232
CompiledScheduleUpdateOptions,
3333
Range,
3434
ScheduleOptions,
35-
ScheduleOverlapPolicy,
3635
ScheduleUpdateOptions,
3736
DayOfWeek,
3837
DAYS_OF_WEEK,
@@ -47,6 +46,7 @@ import {
4746
ScheduleExecutionActionResult,
4847
ScheduleExecutionResult,
4948
ScheduleExecutionStartWorkflowActionResult,
49+
encodeScheduleOverlapPolicy,
5050
} from './schedule-types';
5151

5252
const [encodeSecond, decodeSecond] = makeCalendarSpecFieldCoders(
@@ -192,21 +192,6 @@ export function decodeOptionalStructuredCalendarSpecs(
192192
);
193193
}
194194

195-
export function encodeOverlapPolicy(input: ScheduleOverlapPolicy): temporal.api.enums.v1.ScheduleOverlapPolicy {
196-
return temporal.api.enums.v1.ScheduleOverlapPolicy[
197-
`SCHEDULE_OVERLAP_POLICY_${ScheduleOverlapPolicy[input] as keyof typeof ScheduleOverlapPolicy}`
198-
];
199-
}
200-
201-
export function decodeOverlapPolicy(input?: temporal.api.enums.v1.ScheduleOverlapPolicy | null): ScheduleOverlapPolicy {
202-
if (!input) return ScheduleOverlapPolicy.UNSPECIFIED;
203-
const encodedPolicyName = temporal.api.enums.v1.ScheduleOverlapPolicy[input];
204-
const decodedPolicyName = encodedPolicyName.substring(
205-
'SCHEDULE_OVERLAP_POLICY_'.length
206-
) as keyof typeof ScheduleOverlapPolicy;
207-
return ScheduleOverlapPolicy[decodedPolicyName];
208-
}
209-
210195
export function compileScheduleOptions(options: ScheduleOptions): CompiledScheduleOptions {
211196
const workflowTypeOrFunc = options.action.workflowType;
212197
const workflowType = extractWorkflowType(workflowTypeOrFunc);
@@ -290,7 +275,7 @@ export function encodeSchedulePolicies(
290275
): temporal.api.schedule.v1.ISchedulePolicies {
291276
return {
292277
catchupWindow: msOptionalToTs(policies?.catchupWindow),
293-
overlapPolicy: policies?.overlap ? encodeOverlapPolicy(policies.overlap) : undefined,
278+
overlapPolicy: policies?.overlap ? encodeScheduleOverlapPolicy(policies.overlap) : undefined,
294279
pauseOnFailure: policies?.pauseOnFailure,
295280
};
296281
}

packages/client/src/schedule-types.ts

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { checkExtends, Replace } from '@temporalio/common/lib/type-helpers';
22
import { Duration, SearchAttributes, Workflow } from '@temporalio/common';
3+
import { makeProtoEnumConverters } from '@temporalio/common/lib/internal-workflow';
34
import type { temporal } from '@temporalio/proto';
45
import { WorkflowStartOptions } from './workflow-options';
56

@@ -795,57 +796,69 @@ export type CompiledScheduleAction = Replace<
795796
/**
796797
* Policy for overlapping Actions.
797798
*/
798-
export enum ScheduleOverlapPolicy {
799-
/**
800-
* Use server default (currently SKIP).
801-
*
802-
* FIXME: remove this field if this issue is implemented: https://github.com/temporalio/temporal/issues/3240
803-
*/
804-
UNSPECIFIED = 0,
805-
799+
export const ScheduleOverlapPolicy = {
806800
/**
807801
* Don't start a new Action.
802+
* @default
808803
*/
809-
SKIP,
804+
SKIP: 'SKIP',
810805

811806
/**
812807
* Start another Action as soon as the current Action completes, but only buffer one Action in this way. If another
813808
* Action is supposed to start, but one Action is running and one is already buffered, then only the buffered one will
814809
* be started after the running Action finishes.
815810
*/
816-
BUFFER_ONE,
811+
BUFFER_ONE: 'BUFFER_ONE',
817812

818813
/**
819814
* Allows an unlimited number of Actions to buffer. They are started sequentially.
820815
*/
821-
BUFFER_ALL,
816+
BUFFER_ALL: 'BUFFER_ALL',
822817

823818
/**
824819
* Cancels the running Action, and then starts the new Action once the cancelled one completes.
825820
*/
826-
CANCEL_OTHER,
821+
CANCEL_OTHER: 'CANCEL_OTHER',
827822

828823
/**
829824
* Terminate the running Action and start the new Action immediately.
830825
*/
831-
TERMINATE_OTHER,
826+
TERMINATE_OTHER: 'TERMINATE_OTHER',
832827

833828
/**
834829
* Allow any number of Actions to start immediately.
835830
*
836831
* This is the only policy under which multiple Actions can run concurrently.
837832
*/
838-
ALLOW_ALL,
839-
}
833+
ALLOW_ALL: 'ALLOW_ALL',
840834

841-
checkExtends<
835+
/**
836+
* Use server default (currently SKIP).
837+
*
838+
* @deprecated Either leave property `undefined`, or use {@link SKIP} instead.
839+
*/
840+
UNSPECIFIED: undefined, // eslint-disable-line deprecation/deprecation
841+
} as const;
842+
export type ScheduleOverlapPolicy = (typeof ScheduleOverlapPolicy)[keyof typeof ScheduleOverlapPolicy];
843+
844+
export const [encodeScheduleOverlapPolicy, decodeScheduleOverlapPolicy] = makeProtoEnumConverters<
845+
temporal.api.enums.v1.ScheduleOverlapPolicy,
846+
typeof temporal.api.enums.v1.ScheduleOverlapPolicy,
842847
keyof typeof temporal.api.enums.v1.ScheduleOverlapPolicy,
843-
`SCHEDULE_OVERLAP_POLICY_${keyof typeof ScheduleOverlapPolicy}`
844-
>();
845-
checkExtends<
846-
`SCHEDULE_OVERLAP_POLICY_${keyof typeof ScheduleOverlapPolicy}`,
847-
keyof typeof temporal.api.enums.v1.ScheduleOverlapPolicy
848-
>();
848+
typeof ScheduleOverlapPolicy,
849+
'SCHEDULE_OVERLAP_POLICY_'
850+
>(
851+
{
852+
[ScheduleOverlapPolicy.SKIP]: 1,
853+
[ScheduleOverlapPolicy.BUFFER_ONE]: 2,
854+
[ScheduleOverlapPolicy.BUFFER_ALL]: 3,
855+
[ScheduleOverlapPolicy.CANCEL_OTHER]: 4,
856+
[ScheduleOverlapPolicy.TERMINATE_OTHER]: 5,
857+
[ScheduleOverlapPolicy.ALLOW_ALL]: 6,
858+
UNSPECIFIED: 0,
859+
} as const,
860+
'SCHEDULE_OVERLAP_POLICY_'
861+
);
849862

850863
export interface Backfill {
851864
/**

packages/client/src/task-queue-client.ts

Lines changed: 37 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { status } from '@grpc/grpc-js';
22
import { filterNullAndUndefined } from '@temporalio/common/lib/internal-non-workflow';
33
import { assertNever, SymbolBasedInstanceOfError, RequireAtLeastOne } from '@temporalio/common/lib/type-helpers';
4+
import { makeProtoEnumConverters } from '@temporalio/common/lib/internal-workflow';
45
import { temporal } from '@temporalio/proto';
56
import { BaseClient, BaseClientOptions, defaultBaseClientOptions, LoadedWithDefaults } from './base-client';
67
import { WorkflowService } from './types';
@@ -148,7 +149,7 @@ export class TaskQueueClient extends BaseClient {
148149
namespace: this.options.namespace,
149150
taskQueues: options.taskQueues,
150151
buildIds,
151-
reachability: reachabilityTypeToProto(options.reachability),
152+
reachability: encodeTaskReachability(options.reachability),
152153
});
153154
} catch (e) {
154155
this.rethrowGrpcError(e, 'Unexpected error fetching Build Id reachability');
@@ -173,14 +174,45 @@ export class TaskQueueClient extends BaseClient {
173174
*/
174175
export type ReachabilityOptions = RequireAtLeastOne<BaseReachabilityOptions, 'buildIds' | 'taskQueues'>;
175176

177+
export const ReachabilityType = {
178+
/** The Build Id might be used by new workflows. */
179+
NEW_WORKFLOWS: 'NEW_WORKFLOWS',
180+
181+
/** The Build Id might be used by open workflows and/or closed workflows. */
182+
EXISTING_WORKFLOWS: 'EXISTING_WORKFLOWS',
183+
184+
/** The Build Id might be used by open workflows. */
185+
OPEN_WORKFLOWS: 'OPEN_WORKFLOWS',
186+
187+
/** The Build Id might be used by closed workflows. */
188+
CLOSED_WORKFLOWS: 'CLOSED_WORKFLOWS',
189+
} as const;
190+
176191
/**
177192
* There are different types of reachability:
178193
* - `NEW_WORKFLOWS`: The Build Id might be used by new workflows
179194
* - `EXISTING_WORKFLOWS` The Build Id might be used by open workflows and/or closed workflows.
180195
* - `OPEN_WORKFLOWS` The Build Id might be used by open workflows
181196
* - `CLOSED_WORKFLOWS` The Build Id might be used by closed workflows
182197
*/
183-
export type ReachabilityType = 'NEW_WORKFLOWS' | 'EXISTING_WORKFLOWS' | 'OPEN_WORKFLOWS' | 'CLOSED_WORKFLOWS';
198+
export type ReachabilityType = (typeof ReachabilityType)[keyof typeof ReachabilityType];
199+
200+
export const [encodeTaskReachability, decodeTaskReachability] = makeProtoEnumConverters<
201+
temporal.api.enums.v1.TaskReachability,
202+
typeof temporal.api.enums.v1.TaskReachability,
203+
keyof typeof temporal.api.enums.v1.TaskReachability,
204+
typeof ReachabilityType,
205+
'TASK_REACHABILITY_'
206+
>(
207+
{
208+
[ReachabilityType.NEW_WORKFLOWS]: 1,
209+
[ReachabilityType.EXISTING_WORKFLOWS]: 2,
210+
[ReachabilityType.OPEN_WORKFLOWS]: 3,
211+
[ReachabilityType.CLOSED_WORKFLOWS]: 4,
212+
UNSPECIFIED: 0,
213+
} as const,
214+
'TASK_REACHABILITY_'
215+
);
184216

185217
/**
186218
* See {@link ReachabilityOptions}
@@ -215,24 +247,6 @@ export interface BuildIdReachability {
215247
taskQueueReachability: Record<string, ReachabilityTypeResponse[]>;
216248
}
217249

218-
function reachabilityTypeToProto(type: ReachabilityType | undefined | null): temporal.api.enums.v1.TaskReachability {
219-
switch (type) {
220-
case null:
221-
case undefined:
222-
return temporal.api.enums.v1.TaskReachability.TASK_REACHABILITY_UNSPECIFIED;
223-
case 'NEW_WORKFLOWS':
224-
return temporal.api.enums.v1.TaskReachability.TASK_REACHABILITY_NEW_WORKFLOWS;
225-
case 'EXISTING_WORKFLOWS':
226-
return temporal.api.enums.v1.TaskReachability.TASK_REACHABILITY_EXISTING_WORKFLOWS;
227-
case 'OPEN_WORKFLOWS':
228-
return temporal.api.enums.v1.TaskReachability.TASK_REACHABILITY_OPEN_WORKFLOWS;
229-
case 'CLOSED_WORKFLOWS':
230-
return temporal.api.enums.v1.TaskReachability.TASK_REACHABILITY_CLOSED_WORKFLOWS;
231-
default:
232-
assertNever('Unknown Build Id reachability operation', type);
233-
}
234-
}
235-
236250
export function reachabilityResponseFromProto(resp: GetWorkerTaskReachabilityResponse): ReachabilityResponse {
237251
return {
238252
buildIdReachability: Object.fromEntries(
@@ -247,7 +261,9 @@ export function reachabilityResponseFromProto(resp: GetWorkerTaskReachabilityRes
247261
taskQueueReachability[tqr.taskQueue] = [];
248262
continue;
249263
}
250-
taskQueueReachability[tqr.taskQueue] = tqr.reachability.map(reachabilityTypeFromProto);
264+
taskQueueReachability[tqr.taskQueue] = tqr.reachability.map(
265+
(x) => decodeTaskReachability(x) ?? 'NOT_FETCHED'
266+
);
251267
}
252268
}
253269
let bid: string | UnversionedBuildIdType;
@@ -262,23 +278,6 @@ export function reachabilityResponseFromProto(resp: GetWorkerTaskReachabilityRes
262278
};
263279
}
264280

265-
function reachabilityTypeFromProto(rtype: temporal.api.enums.v1.TaskReachability): ReachabilityTypeResponse {
266-
switch (rtype) {
267-
case temporal.api.enums.v1.TaskReachability.TASK_REACHABILITY_UNSPECIFIED:
268-
return 'NOT_FETCHED';
269-
case temporal.api.enums.v1.TaskReachability.TASK_REACHABILITY_NEW_WORKFLOWS:
270-
return 'NEW_WORKFLOWS';
271-
case temporal.api.enums.v1.TaskReachability.TASK_REACHABILITY_EXISTING_WORKFLOWS:
272-
return 'EXISTING_WORKFLOWS';
273-
case temporal.api.enums.v1.TaskReachability.TASK_REACHABILITY_OPEN_WORKFLOWS:
274-
return 'OPEN_WORKFLOWS';
275-
case temporal.api.enums.v1.TaskReachability.TASK_REACHABILITY_CLOSED_WORKFLOWS:
276-
return 'CLOSED_WORKFLOWS';
277-
default:
278-
return assertNever('Unknown Build Id reachability operation', rtype);
279-
}
280-
}
281-
282281
/**
283282
* Thrown when one or more Build Ids are not found while using the {@link TaskQueueClient}.
284283
*

0 commit comments

Comments
 (0)