Skip to content

Commit bacd3b2

Browse files
authored
fix(client): Fix workflow arg types on create/update schedules (#1028)
1 parent 420ff33 commit bacd3b2

File tree

5 files changed

+104
-24
lines changed

5 files changed

+104
-24
lines changed

packages/client/src/schedule-client.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { status as grpcStatus } from '@grpc/grpc-js';
22
import { v4 as uuid4 } from 'uuid';
3-
import { mapToPayloads, searchAttributePayloadConverter } from '@temporalio/common';
3+
import { mapToPayloads, searchAttributePayloadConverter, Workflow } from '@temporalio/common';
44
import { composeInterceptors, Headers } from '@temporalio/common/lib/interceptors';
55
import {
66
encodeMapToPayloads,
@@ -20,6 +20,8 @@ import {
2020
ScheduleOptions,
2121
ScheduleOverlapPolicy,
2222
ScheduleUpdateOptions,
23+
ScheduleOptionsAction,
24+
ScheduleOptionsStartWorkflowAction,
2325
} from './schedule-types';
2426
import {
2527
compileScheduleOptions,
@@ -67,7 +69,9 @@ export interface ScheduleHandle {
6769
* sends the returned `UpdatedSchedule` to the Server to update the Schedule definition. Note that,
6870
* in the future, `updateFn` might be invoked multiple time, with identical or different input.
6971
*/
70-
update(updateFn: (previous: ScheduleDescription) => ScheduleUpdateOptions): Promise<void>;
72+
update<W extends Workflow = Workflow>(
73+
updateFn: (previous: ScheduleDescription) => ScheduleUpdateOptions<ScheduleOptionsStartWorkflowAction<W>>
74+
): Promise<void>;
7175

7276
/**
7377
* Delete the Schedule
@@ -197,7 +201,10 @@ export class ScheduleClient extends BaseClient {
197201
* @throws {@link ScheduleAlreadyRunning} if there's a running (not deleted) Schedule with the given `id`
198202
* @returns a ScheduleHandle to the created Schedule
199203
*/
200-
public async create(options: ScheduleOptions): Promise<ScheduleHandle> {
204+
public async create<W extends Workflow = Workflow>(
205+
options: ScheduleOptions<ScheduleOptionsStartWorkflowAction<W>>
206+
): Promise<ScheduleHandle>;
207+
public async create<A extends ScheduleOptionsAction>(options: ScheduleOptions<A>): Promise<ScheduleHandle> {
201208
await this._createSchedule(options);
202209
return this.getHandle(options.scheduleId);
203210
}

packages/client/src/schedule-types.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { WorkflowStartOptions } from './workflow-options';
88
*
99
* @experimental
1010
*/
11-
export interface ScheduleOptions {
11+
export interface ScheduleOptions<A extends ScheduleOptionsAction = ScheduleOptionsAction> {
1212
/**
1313
* Schedule Id
1414
*
@@ -24,7 +24,7 @@ export interface ScheduleOptions {
2424
/**
2525
* Which Action to take
2626
*/
27-
action: ScheduleOptionsAction;
27+
action: A;
2828

2929
policies?: {
3030
/**
@@ -132,11 +132,11 @@ export type CompiledScheduleOptions = Replace<
132132
*
133133
* @experimental
134134
*/
135-
export type ScheduleUpdateOptions = Replace<
135+
export type ScheduleUpdateOptions<A extends ScheduleOptionsAction = ScheduleOptionsAction> = Replace<
136136
Omit<ScheduleOptions, 'scheduleId' | 'memo' | 'searchAttributes'>,
137137
{
138138
action: Replace<
139-
ScheduleOptions['action'],
139+
A,
140140
{
141141
// No default value on update
142142
workflowId: string;
@@ -834,8 +834,6 @@ export enum ScheduleOverlapPolicy {
834834
ALLOW_ALL,
835835
}
836836

837-
export type ScheduleOverlapPolicy2 = keyof typeof temporal.api.enums.v1.ScheduleOverlapPolicy;
838-
839837
checkExtends<
840838
keyof typeof temporal.api.enums.v1.ScheduleOverlapPolicy,
841839
`SCHEDULE_OVERLAP_POLICY_${keyof typeof ScheduleOverlapPolicy}`

packages/test/src/test-schedules.ts

Lines changed: 88 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import { randomUUID } from 'crypto';
22
import anyTest, { TestFn } from 'ava';
33
import asyncRetry from 'async-retry';
4-
import { Client, defaultPayloadConverter } from '@temporalio/client';
4+
import {
5+
Client,
6+
defaultPayloadConverter,
7+
CalendarSpec,
8+
CalendarSpecDescription,
9+
ScheduleHandle,
10+
ScheduleSummary,
11+
ScheduleUpdateOptions,
12+
} from '@temporalio/client';
513
import { msToNumber } from '@temporalio/common/lib/time';
6-
import { CalendarSpec, CalendarSpecDescription, ScheduleSummary } from '@temporalio/client/lib/schedule-types';
7-
import { ScheduleHandle } from '@temporalio/client/lib/schedule-client';
814
import { RUN_INTEGRATION_TESTS } from './helpers';
915

1016
export interface Context {
@@ -15,7 +21,8 @@ const taskQueue = 'async-activity-completion';
1521
const test = anyTest as TestFn<Context>;
1622

1723
const dummyWorkflow = async () => undefined;
18-
const dummyWorkflow2 = async (_x?: string) => undefined;
24+
const dummyWorkflowWith1Arg = async (_s: string) => undefined;
25+
const dummyWorkflowWith2Args = async (_x: number, _y: number) => undefined;
1926

2027
const calendarSpecDescriptionDefaults: CalendarSpecDescription = {
2128
second: [{ start: 0, end: 0, step: 1 }],
@@ -29,7 +36,7 @@ const calendarSpecDescriptionDefaults: CalendarSpecDescription = {
2936
};
3037

3138
if (RUN_INTEGRATION_TESTS) {
32-
test.before(async (t) => {
39+
test.beforeEach(async (t) => {
3340
t.context = {
3441
client: new Client(),
3542
};
@@ -115,7 +122,7 @@ if (RUN_INTEGRATION_TESTS) {
115122
}
116123
});
117124

118-
test('Can create schedule with startWorkflow action', async (t) => {
125+
test('Can create schedule with startWorkflow action (no arg)', async (t) => {
119126
const { client } = t.context;
120127
const scheduleId = `can-create-schedule-with-startWorkflow-action-${randomUUID()}`;
121128
const handle = await client.schedule.create({
@@ -148,6 +155,41 @@ if (RUN_INTEGRATION_TESTS) {
148155
}
149156
});
150157

158+
test('Can create schedule with startWorkflow action (with args)', async (t) => {
159+
const { client } = t.context;
160+
const scheduleId = `can-create-schedule-with-startWorkflow-action-${randomUUID()}`;
161+
const handle = await client.schedule.create({
162+
scheduleId,
163+
spec: {
164+
calendars: [{ hour: { start: 2, end: 7, step: 1 } }],
165+
},
166+
action: {
167+
type: 'startWorkflow',
168+
workflowType: dummyWorkflowWith2Args,
169+
args: [3, 4],
170+
taskQueue,
171+
memo: {
172+
'my-memo': 'foo',
173+
},
174+
searchAttributes: {
175+
CustomKeywordField: ['test-value2'],
176+
},
177+
},
178+
});
179+
180+
try {
181+
const describedSchedule = await handle.describe();
182+
183+
t.is(describedSchedule.action.type, 'startWorkflow');
184+
t.is(describedSchedule.action.workflowType, 'dummyWorkflowWith2Args');
185+
t.deepEqual(describedSchedule.action.args, [3, 4]);
186+
t.deepEqual(describedSchedule.action.memo, { 'my-memo': 'foo' });
187+
t.deepEqual(describedSchedule.action.searchAttributes?.CustomKeywordField, ['test-value2']);
188+
} finally {
189+
await handle.delete();
190+
}
191+
});
192+
151193
test('Interceptor is called on create schedule', async (t) => {
152194
const clientWithInterceptor = new Client({
153195
connection: t.context.client.connection,
@@ -272,9 +314,9 @@ if (RUN_INTEGRATION_TESTS) {
272314
}
273315
});
274316

275-
test('Can update schedule', async (t) => {
317+
test('Can update schedule calendar', async (t) => {
276318
const { client } = t.context;
277-
const scheduleId = `can-update-schedule-${randomUUID()}`;
319+
const scheduleId = `can-update-schedule-calendar-${randomUUID()}`;
278320
const handle = await client.schedule.create({
279321
scheduleId,
280322
spec: {
@@ -315,7 +357,8 @@ if (RUN_INTEGRATION_TESTS) {
315357
action: {
316358
type: 'startWorkflow',
317359
workflowId: `${scheduleId}-workflow`,
318-
workflowType: dummyWorkflow,
360+
workflowType: dummyWorkflowWith1Arg,
361+
args: ['foo'],
319362
taskQueue,
320363
},
321364
});
@@ -326,16 +369,48 @@ if (RUN_INTEGRATION_TESTS) {
326369
action: {
327370
type: 'startWorkflow',
328371
workflowId: `${scheduleId}-workflow-2`,
329-
workflowType: dummyWorkflow2,
330-
args: ['updated'],
372+
workflowType: dummyWorkflowWith2Args,
373+
args: [3, 4],
331374
taskQueue,
332375
},
333376
}));
334377

335378
const describedSchedule = await handle.describe();
336379
t.is(describedSchedule.action.type, 'startWorkflow');
337-
t.is(describedSchedule.action.workflowType, 'dummyWorkflow2');
338-
t.deepEqual(describedSchedule.action.args, ['updated']);
380+
t.is(describedSchedule.action.workflowType, 'dummyWorkflowWith2Args');
381+
t.deepEqual(describedSchedule.action.args, [3, 4]);
382+
} finally {
383+
await handle.delete();
384+
}
385+
});
386+
387+
test('Can update schedule intervals', async (t) => {
388+
const { client } = t.context;
389+
const scheduleId = `can-update-schedule-intervals-${randomUUID()}`;
390+
const handle = await client.schedule.create({
391+
scheduleId,
392+
spec: {
393+
intervals: [{ every: '5h' }],
394+
},
395+
action: {
396+
type: 'startWorkflow',
397+
workflowId: `${scheduleId}-workflow`,
398+
workflowType: dummyWorkflowWith1Arg,
399+
args: ['foo'],
400+
taskQueue,
401+
},
402+
});
403+
404+
try {
405+
await handle.update((x: ScheduleUpdateOptions) => {
406+
x.spec.intervals = [{ every: '3h' }];
407+
return x;
408+
});
409+
410+
const describedSchedule = await handle.describe();
411+
t.is(describedSchedule.action.type, 'startWorkflow');
412+
t.is(describedSchedule.action.workflowType, 'dummyWorkflowWith1Arg');
413+
t.deepEqual(describedSchedule.action.args, ['foo']);
339414
} finally {
340415
await handle.delete();
341416
}

packages/test/src/workflows/condition-timeout-0.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* which means that the condition would block indefinitely and would return undefined once
44
* fn evaluates to true, rather than returning true or false.
55
*/
6-
import { condition, setHandler, defineSignal, sleep, ApplicationFailure } from '@temporalio/workflow';
6+
import { condition, setHandler, defineSignal, sleep } from '@temporalio/workflow';
77

88
export const aSignal = defineSignal('a');
99
export const bSignal = defineSignal('b');

packages/worker/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export { NativeConnectionOptions, RequiredNativeConnectionOptions, TLSConfig } f
2525
export { startDebugReplayer } from './debug-replayer';
2626
export { IllegalStateError } from '@temporalio/common';
2727
export { ShutdownError, TransportError, UnexpectedError } from '@temporalio/core-bridge';
28-
export { GracefulShutdownPeriodExpiredError, errors } from './errors';
28+
export { GracefulShutdownPeriodExpiredError, errors } from './errors'; // eslint-disable-line deprecation/deprecation
2929
export * from './interceptors';
3030
export * from './logger';
3131
export { History, Runtime, RuntimeOptions, makeTelemetryFilterString } from './runtime';

0 commit comments

Comments
 (0)