From 679ea5c928fb7ccb6c13d20da1fda3f50e81486e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Thu, 28 Jul 2022 20:42:05 -0400 Subject: [PATCH 01/27] Add API usage --- typescript/schedules.md | 108 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 typescript/schedules.md diff --git a/typescript/schedules.md b/typescript/schedules.md new file mode 100644 index 0000000..7ab85bd --- /dev/null +++ b/typescript/schedules.md @@ -0,0 +1,108 @@ +# Schedules + +- [Docs](https://docs.temporal.io/workflows/#schedules) +- gRPC API: + - [methods](https://github.com/temporalio/api/blob/799926c86eb13d8a9717d3561ab9b0df43796c06/temporal/api/workflowservice/v1/service.proto#L328-L370) + - [`request_response.proto`](https://github.com/temporalio/api/blob/799926c86eb13d8a9717d3561ab9b0df43796c06/temporal/api/workflowservice/v1/request_response.proto#L821-L957) + - [`schedule/v1/message.proto`](https://github.com/temporalio/api/blob/master/temporal/api/schedule/v1/message.proto) + - [`ScheduleOverlapPolicy` enum](https://github.com/temporalio/api/blob/master/temporal/api/enums/v1/schedule.proto) + +## TS API + +Usage: + +```ts +const client = new ScheduleClient() +// or for languages with higher level clients: +// client = new TemporalClient() +// client.schedule.create() +// or +// client.createSchedule() + +const schedule = await client.create({ + id: 'biz-id', + spec: { + // every hour at minute 5 + interval: { + every: '1h', + at: '5m', + }, + // every 20 days since epoch at day 2 + // interval: { + // every: '20d', + // at: '2d' + // } + exclude: { + // skip 11:05 pm + hour: 23, + minute: 5, + }, + endAt: addWeeks(new Date(), 4), + jitter: '30s', + timezone: 'US/Eastern', + }, + action: { + startWorkflow: { + workflowId: 'biz-id', + type: myWorkflow, + input: ['sorry this is the only thing reused, chad 😄'], + }, + }, + policies: { + overlap: ScheduleOverlapPolicy.BUFFER_ONE, + catchupWindow: '2m', + pauseOnFailure: true, + }, + state: { + note: 'started schedule', + paused: true, + limitedActions: 10, + }, + patch: { + triggerImmediately: true, + backfill: [ + { + start: new Date(), + end: new Date(), + overlap: ScheduleOverlapPolicy.ALLOW_ALL, + }, + ], + pause: true, // redundant with state.paused above (can use either) + }, + memo, + searchAttributes, +}) + +const scheduleDescription = await schedule.describe() + +await schedule.listMatchingTimes({ start: new Date(), end: new Date() }) + +await schedule.update({ + spec, + action, + policies, + state, + conflictToken: scheduleDescription.conflictToken, +}) + +await schedule.patch({ + triggerImmediately: true, + backfill, + unpause: true, +}) + +await schedule.delete() + +const { schedules, nextPageToken } = await client.list({ + pageSize: 50, + nextPageToken: 'base64', +}) +``` + +Types: + +```ts +interface ScheduleHandle { + id: string +} +``` From dc832ba223b6c5ff811aae803b8388cccd703ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Fri, 29 Jul 2022 02:11:53 -0400 Subject: [PATCH 02/27] Add higher-level client --- typescript/schedules.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index 7ab85bd..fcb81f4 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -9,15 +9,12 @@ ## TS API -Usage: - ```ts +import { ScheduleClient, ScheduleOverlapPolicy } from '@temporalio/client' +import { addWeeks } from 'date-fns' +import { myWorkflow } from './workflows' + const client = new ScheduleClient() -// or for languages with higher level clients: -// client = new TemporalClient() -// client.schedule.create() -// or -// client.createSchedule() const schedule = await client.create({ id: 'biz-id', @@ -37,7 +34,7 @@ const schedule = await client.create({ hour: 23, minute: 5, }, - endAt: addWeeks(new Date(), 4), + endAt: addWeeks(new Date(), 4), jitter: '30s', timezone: 'US/Eastern', }, @@ -99,10 +96,15 @@ const { schedules, nextPageToken } = await client.list({ }) ``` -Types: +### Higher-level client ```ts -interface ScheduleHandle { - id: string -} -``` +import { Client } from '@temporalio/client' + +const client = new Client() + +client.schedule.create() +client.workflow.start() +client.asyncCompletion.heartbeat() +client.operator.addSearchAttributes() +``` \ No newline at end of file From 9359dfeb048e7b61b538ab9836aafc60e6c9a124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Fri, 29 Jul 2022 02:58:02 -0400 Subject: [PATCH 03/27] args --- typescript/schedules.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index fcb81f4..43a3c45 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -42,7 +42,7 @@ const schedule = await client.create({ startWorkflow: { workflowId: 'biz-id', type: myWorkflow, - input: ['sorry this is the only thing reused, chad 😄'], + args: ['sorry this is the only thing reused, chad 😄'], }, }, policies: { @@ -72,7 +72,7 @@ const schedule = await client.create({ const scheduleDescription = await schedule.describe() -await schedule.listMatchingTimes({ start: new Date(), end: new Date() }) +const matchingStartTimes = await schedule.listMatchingTimes({ start: new Date(), end: new Date() }) await schedule.update({ spec, From 3f45cb3bce9d65770a4dbaa5b25a8c33bda5b8c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Fri, 29 Jul 2022 03:20:39 -0400 Subject: [PATCH 04/27] Add ClientOptions and Interceptors section --- typescript/schedules.md | 49 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index 43a3c45..4cb29e3 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -101,10 +101,57 @@ const { schedules, nextPageToken } = await client.list({ ```ts import { Client } from '@temporalio/client' -const client = new Client() +const client = new Client(options) client.schedule.create() client.workflow.start() client.asyncCompletion.heartbeat() client.operator.addSearchAttributes() + +interface ClientOptions { + dataConverter?: DataConverter; + interceptors?: { + workflow: WorkflowClientInterceptors, + schedule: ScheduleClientInterceptors, + }; + identity?: string; + connection?: ConnectionLike; + namespace?: string; + queryRejectCondition?: temporal.api.enums.v1.QueryRejectCondition; +} +``` + +### Interceptors + +```ts +interface ScheduleClientCallsInterceptor { + /** + * Intercept a service call to CreateSchedule + */ + create?: (input: ScheduleStartInput, next: Next) => Promise; + describe + listMatchingTimes + update + patch + delete + list +} + +interface ScheduleClientCallsInterceptorFactoryInput { + id: string; +} + +/** + * A function that takes a {@link ScheduleClientCallsInterceptorFactoryInput} and returns an interceptor + */ +export interface ScheduleClientCallsInterceptorFactory { + (input: ScheduleClientCallsInterceptorFactoryInput): ScheduleClientCallsInterceptor; +} + +/** + * A mapping of interceptor type of a list of factory functions + */ +export interface ScheduleClientInterceptors { + calls?: ScheduleClientCallsInterceptorFactory[]; +} ``` \ No newline at end of file From 082d691f63ac6f0b41a8db441f2e9e4928b7195a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Fri, 29 Jul 2022 03:27:07 -0400 Subject: [PATCH 05/27] include startWorkflow type --- typescript/schedules.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/typescript/schedules.md b/typescript/schedules.md index 4cb29e3..afceb52 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -40,9 +40,12 @@ const schedule = await client.create({ }, action: { startWorkflow: { + // type: WorkflowStartOptions & { workflowId: string } workflowId: 'biz-id', type: myWorkflow, args: ['sorry this is the only thing reused, chad 😄'], + // ... other WorkflowOptions + // https://typescript.temporal.io/api/interfaces/client.WorkflowOptions }, }, policies: { From ebbbab57d21de93a416faea4b32523ef8a4a5a66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Fri, 29 Jul 2022 03:29:02 -0400 Subject: [PATCH 06/27] add handle --- typescript/schedules.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index afceb52..2b2b572 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -17,7 +17,7 @@ import { myWorkflow } from './workflows' const client = new ScheduleClient() const schedule = await client.create({ - id: 'biz-id', + id: 'schedule-biz-id', spec: { // every hour at minute 5 interval: { @@ -41,7 +41,7 @@ const schedule = await client.create({ action: { startWorkflow: { // type: WorkflowStartOptions & { workflowId: string } - workflowId: 'biz-id', + workflowId: 'wf-biz-id', type: myWorkflow, args: ['sorry this is the only thing reused, chad 😄'], // ... other WorkflowOptions @@ -73,6 +73,8 @@ const schedule = await client.create({ searchAttributes, }) +const schedule = await client.getHandle('schedule-biz-id') + const scheduleDescription = await schedule.describe() const matchingStartTimes = await schedule.listMatchingTimes({ start: new Date(), end: new Date() }) From c6806012cb25e78546c7a1117063f9d54a150607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Fri, 29 Jul 2022 03:35:04 -0400 Subject: [PATCH 07/27] diff WorkflowOptions with NewWorkflowExecutionInfo --- typescript/schedules.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index 2b2b572..8311349 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -40,11 +40,10 @@ const schedule = await client.create({ }, action: { startWorkflow: { - // type: WorkflowStartOptions & { workflowId: string } workflowId: 'wf-biz-id', type: myWorkflow, args: ['sorry this is the only thing reused, chad 😄'], - // ... other WorkflowOptions + // ...WorkflowOptions + header - followRuns // https://typescript.temporal.io/api/interfaces/client.WorkflowOptions }, }, From c19c8d7cee5e6e1952f7ffba3235484cb8610876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Fri, 29 Jul 2022 03:35:59 -0400 Subject: [PATCH 08/27] not async --- typescript/schedules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index 8311349..0b5f458 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -72,7 +72,7 @@ const schedule = await client.create({ searchAttributes, }) -const schedule = await client.getHandle('schedule-biz-id') +const schedule = client.getHandle('schedule-biz-id') const scheduleDescription = await schedule.describe() From 58233cb0fb52c115190ec267c853c2d417175804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Fri, 29 Jul 2022 15:16:08 -0400 Subject: [PATCH 09/27] arrays --- typescript/schedules.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index 0b5f458..ac0fddc 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -20,20 +20,20 @@ const schedule = await client.create({ id: 'schedule-biz-id', spec: { // every hour at minute 5 - interval: { + intervals: [{ every: '1h', at: '5m', - }, + }], // every 20 days since epoch at day 2 - // interval: { + // intervals: [{ // every: '20d', // at: '2d' - // } - exclude: { + // }] + exclude: [{ // skip 11:05 pm hour: 23, minute: 5, - }, + }], endAt: addWeeks(new Date(), 4), jitter: '30s', timezone: 'US/Eastern', From f284479185a4f86111c3a64afb34466b156de784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Wed, 3 Aug 2022 02:19:02 -0400 Subject: [PATCH 10/27] Address review comments --- typescript/schedules.md | 50 ++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index ac0fddc..58784d3 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -47,27 +47,20 @@ const schedule = await client.create({ // https://typescript.temporal.io/api/interfaces/client.WorkflowOptions }, }, - policies: { - overlap: ScheduleOverlapPolicy.BUFFER_ONE, - catchupWindow: '2m', - pauseOnFailure: true, - }, - state: { - note: 'started schedule', - paused: true, - limitedActions: 10, - }, - patch: { - triggerImmediately: true, - backfill: [ - { - start: new Date(), - end: new Date(), - overlap: ScheduleOverlapPolicy.ALLOW_ALL, - }, - ], - pause: true, // redundant with state.paused above (can use either) - }, + overlap: ScheduleOverlapPolicy.BUFFER_ONE, + catchupWindow: '2m', + pauseOnFailure: true, + note: 'started schedule', + pause: true, // this sets `state.paused: true` (default false) + limitedActions: 10, + triggerImmediately: true, + backfill: [ + { + start: new Date(), + end: new Date(), + overlap: ScheduleOverlapPolicy.ALLOW_ALL, + }, + ], memo, searchAttributes, }) @@ -86,11 +79,10 @@ await schedule.update({ conflictToken: scheduleDescription.conflictToken, }) -await schedule.patch({ - triggerImmediately: true, - backfill, - unpause: true, -}) +await schedule.trigger() +await schedule.backfill(backfill) // also takes array +await schedule.pause('note: pausing') +await schedule.unpause('now unpause') await schedule.delete() @@ -98,6 +90,12 @@ const { schedules, nextPageToken } = await client.list({ pageSize: 50, nextPageToken: 'base64', }) + +for await (const schedule: ScheduleListEntry of client.list()) { + const { id, memo, searchAttributes, info } = schedule + // ... +} + ``` ### Higher-level client From d982e80169088f7df6be7ea9ab128edfdc8a848c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Wed, 10 Aug 2022 15:13:30 -0400 Subject: [PATCH 11/27] Add types --- typescript/schedules.md | 1169 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 1128 insertions(+), 41 deletions(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index 58784d3..1de090f 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -14,40 +14,39 @@ import { ScheduleClient, ScheduleOverlapPolicy } from '@temporalio/client' import { addWeeks } from 'date-fns' import { myWorkflow } from './workflows' -const client = new ScheduleClient() +const client = new ScheduleClient(); const schedule = await client.create({ id: 'schedule-biz-id', spec: { // every hour at minute 5 - intervals: [{ - every: '1h', - at: '5m', - }], + intervals: [ + { + every: '1h', + at: '5m', + }, + ], // every 20 days since epoch at day 2 // intervals: [{ // every: '20d', // at: '2d' // }] - exclude: [{ - // skip 11:05 pm - hour: 23, - minute: 5, - }], - endAt: addWeeks(new Date(), 4), + skip: [ + { + // skip 11:05 pm + hour: 23, + minute: 5, + }, + ], + endAt: addWeeks(new Date(), 4), jitter: '30s', - timezone: 'US/Eastern', }, action: { - startWorkflow: { - workflowId: 'wf-biz-id', - type: myWorkflow, - args: ['sorry this is the only thing reused, chad 😄'], - // ...WorkflowOptions + header - followRuns - // https://typescript.temporal.io/api/interfaces/client.WorkflowOptions - }, + workflowId: 'wf-biz-id', + type: myWorkflow, + args: ['arg1', 'arg2'], }, - overlap: ScheduleOverlapPolicy.BUFFER_ONE, + overlap: ScheduleOverlapPolicy.BufferOne, catchupWindow: '2m', pauseOnFailure: true, note: 'started schedule', @@ -58,44 +57,1132 @@ const schedule = await client.create({ { start: new Date(), end: new Date(), - overlap: ScheduleOverlapPolicy.ALLOW_ALL, + overlap: ScheduleOverlapPolicy.AllowAll, }, ], memo, searchAttributes, -}) +}); + +const schedule = client.getHandle('schedule-biz-id'); -const schedule = client.getHandle('schedule-biz-id') +const scheduleDescription = await schedule.describe(); -const scheduleDescription = await schedule.describe() +// For later, pending API finalization: +// https://github.com/temporalio/proposals/pull/62/files#r933532170 +// const matchingStartTimes = await schedule.listMatchingTimes({ start: new Date(), end: new Date() }) +// const matchingStartTimes = await client.listMatchingTimes({ spec, start, end }) -const matchingStartTimes = await schedule.listMatchingTimes({ start: new Date(), end: new Date() }) +await schedule.update( + (schedule) => { + schedule.spec.intervals[0].every = '1d'; + // unset with: + // delete schedule.spec.intervals; -await schedule.update({ - spec, - action, - policies, - state, - conflictToken: scheduleDescription.conflictToken, -}) + // return false to stop retrying early + }, + { retry: retryPolicy } +); -await schedule.trigger() -await schedule.backfill(backfill) // also takes array -await schedule.pause('note: pausing') -await schedule.unpause('now unpause') +await schedule.trigger(); +await schedule.backfill({ startAt: new Date(), endAt: addWeeks(new Date(), 1) }); // also takes array +await schedule.pause('note: pausing'); +await schedule.unpause('now unpause'); -await schedule.delete() +await schedule.delete(); -const { schedules, nextPageToken } = await client.list({ +const { schedules, nextPageToken } = await client.listByPage({ pageSize: 50, nextPageToken: 'base64', -}) +}); -for await (const schedule: ScheduleListEntry of client.list()) { - const { id, memo, searchAttributes, info } = schedule +for await (const schedule: Schedule of client.list()) { + const { id, memo, searchAttributes } = schedule; // ... } +``` + +### Types + +```ts +import { DataConverter, LoadedDataConverter } from '@temporalio/common'; +import { loadDataConverter } from '@temporalio/internal-non-workflow-common'; +import { + composeInterceptors, + Headers, + Replace, + RetryPolicy, + SearchAttributes, + Workflow, +} from '@temporalio/internal-workflow-common'; +import { temporal } from '@temporalio/proto'; +import os from 'os'; +import { Connection } from './connection'; +import { ScheduleClientCallsInterceptor, ScheduleClientInterceptors } from './interceptors'; +import { ConnectionLike, Metadata, WorkflowService } from './types'; +import { WorkflowHandle, WorkflowStartOptions } from './workflow-client'; + +import type { NonNegativeInteger, RequireAtLeastOne } from 'type-fest'; + +// TODO is a non-generic NonNegativeInteger possible? The ones below error due to no type argument +export type PositiveInteger = Exclude; + +/** + * @format number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string} + */ +export type Milliseconds = string | NonNegativeInteger; + +/** + * @format number of seconds or {@link https://www.npmjs.com/package/ms | ms-formatted string} + */ +export type Seconds = string | NonNegativeInteger; + +export interface UpdateScheduleOptions { + /** + * @default TODO + */ + retry?: RetryPolicy; +} + +export interface Backfill { + /** Time range to evaluate Schedule in. */ + startAt: Date; + endAt: Date; + + /** Override Overlap Policy for this request. */ + overlapPolicyOverride?: ScheduleOverlapPolicy; +} + +/** + * Handle to a single Schedule + */ +export interface ScheduleHandle { + /** + * This Schedule's identifier + */ + readonly id: string; + + /** + * Update the Schedule + */ + update(mutateFn: (schedule: ScheduleDescription) => void | false, options?: UpdateScheduleOptions): Promise; + + /** + * Delete the Schedule + */ + delete(): Promise; + + /** + * Trigger an Action to be taken immediately + */ + trigger(): Promise; + + /** + * Run though the specified time period(s) and take Actions as if that time passed by right now, all at once. The + * Overlap Policy can be overridden for the scope of the Backfill. + */ + backfill(options: Backfill | Backfill[]): Promise; + + /** + * Pause the Schedule + */ + pause(note?: string): Promise; + + /** + * Unpause the Schedule + */ + unpause(note?: string): Promise; + + /** + * Fetch the Schedule's description from the Server + */ + describe(): Promise; + + /** + * Get a handle to the most recent Action started + */ + lastAction(): Promise>; + + /** + * Readonly accessor to the underlying ScheduleClient + */ + readonly client: ScheduleClient; +} + +export type Base64 = string; + +export interface Schedule { + /** + * Schedule Id + * + * We recommend using a meaningful business identifier. + */ + id: string; + + /** + * Additional non-indexed information attached to the Schedule. The values can be anything that is + * serializable by the {@link DataConverter}. + */ + memo?: Record; + + /** + * Additional indexed information attached to the Schedule. More info: + * https://docs.temporal.io/docs/typescript/search-attributes + * + * Values are always converted using {@link JsonPayloadConverter}, even when a custom Data Converter is provided. + */ + searchAttributes: SearchAttributes; + + // TODO flatten this? + info: ScheduleInfo; +} + +/** + * The current Schedule details. They may not match the Schedule as created because: + * - some fields in the state are modified automatically + * - the schedule may have been modified by {@link ScheduleHandle.update} or + * {@link ScheduleHandle.pause}/{@link ScheduleHandle.unpause} + */ +export interface ScheduleDescription { + /** When Actions should be taken */ + spec: RequireAtLeastOne; + + /** + * Which Action to take + */ + action: ScheduleActionOptions; + + /** + * Controls what happens when an Action would be started by a Schedule at the same time that an older Action is still + * running. + * + * @default {@link ScheduleOverlapPolicy.Skip} + */ + overlap: ScheduleOverlapPolicy; + + /** + * The Temporal Server might be down or unavailable at the time when a Schedule should take an Action. When the Server + * comes back up, `catchupWindow` controls which missed Actions should be taken at that point. The default is one + * minute, which means that the Schedule attempts to take any Actions that wouldn't be more than one minute late. It + * takes those Actions according to the {@link ScheduleOverlapPolicy}. An outage that lasts longer than the Catchup + * Window could lead to missed Actions. (But you can always {@link ScheduleHandle.backfill}.) + * + * @default 1 minute + */ + catchupWindow: Milliseconds; + + /** + * When an Action times out or reaches the end of its Retry Policy, {@link pause}. + * + * With {@link ScheduleOverlapPolicy.AllowAll}, this pause might not apply to the next Action, because the next Action + * might have already started previous to the failed one finishing. Pausing applies only to Actions that are scheduled + * to start after the failed one finishes. + * + * @default false + */ + pauseOnFailure: boolean; + + /** + * Informative human-readable message with contextual notes, e.g. the reason + * a Schedule is paused. The system may overwrite this message on certain + * conditions, e.g. when pause-on-failure happens. + */ + note?: string; + + /** + * Is currently paused. + * + * @default false + */ + paused: boolean; + + /** Whether the number of Actions to take is limited. */ + actionsAreLimited: boolean; + + /** + * Limit the number of Actions to take. + * + * This number is decremented after each Action is taken, and Actions are not + * taken when the number is `0` (unless {@link ScheduleHandle.trigger} is called). + * + * @default unlimited + */ + remainingActions?: NonNegativeInteger; +} + +export interface ScheduleListPage { + schedules: Schedule[]; + nextPageToken?: Base64; + + /** + * Token to use to when calling {@link ScheduleClient.listByPage}. + * + * If `undefined`, there are no more Schedules. + */ + nextPageToken?: Base64; +} + +export type WorkflowExecution = Required; + +export type ScheduleInfo = Schedule & { + /** Number of Actions taken so far. */ + numActionsTaken: number; + // TODO or numberOfActionsTaken etc? + + /** Number of times a scheduled Action was skipped due to missing the catchup window. */ + numActionsMissedCatchupWindow: number; + /** Number of Actions skipped due to overlap. */ + numActionsSkippedOverlap: number; + + /** + * Currently-running workflows started by this schedule. (There might be + * more than one if the overlap policy allows overlaps.) + * Note that the run_ids in here are the original execution run ids as + * started by the schedule. If the workflows retried, did continue-as-new, + * or were reset, they might still be running but with a different run_id. + */ + runningWorkflows: WorkflowExecution[]; + + /** + * Most recent 10 Actions started (including manual triggers). + * + * Sorted from older start time to newer. + */ + recentActions: ScheduleAction[]; + + /** Next 10 scheduled Action times */ + nextActionTimes: Date[]; + + createdAt: Date; + lastUpdatedAt: Date; + + isValid(): boolean; + + /** Error for invalid schedule. If this is present, no actions will be taken. */ + invalidScheduleError?: string; +}; + +export interface ScheduleAction { + /** Time that the Action was scheduled for, including jitter. */ + scheduledAt: Date; + + /** Time that the Action was actually taken. */ + takenAt: Date; + + /** If action was {@link StartWorkflowAction}. */ + workflow?: WorkflowExecution; + // TODO or WorkflowHandle? would be more convenient, eg `const latestResult = await recentActions.pop().result()` +} + +export interface ScheduleClientOptions { + /** + * {@link DataConverter} to use for serializing and deserializing payloads + */ + dataConverter?: DataConverter; + + /** + * Used to override and extend default Connection functionality + * + * Useful for injecting auth headers and tracing Workflow executions + */ + interceptors?: ScheduleClientInterceptors; + + /** + * Identity to report to the server + * + * @default `${process.pid}@${os.hostname()}` + */ + identity?: string; + + /** + * Connection to use to communicate with the server. + * + * By default `ScheduleClient` connects to localhost. + * + * Connections are expensive to construct and should be reused. + */ + connection?: ConnectionLike; + + /** + * Server namespace + * + * @default default + */ + namespace?: string; +} + +export type ScheduleClientOptionsWithDefaults = Replace< + Required, + { + connection?: ConnectionLike; + } +>; +export type LoadedScheduleClientOptions = ScheduleClientOptionsWithDefaults & { + loadedDataConverter: LoadedDataConverter; +}; + +export function defaultScheduleClientOptions(): ScheduleClientOptionsWithDefaults { + return { + dataConverter: {}, + // The equivalent in Java is ManagementFactory.getRuntimeMXBean().getName() + identity: `${process.pid}@${os.hostname()}`, + interceptors: {}, + namespace: 'default', + }; +} + +// TODO +// function ensureArgs>(opts: T): Omit & { args: unknown[] } { +// const { args, ...rest } = opts; +// return { args: args ?? [], ...rest }; +// } + +interface SchdeduleHandleOptions extends GetSchdeduleHandleOptions { + workflowId: string; + runId?: string; + interceptors: ScheduleClientCallsInterceptor[]; + /** + * A runId to use for getting the workflow's result. + * + * - When creating a handle using `getHandle`, uses the provided runId or firstExecutionRunId + * - When creating a handle using `start`, uses the returned runId (first in the chain) + * - When creating a handle using `signalWithStart`, uses the the returned runId + */ + runIdForResult?: string; +} + +/** + * Policy for overlapping Actions. + */ +export enum ScheduleOverlapPolicy { + /** + * Don't start a new Action. + */ + Skip = 1, + + /** + * Start another Action as soon as the current Action completes, but only buffer one Action in this way. If another + * Action is supposed to start, but one Action is running and one is already buffered, then only the buffered one will + * be started after the running Action finishes. + */ + BufferOne, + + /** + * Allows an unlimited number of Actions to buffer. They are started sequentially. + */ + BufferAll, + + /** + * Cancels the running Action, and then starts the new Action once the cancelled one completes. + */ + CancelOther, + + /** + * Terminate the running Action and start the new Action immediately. + */ + TerminateOther, + + /** + * Allow any number of Actions to start immediately. + * + * This is the only policy under which multiple Actions can run concurrently. + * + * {@link lastCompletionResult} and {@link lastFailure} will not be available. + */ + AllowAll, +} + +export interface Backfill { + start: Date; + end: Date; + /** + * Override overlap policy for this request. + */ + overlap?: ScheduleOverlapPolicy; +} + +/** + * The range of values depends on which, if any, fields are set: + * + * ``` + * {} -> all values + * {start} -> start + * {start, end} -> every value from start to end (implies step = 1) + * {start, step} -> from start, by step (implies end is max for this field) + * {start, end, step} -> from start to end, by step + * {step} -> all values, by step (implies start and end are full range for this field) + * {end} and {end, step} are not allowed + * ``` + * TODO is there a way to express not allowed ^ in type? + * + * For example: + * + * ``` + * {start: 2, end: 10, step: 3} -> 2, 5, 8 + * ``` + */ +export interface Range { + start?: Unit; + end?: Unit; + + /** + * The step to take between each value. + * + * @default 1 + */ + step?: PositiveInteger; +} + +export type AnyValue = '*'; + +export type ZeroTo59 = + | 0 + | 1 + | 2 + | 3 + | 4 + | 5 + | 6 + | 7 + | 8 + | 9 + | 10 + | 11 + | 12 + | 13 + | 14 + | 15 + | 16 + | 17 + | 18 + | 19 + | 20 + | 21 + | 22 + | 23 + | 24 + | 25 + | 26 + | 27 + | 28 + | 29 + | 30 + | 31 + | 32 + | 33 + | 34 + | 35 + | 36 + | 37 + | 38 + | 39 + | 40 + | 41 + | 42 + | 43 + | 44 + | 45 + | 46 + | 47 + | 48 + | 49 + | 50 + | 51 + | 52 + | 53 + | 54 + | 55 + | 56 + | 57 + | 58 + | 59; + +export type Second = Range | ZeroTo59; +export type Minute = Range | ZeroTo59; + +export type ZeroTo23 = + | 0 + | 1 + | 2 + | 3 + | 4 + | 5 + | 6 + | 7 + | 8 + | 9 + | 10 + | 11 + | 12 + | 13 + | 14 + | 15 + | 16 + | 17 + | 18 + | 19 + | 20 + | 21 + | 22 + | 23; + +export type Hour = Range | ZeroTo23; + +export type ZeroTo30 = + | 0 + | 1 + | 2 + | 3 + | 4 + | 5 + | 6 + | 7 + | 8 + | 9 + | 10 + | 11 + | 12 + | 13 + | 14 + | 15 + | 16 + | 17 + | 18 + | 19 + | 20 + | 21 + | 22 + | 23 + | 24 + | 25 + | 26 + | 27 + | 28 + | 29 + | 30; + +export type DayOfMonth = Range | ZeroTo30; + +export type ZeroTo11 = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11; + +export type MonthName = + | 'JAN' + | 'JANUARY' + | 'FEB' + | 'FEBRUARY' + | 'MAR' + | 'MARCH' + | 'APR' + | 'APRIL' + | 'MAY' + | 'JUN' + | 'JUNE' + | 'JUL' + | 'JULY' + | 'AUG' + | 'AUGUST' + | 'SEP' + | 'SEPTEMBER' + | 'OCT' + | 'OCTOBER' + | 'NOV' + | 'NOVEMBER' + | 'DEC' + | 'DECEMBER'; + +export type MonthValue = ZeroTo11 | MonthName; + +export type Month = Range | MonthValue; + +export type TwoThousandTo2100 = + | 2000 + | 2001 + | 2002 + | 2003 + | 2004 + | 2005 + | 2006 + | 2007 + | 2008 + | 2009 + | 2010 + | 2011 + | 2012 + | 2013 + | 2014 + | 2015 + | 2016 + | 2017 + | 2018 + | 2019 + | 2020 + | 2021 + | 2022 + | 2023 + | 2024 + | 2025 + | 2026 + | 2027 + | 2028 + | 2029 + | 2030 + | 2031 + | 2032 + | 2033 + | 2034 + | 2035 + | 2036 + | 2037 + | 2038 + | 2039 + | 2040 + | 2041 + | 2042 + | 2043 + | 2044 + | 2045 + | 2046 + | 2047 + | 2048 + | 2049 + | 2050 + | 2051 + | 2052 + | 2053 + | 2054 + | 2055 + | 2056 + | 2057 + | 2058 + | 2059 + | 2060 + | 2061 + | 2062 + | 2063 + | 2064 + | 2065 + | 2066 + | 2067 + | 2068 + | 2069 + | 2070 + | 2071 + | 2072 + | 2073 + | 2074 + | 2075 + | 2076 + | 2077 + | 2078 + | 2079 + | 2080 + | 2081 + | 2082 + | 2083 + | 2084 + | 2085 + | 2086 + | 2087 + | 2088 + | 2089 + | 2090 + | 2091 + | 2092 + | 2093 + | 2094 + | 2095 + | 2096 + | 2097 + | 2098 + | 2099 + | 2100; + +/** + * Temporal Server currently only supports specifying years in the range `[2000, 2100]`. + */ +export type Year = Range | TwoThousandTo2100; + +/** + * Both 0 and 7 mean Sunday + */ +export type ZeroTo7 = 0 | 1 | 2 | 3 | 4 | 5 | 7; + +export type DayOfWeekName = + | 'SU' + | 'SUN' + | 'SUNDAY' + | 'MO' + | 'MON' + | 'MONDAY' + | 'TU' + | 'TUE' + | 'TUES' + | 'TUESDAY' + | 'WE' + | 'WED' + | 'WEDNESDAY' + | 'TH' + | 'THU' + | 'THUR' + | 'THURS' + | 'THURSDAY' + | 'FR' + | 'FRI' + | 'FRIDAY' + | 'SA' + | 'SAT' + | 'SATURDAY'; + +export type DayOfWeekValue = ZeroTo7 | DayOfWeekName; + +// TODO can we do something to make it case insensitive? +// export type DayOfWeekValue = ZeroTo7 | Uppercase extends DayOfWeekName ? T : DayOfWeekName +export type DayOfWeek = Range | DayOfWeekValue; + +/** + * An event specification relative to the calendar, similar to a traditional cron specification. + * + * A second in time matches if all fields match. This includes `dayOfMonth` and `dayOfWeek`. + */ +export interface CalendarSpec { + /** + * @default 0 + */ + second?: Second | Second[]; + + /** + * @default 0 + */ + minute?: Minute | Minute[]; + + /** + * @default 0 + */ + hour?: Hour | Hour[]; + + /** + * @default {@link AnyValue} + */ + dayOfMonth?: DayOfMonth | DayOfMonth[]; + + /** + * @default {@link AnyValue} + */ + month?: Month | Month[]; + + /** + * @default {@link AnyValue} + */ + year?: Year | Year[]; + + /** + * Can accept 0 or 7 as Sunday. + * + * @default {@link AnyValue} + */ + dayOfWeek?: DayOfWeek | DayOfWeek[]; +} + +/** + * IntervalSpec matches times that can be expressed as: + * + * `Epoch + (n * every) + at` + * + * where `n` is any integer ≥ 0. + * + * For example, an `every` of 1 hour with `at` of zero would match every hour, on the hour. The same `every` but an `at` + * of 19 minutes would match every `xx:19:00`. An `every` of 28 days with `at` zero would match `2022-02-17T00:00:00Z` + * (among other times). The same `every` with `at` of 3 days, 5 hours, and 23 minutes would match `2022-02-20T05:23:00Z` + * instead. + */ +export interface IntervalSpec { + every: Seconds; + + /** + * @default 0 + */ + at?: Seconds; +} + +/** + * A complete description of a set of absolute timestamps (possibly infinite) that an Action should occur at. These times + * never change, except that the definition of a time zone can change over time (most commonly, when daylight saving + * time policy changes for an area). To create a totally self-contained `ScheduleSpec`, use UTC. + */ +export interface ScheduleSpec { + /** Calendar-based specifications of times. */ + calendars?: CalendarSpec[]; + + /** Interval-based specifications of times. */ + intervals?: IntervalSpec[]; + + /** Any timestamps matching any of the exclude_calendar specs will be skipped. */ + skip?: CalendarSpec[]; + + /** + * Any timestamps before `startAt` will be skipped. Together, `startAt` and `endAt` make an inclusive interval. + * + * @default The beginning of time + */ + startAt?: Date; + + /** + * Any timestamps after `endAt` will be skipped. + * + * @default The end of time + */ + endAt?: Date; + + /** + * All timestamps will be incremented by a random value from 0 to this amount of jitter. + * + * @default 1 second + */ + jitter?: Milliseconds; + + // Add to SDK if requested by users: + // + // string timezone_name = 10; + // bytes timezone_data = 11; + // + // Time zone to interpret all CalendarSpecs in. + // + // If unset, defaults to UTC. We recommend using UTC for your application if + // at all possible, to avoid various surprising properties of time zones. + // + // Time zones may be provided by name, corresponding to names in the IANA + // time zone database (see https://www.iana.org/time-zones). The definition + // will be loaded by the Temporal server from the environment it runs in. + // + // If your application requires more control over the time zone definition + // used, it may pass in a complete definition in the form of a TZif file + // from the time zone database. If present, this will be used instead of + // loading anything from the environment. You are then responsible for + // updating timezone_data when the definition changes. + // + // Calendar spec matching is based on literal matching of the clock time + // with no special handling of DST: if you write a calendar spec that fires + // at 2:30am and specify a time zone that follows DST, that action will not + // be triggered on the day that has no 2:30am. Similarly, an action that + // fires at 1:30am will be triggered twice on the day that has two 1:30s. +} + +export type StartWorkflowAction = Omit< + WorkflowStartOptions, + 'workflowIdReusePolicy' | 'cronSchedule' | 'followRuns' +> & { + /** + * Metadata that's propagated between workflows and activities? TODO someone explain headers to me so I can improve + * this apidoc and maybe: + * - remove from here and only expose to interceptor? + * - or make it Record and apply client's data converter? + */ + headers: Headers; +}; + +export type ScheduleActionType = Workflow; + +/** + * Currently, Temporal Server only supports {@link StartWorkflowAction}. + */ +export type ScheduleActionOptions = StartWorkflowAction; +// in future: +// type SomethingElse = 'hi' +// type ScheduleAction = Workflow | SomethingElse +// type ExpectsSomethingElse = Action extends SomethingElse ? Action : number; +// type StartSomethingElseAction = ExpectsSomethingElse +// type ScheduleActionOptions = Action extends Workflow +// ? StartWorkflowAction +// : Action extends SomethingElse +// ? StartSomethingElseAction +// : never; + +export type HandleFor = T extends Workflow ? WorkflowHandle : never; + +/** + * Options for starting a Workflow + */ +export interface ScheduleOptions { + /** + * Schedule Id + * + * We recommend using a meaningful business identifier. + */ + id: string; + + /** When Actions should be taken */ + spec: RequireAtLeastOne; + + /** + * Which Action to take + */ + action: ScheduleActionOptions; + + /** + * Controls what happens when an Action would be started by a Schedule at the same time that an older Action is still + * running. + * + * @default {@link ScheduleOverlapPolicy.Skip} + */ + overlap?: ScheduleOverlapPolicy; + + /** + * The Temporal Server might be down or unavailable at the time when a Schedule should take an Action. When the Server + * comes back up, `catchupWindow` controls which missed Actions should be taken at that point. The default is one + * minute, which means that the Schedule attempts to take any Actions that wouldn't be more than one minute late. It + * takes those Actions according to the {@link ScheduleOverlapPolicy}. An outage that lasts longer than the Catchup + * Window could lead to missed Actions. (But you can always {@link ScheduleHandle.backfill}.) + * + * @default 1 minute + */ + catchupWindow?: Milliseconds; + + /** + * When an Action times out or reaches the end of its Retry Policy, {@link pause}. + * + * With {@link ScheduleOverlapPolicy.AllowAll}, this pause might not apply to the next Action, because the next Action + * might have already started previous to the failed one finishing. Pausing applies only to Actions that are scheduled + * to start after the failed one finishes. + * + * @default false + */ + pauseOnFailure?: boolean; + + /** + * Informative human-readable message with contextual notes, e.g. the reason + * a Schedule is paused. The system may overwrite this message on certain + * conditions, e.g. when pause-on-failure happens. + */ + note?: string; + + /** + * Start in paused state. + * + * @default false + */ + pause?: boolean; + + /** + * Limit the number of Actions to take. + * + * This number is decremented after each Action is taken, and Actions are not + * taken when the number is `0` (unless {@link ScheduleHandle.trigger} is called). + * + * @default unlimited + */ + remainingActions?: NonNegativeInteger; + + /** + * Trigger one Action immediately. + * + * @default false + */ + triggerImmediately?: boolean; + + /** + * Runs though the specified time periods and takes Actions as if that time passed by right now, all at once. The + * overlap policy can be overridden for the scope of the backfill. + */ + backfill?: Backfill[]; + + /** + * Additional non-indexed information attached to the Schedule. The values can be anything that is + * serializable by the {@link DataConverter}. + */ + memo?: Record; + + /** + * Additional indexed information attached to the Schedule. More info: + * https://docs.temporal.io/docs/typescript/search-attributes + * + * Values are always converted using {@link JsonPayloadConverter}, even when a custom Data Converter is provided. + */ + searchAttributes?: SearchAttributes; +} + +export interface ListScheduleOptions { + /** + * How many results to return + * @default 1000 + */ + pageSize?: number; + + /** Token to get the next page of results */ + nextPageToken?: Base64; +} + +/** + * Client for starting Workflow executions and creating Workflow handles + */ +export class ScheduleClient { + public readonly options: LoadedScheduleClientOptions; + public readonly connection: ConnectionLike; + + constructor(options?: ScheduleClientOptions) { + this.connection = options?.connection ?? Connection.lazy(); + this.options = { + ...defaultScheduleClientOptions(), + ...options, + loadedDataConverter: loadDataConverter(options?.dataConverter), + }; + } + + /** + * Raw gRPC access to the Temporal service. Schedule-related methods are included in {@link WorkflowService}. + * + * **NOTE**: The namespace provided in {@link options} is **not** automatically set on requests made to the service. + */ + get scheduleService(): WorkflowService { + return this.connection.workflowService; + } + + /** + * Set the deadline for any service requests executed in `fn`'s scope. + */ + async withDeadline(deadline: number | Date, fn: () => Promise): Promise { + return await this.connection.withDeadline(deadline, fn); + } + + /** + * Set metadata for any service requests executed in `fn`'s scope. + * + * @returns returned value of `fn` + * + * @see {@link Connection.withMetadata} + */ + async withMetadata(metadata: Metadata, fn: () => Promise): Promise { + return await this.connection.withMetadata(metadata, fn); + } + + /** + * Create a new Schedule. + */ + public async create(options: ScheduleOptions): Promise { } + + /** + * List Schedules with an `AsyncIterator`: + * + * ```ts + * for await (const schedule: Schedule of client.list()) { + * const { id, memo, searchAttributes } = schedule + * // ... + * } + * ``` + */ + public list(options?: ListScheduleOptions): AsyncIterator {} + + /** List Schedules, one page at a time */ + public async listByPage(options?: ListScheduleOptions): Promise {} + + /** + * Get a handle to a Schedule + * + * This method does not validate `scheduleId`. If there is no Schedule with the given `scheduleId`, handle + * methods like `handle.describe()` will throw a {@link ScheduleNotFoundError} error. + */ + public getHandle(scheduleId: string): ScheduleHandle { } +} ``` ### Higher-level client From 2dfc7dff52a87548b79019c316187a8f42fa304c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Wed, 10 Aug 2022 16:57:07 -0400 Subject: [PATCH 12/27] Address comments --- typescript/schedules.md | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index 1de090f..5c230f9 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -40,6 +40,7 @@ const schedule = await client.create({ ], endAt: addWeeks(new Date(), 4), jitter: '30s', + timezone: 'US/Pacific', }, action: { workflowId: 'wf-biz-id', @@ -487,12 +488,12 @@ export enum ScheduleOverlapPolicy { * Allow any number of Actions to start immediately. * * This is the only policy under which multiple Actions can run concurrently. - * - * {@link lastCompletionResult} and {@link lastFailure} will not be available. */ AllowAll, } +checkExtends(); + export interface Backfill { start: Date; end: Date; @@ -629,8 +630,7 @@ export type ZeroTo23 = export type Hour = Range | ZeroTo23; -export type ZeroTo30 = - | 0 +export type OneTo31 = | 1 | 2 | 3 @@ -660,11 +660,12 @@ export type ZeroTo30 = | 27 | 28 | 29 - | 30; + | 30 + | 31; -export type DayOfMonth = Range | ZeroTo30; +export type DayOfMonth = Range | OneTo31; -export type ZeroTo11 = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11; +export type OneTo12 = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; export type MonthName = | 'JAN' @@ -691,7 +692,7 @@ export type MonthName = | 'DEC' | 'DECEMBER'; -export type MonthValue = ZeroTo11 | MonthName; +export type MonthValue = OneTo12 | MonthName; export type Month = Range | MonthValue; @@ -941,9 +942,17 @@ export interface ScheduleSpec { */ jitter?: Milliseconds; + /** + * IANA timezone name, for example `US/Pacific`. + * + * https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + * + * @default UTC + */ + timezone?: string; + // Add to SDK if requested by users: // - // string timezone_name = 10; // bytes timezone_data = 11; // // Time zone to interpret all CalendarSpecs in. From 0ba9a12f157490f98b9be9a1d0bd88d6eb9d5c37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Thu, 11 Aug 2022 12:06:55 -0400 Subject: [PATCH 13/27] fixes --- typescript/schedules.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index 5c230f9..65403c1 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -94,7 +94,7 @@ await schedule.delete(); const { schedules, nextPageToken } = await client.listByPage({ pageSize: 50, - nextPageToken: 'base64', + nextPageToken: 'string', }); for await (const schedule: Schedule of client.list()) { @@ -210,8 +210,6 @@ export interface ScheduleHandle { readonly client: ScheduleClient; } -export type Base64 = string; - export interface Schedule { /** * Schedule Id @@ -244,7 +242,7 @@ export interface Schedule { * - the schedule may have been modified by {@link ScheduleHandle.update} or * {@link ScheduleHandle.pause}/{@link ScheduleHandle.unpause} */ -export interface ScheduleDescription { +export type ScheduleDescription = Schedule & { /** When Actions should be taken */ spec: RequireAtLeastOne; @@ -313,19 +311,18 @@ export interface ScheduleDescription { export interface ScheduleListPage { schedules: Schedule[]; - nextPageToken?: Base64; /** * Token to use to when calling {@link ScheduleClient.listByPage}. * * If `undefined`, there are no more Schedules. */ - nextPageToken?: Base64; + nextPageToken?: string; } export type WorkflowExecution = Required; -export type ScheduleInfo = Schedule & { +export interface ScheduleInfo { /** Number of Actions taken so far. */ numActionsTaken: number; // TODO or numberOfActionsTaken etc? @@ -358,7 +355,7 @@ export type ScheduleInfo = Schedule & { createdAt: Date; lastUpdatedAt: Date; - isValid(): boolean; + isValid: boolean; /** Error for invalid schedule. If this is present, no actions will be taken. */ invalidScheduleError?: string; @@ -898,12 +895,17 @@ export interface CalendarSpec { * instead. */ export interface IntervalSpec { - every: Seconds; + /** + * Value is rounded to the nearest second. + */ + every: Milliseconds; /** + * Value is rounded to the nearest second. + * * @default 0 */ - at?: Seconds; + at?: Milliseconds; } /** @@ -1118,7 +1120,7 @@ export interface ListScheduleOptions { pageSize?: number; /** Token to get the next page of results */ - nextPageToken?: Base64; + nextPageToken?: string; } /** From acc6acb9c80cb771598e72d8320c3d8caf2c1422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Wed, 17 Aug 2022 16:53:17 -0400 Subject: [PATCH 14/27] address review --- typescript/schedules.md | 707 +++++++++++++--------------------------- 1 file changed, 228 insertions(+), 479 deletions(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index 65403c1..13e796f 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -11,7 +11,7 @@ ```ts import { ScheduleClient, ScheduleOverlapPolicy } from '@temporalio/client' -import { addWeeks } from 'date-fns' +import { addWeeks, subDays } from 'date-fns' import { myWorkflow } from './workflows' const client = new ScheduleClient(); @@ -19,25 +19,36 @@ const client = new ScheduleClient(); const schedule = await client.create({ id: 'schedule-biz-id', spec: { - // every hour at minute 5 + // Schedule fires at combination of all `intervals` and `calendars`, minus the `skip` calendar: intervals: [ + // every hour at minute 5 { every: '1h', - at: '5m', + offset: '5m', + }, + // every 20 days since epoch at day 2 + { + every: '20d', + offset: '2d', }, ], - // every 20 days since epoch at day 2 - // intervals: [{ - // every: '20d', - // at: '2d' - // }] + calendars: [{ + // at noon on the 1st, 3rd, 5th, 7th, 9th, and 22nd days of the month + hour: 12, + dayOf: [{ + start: 1, + end: 10, + step: 2, + }, 22], + }], skip: [ { - // skip 11:05 pm + // skip 11:05 pm daily hour: 23, minute: 5, }, ], + startAt: new Date(), endAt: addWeeks(new Date(), 4), jitter: '30s', timezone: 'US/Pacific', @@ -47,31 +58,40 @@ const schedule = await client.create({ type: myWorkflow, args: ['arg1', 'arg2'], }, - overlap: ScheduleOverlapPolicy.BufferOne, + overlap: ScheduleOverlapPolicy.BUFFER_ONE, catchupWindow: '2m', pauseOnFailure: true, - note: 'started schedule', - pause: true, // this sets `state.paused: true` (default false) + note: 'started demo schedule', + paused: true, // start paused limitedActions: 10, triggerImmediately: true, backfill: [ { - start: new Date(), + start: subDays(new Date(), 5), end: new Date(), - overlap: ScheduleOverlapPolicy.AllowAll, + overlap: ScheduleOverlapPolicy.ALLOW_ALL, }, ], - memo, - searchAttributes, + memo: { startedBy: 'Loren' }, + searchAttributes: { SearchField: 'foo' }, }); -const schedule = client.getHandle('schedule-biz-id'); - const scheduleDescription = await schedule.describe(); +const sameSchedule = client.getHandle('schedule-biz-id'); + +const newSimilarSchedule = await client.create(scheduleDescription.copyOptions({ + id: 'new-id', + action: { + workflowId: 'wf-biz-id-2', + type: differentWorkflow, + } +}) + // For later, pending API finalization: // https://github.com/temporalio/proposals/pull/62/files#r933532170 // const matchingStartTimes = await schedule.listMatchingTimes({ start: new Date(), end: new Date() }) +// and/or: // const matchingStartTimes = await client.listMatchingTimes({ spec, start, end }) await schedule.update( @@ -79,10 +99,12 @@ await schedule.update( schedule.spec.intervals[0].every = '1d'; // unset with: // delete schedule.spec.intervals; - - // return false to stop retrying early + return schedule + // to not update or stop retrying early: + // import { CancelUpdate } from '@temporalio/client' + // `throw CancelUpdate` }, - { retry: retryPolicy } + { maximumAttempts: 10 } ); await schedule.trigger(); @@ -92,15 +114,19 @@ await schedule.unpause('now unpause'); await schedule.delete(); -const { schedules, nextPageToken } = await client.listByPage({ - pageSize: 50, - nextPageToken: 'string', -}); - for await (const schedule: Schedule of client.list()) { const { id, memo, searchAttributes } = schedule; // ... } + +const calender = fromCronStringToCalendarSpec('0 0 12 * * MON-WED,FRI') +// calendar = { +// hour: 12, +// dayOfWeek: [{ +// start: 'MONDAY' +// end: 'WEDNESDAY' +// }, 'FRIDAY'] +// } ``` ### Types @@ -123,36 +149,21 @@ import { ScheduleClientCallsInterceptor, ScheduleClientInterceptors } from './in import { ConnectionLike, Metadata, WorkflowService } from './types'; import { WorkflowHandle, WorkflowStartOptions } from './workflow-client'; -import type { NonNegativeInteger, RequireAtLeastOne } from 'type-fest'; - -// TODO is a non-generic NonNegativeInteger possible? The ones below error due to no type argument -export type PositiveInteger = Exclude; - -/** - * @format number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string} - */ -export type Milliseconds = string | NonNegativeInteger; - -/** - * @format number of seconds or {@link https://www.npmjs.com/package/ms | ms-formatted string} - */ -export type Seconds = string | NonNegativeInteger; - export interface UpdateScheduleOptions { /** - * @default TODO + * How many times to retry the update. + * + * Set to `1` if you don't want to retry. + * + * @default 3 */ - retry?: RetryPolicy; + maximumAttempts?: number; } -export interface Backfill { - /** Time range to evaluate Schedule in. */ - startAt: Date; - endAt: Date; - - /** Override Overlap Policy for this request. */ - overlapPolicyOverride?: ScheduleOverlapPolicy; +class CancelUpdate extends Error { + public readonly name: string = 'CancelUpdate'; } +export const CancelUpdate = new CancelUpdate() /** * Handle to a single Schedule @@ -165,8 +176,14 @@ export interface ScheduleHandle { /** * Update the Schedule + * + * This function calls `.describe()` and then tries to send the update to the Server. If the Schedule has changed + * between the time of `.describe()` and the update, the Server throws an error, and the SDK retries, up to + * {@link UpdateScheduleOptions.maximumAttempts}. + * + * If, inside `updateFn`, you no longer want the SDK to try sending the update to the Server, `throw CancelUpdate`. */ - update(mutateFn: (schedule: ScheduleDescription) => void | false, options?: UpdateScheduleOptions): Promise; + update(updateFn: (schedule: ScheduleDescription) => ScheduleDescription, options?: UpdateScheduleOptions): Promise; /** * Delete the Schedule @@ -175,8 +192,10 @@ export interface ScheduleHandle { /** * Trigger an Action to be taken immediately + * + * @param overlap Override the Overlap Policy for this one trigger. Defaults to {@link ScheduleOverlapPolicy.ALLOW_ALL}. */ - trigger(): Promise; + trigger(overlap?: ScheduleOverlapPolicy;): Promise; /** * Run though the specified time period(s) and take Actions as if that time passed by right now, all at once. The @@ -186,11 +205,17 @@ export interface ScheduleHandle { /** * Pause the Schedule + * + * @param note A new {@link ScheduleDescription.note}. Defaults to `"Paused via TypeScript SDK"` + * @throws {@link ValueError} if empty string is passed */ pause(note?: string): Promise; /** * Unpause the Schedule + * + * @param note A new {@link ScheduleDescription.note}. Defaults to `"Unpaused via TypeScript SDK"` + * @throws {@link ValueError} if empty string is passed */ unpause(note?: string): Promise; @@ -232,8 +257,34 @@ export interface Schedule { */ searchAttributes: SearchAttributes; - // TODO flatten this? - info: ScheduleInfo; + /** Number of Actions taken so far. */ + numActionsTaken: number; + // TODO or numberOfActionsTaken? + + /** Number of times a scheduled Action was skipped due to missing the catchup window. */ + numActionsMissedCatchupWindow: number; + + /** Number of Actions skipped due to overlap. */ + numActionsSkippedOverlap: number; + + /** + * Currently-running workflows started by this schedule. (There might be + * more than one if the overlap policy allows overlaps.) + */ + runningWorkflows: WorkflowExecutionWithFirstExecutionRunId[]; + + /** + * Most recent 10 Actions started (including manual triggers). + * + * Sorted from older start time to newer. + */ + recentActions: ScheduleAction[]; + + /** Next 10 scheduled Action times */ + nextActionTimes: Date[]; + + createdAt: Date; + lastUpdatedAt: Date; } /** @@ -255,7 +306,7 @@ export type ScheduleDescription = Schedule & { * Controls what happens when an Action would be started by a Schedule at the same time that an older Action is still * running. * - * @default {@link ScheduleOverlapPolicy.Skip} + * @default {@link ScheduleOverlapPolicy.SKIP} */ overlap: ScheduleOverlapPolicy; @@ -267,13 +318,14 @@ export type ScheduleDescription = Schedule & { * Window could lead to missed Actions. (But you can always {@link ScheduleHandle.backfill}.) * * @default 1 minute + * @format number of milliseconds */ - catchupWindow: Milliseconds; + catchupWindow: number; /** * When an Action times out or reaches the end of its Retry Policy, {@link pause}. * - * With {@link ScheduleOverlapPolicy.AllowAll}, this pause might not apply to the next Action, because the next Action + * With {@link ScheduleOverlapPolicy.ALLOW_ALL}, this pause might not apply to the next Action, because the next Action * might have already started previous to the failed one finishing. Pausing applies only to Actions that are scheduled * to start after the failed one finishes. * @@ -290,76 +342,42 @@ export type ScheduleDescription = Schedule & { /** * Is currently paused. - * + * * @default false */ paused: boolean; - /** Whether the number of Actions to take is limited. */ - actionsAreLimited: boolean; - /** - * Limit the number of Actions to take. - * - * This number is decremented after each Action is taken, and Actions are not - * taken when the number is `0` (unless {@link ScheduleHandle.trigger} is called). - * - * @default unlimited + * The Actions remaining in this Schedule. Once this number hits `0`, no further Actions are taken (unless {@link + * ScheduleHandle.trigger} is called). + * + * @default undefined (unlimited) */ - remainingActions?: NonNegativeInteger; -} - -export interface ScheduleListPage { - schedules: Schedule[]; + remainingActions?: number; /** - * Token to use to when calling {@link ScheduleClient.listByPage}. - * - * If `undefined`, there are no more Schedules. + * Create a {@link ScheduleOptions} object based on this `ScheduleDescription` to use with {@link ScheduleClient.create}. */ - nextPageToken?: string; + copyOptions(overrides: ScheduleOptionsOverrides): ScheduleOptions; } -export type WorkflowExecution = Required; - -export interface ScheduleInfo { - /** Number of Actions taken so far. */ - numActionsTaken: number; - // TODO or numberOfActionsTaken etc? - - /** Number of times a scheduled Action was skipped due to missing the catchup window. */ - numActionsMissedCatchupWindow: number; +/** + * Make all properties optional. + * + * If original Schedule is deleted, okay to use same `id`. + */ +export type ScheduleOptionsOverrides = Partial> - /** Number of Actions skipped due to overlap. */ - numActionsSkippedOverlap: number; - /** - * Currently-running workflows started by this schedule. (There might be - * more than one if the overlap policy allows overlaps.) - * Note that the run_ids in here are the original execution run ids as - * started by the schedule. If the workflows retried, did continue-as-new, - * or were reset, they might still be running but with a different run_id. - */ - runningWorkflows: WorkflowExecution[]; +export interface WorkflowExecutionWithFirstExecutionRunId { + workflowId: string; /** - * Most recent 10 Actions started (including manual triggers). - * - * Sorted from older start time to newer. + * The Run Id of the original execution that was started by the Schedule. If the Workflow retried, did + * Continue-As-New, or was Reset, the following runs would have different Run Ids. */ - recentActions: ScheduleAction[]; - - /** Next 10 scheduled Action times */ - nextActionTimes: Date[]; - - createdAt: Date; - lastUpdatedAt: Date; - - isValid: boolean; - - /** Error for invalid schedule. If this is present, no actions will be taken. */ - invalidScheduleError?: string; -}; + firstExecutionRunId: string; +} export interface ScheduleAction { /** Time that the Action was scheduled for, including jitter. */ @@ -369,8 +387,7 @@ export interface ScheduleAction { takenAt: Date; /** If action was {@link StartWorkflowAction}. */ - workflow?: WorkflowExecution; - // TODO or WorkflowHandle? would be more convenient, eg `const latestResult = await recentActions.pop().result()` + workflow?: WorkflowExecutionWithFirstExecutionRunId; } export interface ScheduleClientOptions { @@ -430,72 +447,65 @@ export function defaultScheduleClientOptions(): ScheduleClientOptionsWithDefault }; } -// TODO -// function ensureArgs>(opts: T): Omit & { args: unknown[] } { -// const { args, ...rest } = opts; -// return { args: args ?? [], ...rest }; -// } - -interface SchdeduleHandleOptions extends GetSchdeduleHandleOptions { - workflowId: string; - runId?: string; - interceptors: ScheduleClientCallsInterceptor[]; - /** - * A runId to use for getting the workflow's result. - * - * - When creating a handle using `getHandle`, uses the provided runId or firstExecutionRunId - * - When creating a handle using `start`, uses the returned runId (first in the chain) - * - When creating a handle using `signalWithStart`, uses the the returned runId - */ - runIdForResult?: string; -} - /** * Policy for overlapping Actions. */ export enum ScheduleOverlapPolicy { + /** + * Use server default (currently SKIP). + * + * TODO remove this field if this issue is implemented: https://github.com/temporalio/temporal/issues/3240 + */ + UNSPECIFIED = 0, + /** * Don't start a new Action. */ - Skip = 1, + SKIP, /** * Start another Action as soon as the current Action completes, but only buffer one Action in this way. If another * Action is supposed to start, but one Action is running and one is already buffered, then only the buffered one will * be started after the running Action finishes. */ - BufferOne, + BUFFER_ONE, /** * Allows an unlimited number of Actions to buffer. They are started sequentially. */ - BufferAll, + BUFFER_ALL, /** * Cancels the running Action, and then starts the new Action once the cancelled one completes. */ - CancelOther, + CANCEL_OTHER, /** * Terminate the running Action and start the new Action immediately. */ - TerminateOther, + TERMINATE_OTHER, /** * Allow any number of Actions to start immediately. * * This is the only policy under which multiple Actions can run concurrently. */ - AllowAll, + ALLOW_ALL, } -checkExtends(); +checkExtendsWithoutPrefix< + temporal.api.enums.v1.schedule.ScheduleOverlapPolicy, + ScheduleOverlapPolicy, + 'SCHEDULE_OVERLAP_POLICY_' +>(); export interface Backfill { + /** Time range to evaluate Schedule in. */ start: Date; end: Date; + /** - * Override overlap policy for this request. + * Override the Overlap Policy for this request. */ overlap?: ScheduleOverlapPolicy; } @@ -532,311 +542,34 @@ export interface Range { step?: PositiveInteger; } -export type AnyValue = '*'; - -export type ZeroTo59 = - | 0 - | 1 - | 2 - | 3 - | 4 - | 5 - | 6 - | 7 - | 8 - | 9 - | 10 - | 11 - | 12 - | 13 - | 14 - | 15 - | 16 - | 17 - | 18 - | 19 - | 20 - | 21 - | 22 - | 23 - | 24 - | 25 - | 26 - | 27 - | 28 - | 29 - | 30 - | 31 - | 32 - | 33 - | 34 - | 35 - | 36 - | 37 - | 38 - | 39 - | 40 - | 41 - | 42 - | 43 - | 44 - | 45 - | 46 - | 47 - | 48 - | 49 - | 50 - | 51 - | 52 - | 53 - | 54 - | 55 - | 56 - | 57 - | 58 - | 59; - -export type Second = Range | ZeroTo59; -export type Minute = Range | ZeroTo59; - -export type ZeroTo23 = - | 0 - | 1 - | 2 - | 3 - | 4 - | 5 - | 6 - | 7 - | 8 - | 9 - | 10 - | 11 - | 12 - | 13 - | 14 - | 15 - | 16 - | 17 - | 18 - | 19 - | 20 - | 21 - | 22 - | 23; - -export type Hour = Range | ZeroTo23; - -export type OneTo31 = - | 1 - | 2 - | 3 - | 4 - | 5 - | 6 - | 7 - | 8 - | 9 - | 10 - | 11 - | 12 - | 13 - | 14 - | 15 - | 16 - | 17 - | 18 - | 19 - | 20 - | 21 - | 22 - | 23 - | 24 - | 25 - | 26 - | 27 - | 28 - | 29 - | 30 - | 31; - -export type DayOfMonth = Range | OneTo31; - -export type OneTo12 = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; - -export type MonthName = - | 'JAN' - | 'JANUARY' - | 'FEB' - | 'FEBRUARY' - | 'MAR' - | 'MARCH' - | 'APR' - | 'APRIL' - | 'MAY' - | 'JUN' - | 'JUNE' - | 'JUL' - | 'JULY' - | 'AUG' - | 'AUGUST' - | 'SEP' - | 'SEPTEMBER' - | 'OCT' - | 'OCTOBER' - | 'NOV' - | 'NOVEMBER' - | 'DEC' - | 'DECEMBER'; - -export type MonthValue = OneTo12 | MonthName; - -export type Month = Range | MonthValue; - -export type TwoThousandTo2100 = - | 2000 - | 2001 - | 2002 - | 2003 - | 2004 - | 2005 - | 2006 - | 2007 - | 2008 - | 2009 - | 2010 - | 2011 - | 2012 - | 2013 - | 2014 - | 2015 - | 2016 - | 2017 - | 2018 - | 2019 - | 2020 - | 2021 - | 2022 - | 2023 - | 2024 - | 2025 - | 2026 - | 2027 - | 2028 - | 2029 - | 2030 - | 2031 - | 2032 - | 2033 - | 2034 - | 2035 - | 2036 - | 2037 - | 2038 - | 2039 - | 2040 - | 2041 - | 2042 - | 2043 - | 2044 - | 2045 - | 2046 - | 2047 - | 2048 - | 2049 - | 2050 - | 2051 - | 2052 - | 2053 - | 2054 - | 2055 - | 2056 - | 2057 - | 2058 - | 2059 - | 2060 - | 2061 - | 2062 - | 2063 - | 2064 - | 2065 - | 2066 - | 2067 - | 2068 - | 2069 - | 2070 - | 2071 - | 2072 - | 2073 - | 2074 - | 2075 - | 2076 - | 2077 - | 2078 - | 2079 - | 2080 - | 2081 - | 2082 - | 2083 - | 2084 - | 2085 - | 2086 - | 2087 - | 2088 - | 2089 - | 2090 - | 2091 - | 2092 - | 2093 - | 2094 - | 2095 - | 2096 - | 2097 - | 2098 - | 2099 - | 2100; +export type Second = Range | number; +export type Minute = Range | number; +export type Hour = Range | number; + +/** + * Valid values: 1-31 + */ +export type DayOfMonth = Range | number; /** - * Temporal Server currently only supports specifying years in the range `[2000, 2100]`. + * Recommend using these strings: JANUARY | FEBRUARY | MARCH | APRIL | MAY | JUNE | JULY | AUGUST | SEPTEMBER | OCTOBER | NOVEMBER | DECEMBER + * + * Currently, the server accepts variations, but this may change in future. */ -export type Year = Range | TwoThousandTo2100; +export type Month = Range | string; +// TODO we could throw error from SDK if not one of recommended values ? /** - * Both 0 and 7 mean Sunday + * Use full years, like 2030 */ -export type ZeroTo7 = 0 | 1 | 2 | 3 | 4 | 5 | 7; - -export type DayOfWeekName = - | 'SU' - | 'SUN' - | 'SUNDAY' - | 'MO' - | 'MON' - | 'MONDAY' - | 'TU' - | 'TUE' - | 'TUES' - | 'TUESDAY' - | 'WE' - | 'WED' - | 'WEDNESDAY' - | 'TH' - | 'THU' - | 'THUR' - | 'THURS' - | 'THURSDAY' - | 'FR' - | 'FRI' - | 'FRIDAY' - | 'SA' - | 'SAT' - | 'SATURDAY'; - -export type DayOfWeekValue = ZeroTo7 | DayOfWeekName; - -// TODO can we do something to make it case insensitive? -// export type DayOfWeekValue = ZeroTo7 | Uppercase extends DayOfWeekName ? T : DayOfWeekName -export type DayOfWeek = Range | DayOfWeekValue; +export type Year = Range | number; + +/** + * Recommend using these strings: SUNDAY | MONDAY | TUESDAY | WEDNESDAY | THURSDAY | FRIDAY | SATURDAY + * + * Currently, the server accepts variations, but this may change in future. + */ +export type DayOfWeek = Range | string; /** * An event specification relative to the calendar, similar to a traditional cron specification. @@ -847,65 +580,72 @@ export interface CalendarSpec { /** * @default 0 */ - second?: Second | Second[]; + second?: Second | Second[] | '*'; + // TODO or: + // second?: Second[]; /** * @default 0 */ - minute?: Minute | Minute[]; + minute?: Minute | Minute[] | '*'; /** * @default 0 */ - hour?: Hour | Hour[]; + hour?: Hour | Hour[] | '*'; /** - * @default {@link AnyValue} + * @default '*' */ - dayOfMonth?: DayOfMonth | DayOfMonth[]; + dayOfMonth?: DayOfMonth | DayOfMonth[] | '*'; /** - * @default {@link AnyValue} + * @default '*' */ - month?: Month | Month[]; + month?: Month | Month[] | '*'; /** - * @default {@link AnyValue} + * @default '*' */ - year?: Year | Year[]; + year?: Year | Year[] | '*'; /** - * Can accept 0 or 7 as Sunday. - * - * @default {@link AnyValue} + * @default '*' */ - dayOfWeek?: DayOfWeek | DayOfWeek[]; + dayOfWeek?: DayOfWeek | DayOfWeek[] | '*'; +} + +export function fromCronStringToCalendarSpec(cron: string): CalendarSpec { + ... } /** * IntervalSpec matches times that can be expressed as: * - * `Epoch + (n * every) + at` + * `Epoch + (n * every) + offset` * - * where `n` is any integer ≥ 0. + * where `n` is all integers ≥ 0. * - * For example, an `every` of 1 hour with `at` of zero would match every hour, on the hour. The same `every` but an `at` - * of 19 minutes would match every `xx:19:00`. An `every` of 28 days with `at` zero would match `2022-02-17T00:00:00Z` - * (among other times). The same `every` with `at` of 3 days, 5 hours, and 23 minutes would match `2022-02-20T05:23:00Z` + * For example, an `every` of 1 hour with `offset` of zero would match every hour, on the hour. The same `every` but an `offset` + * of 19 minutes would match every `xx:19:00`. An `every` of 28 days with `offset` zero would match `2022-02-17T00:00:00Z` + * (among other times). The same `every` with `offset` of 3 days, 5 hours, and 23 minutes would match `2022-02-20T05:23:00Z` * instead. */ export interface IntervalSpec { /** * Value is rounded to the nearest second. + * + * @format number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string} */ - every: Milliseconds; + every: number | string; /** * Value is rounded to the nearest second. * * @default 0 + * @format number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string} */ - at?: Milliseconds; + offset?: number | string; } /** @@ -941,8 +681,9 @@ export interface ScheduleSpec { * All timestamps will be incremented by a random value from 0 to this amount of jitter. * * @default 1 second + * @format number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string} */ - jitter?: Milliseconds; + jitter?: number | string; /** * IANA timezone name, for example `US/Pacific`. @@ -1034,7 +775,7 @@ export interface ScheduleOptions { * Controls what happens when an Action would be started by a Schedule at the same time that an older Action is still * running. * - * @default {@link ScheduleOverlapPolicy.Skip} + * @default {@link ScheduleOverlapPolicy.SKIP} */ overlap?: ScheduleOverlapPolicy; @@ -1046,13 +787,14 @@ export interface ScheduleOptions { * Window could lead to missed Actions. (But you can always {@link ScheduleHandle.backfill}.) * * @default 1 minute + * @format number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string} */ - catchupWindow?: Milliseconds; + catchupWindow?: number | string; /** * When an Action times out or reaches the end of its Retry Policy, {@link pause}. * - * With {@link ScheduleOverlapPolicy.AllowAll}, this pause might not apply to the next Action, because the next Action + * With {@link ScheduleOverlapPolicy.ALLOW_ALL}, this pause might not apply to the next Action, because the next Action * might have already started previous to the failed one finishing. Pausing applies only to Actions that are scheduled * to start after the failed one finishes. * @@ -1072,7 +814,7 @@ export interface ScheduleOptions { * * @default false */ - pause?: boolean; + paused?: boolean; /** * Limit the number of Actions to take. @@ -1082,7 +824,7 @@ export interface ScheduleOptions { * * @default unlimited */ - remainingActions?: NonNegativeInteger; + remainingActions?: number; /** * Trigger one Action immediately. @@ -1114,13 +856,21 @@ export interface ScheduleOptions { export interface ListScheduleOptions { /** - * How many results to return + * How many results to fetch from the Server at a time. * @default 1000 */ pageSize?: number; +} + +/** + * Thrown from {@link ScheduleClient.create} if there's a running (not deleted) Schedule with the given `id`. + */ +export class ScheduleAlreadyRunning extends Error { + public readonly name: string = 'ScheduleAlreadyRunning'; - /** Token to get the next page of results */ - nextPageToken?: string; + constructor(message: string, public readonly scheduleId: string) { + super(message); + } } /** @@ -1168,6 +918,8 @@ export class ScheduleClient { /** * Create a new Schedule. + * + * @throws {@link ScheduleAlreadyRunning} if there's a running (not deleted) Schedule with the given `id` */ public async create(options: ScheduleOptions): Promise { } @@ -1180,12 +932,15 @@ export class ScheduleClient { * // ... * } * ``` + * + * To list one page at a time, instead use the raw gRPC method {@link WorkflowService.listSchedules}: + * + * ```ts + * await { schedules, nextPageToken } = client.scheduleService.listSchedules() + * ``` */ public list(options?: ListScheduleOptions): AsyncIterator {} - /** List Schedules, one page at a time */ - public async listByPage(options?: ListScheduleOptions): Promise {} - /** * Get a handle to a Schedule * @@ -1196,7 +951,7 @@ export class ScheduleClient { } ``` -### Higher-level client +### Higher-level TS client ```ts import { Client } from '@temporalio/client' @@ -1229,12 +984,6 @@ interface ScheduleClientCallsInterceptor { * Intercept a service call to CreateSchedule */ create?: (input: ScheduleStartInput, next: Next) => Promise; - describe - listMatchingTimes - update - patch - delete - list } interface ScheduleClientCallsInterceptorFactoryInput { From e5254eddd1a83b92943dc5138308f6305d2e2304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Wed, 17 Aug 2022 17:06:35 -0400 Subject: [PATCH 15/27] update --- typescript/schedules.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index 13e796f..c7ff3ec 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -581,8 +581,6 @@ export interface CalendarSpec { * @default 0 */ second?: Second | Second[] | '*'; - // TODO or: - // second?: Second[]; /** * @default 0 From d6dc8920e55bf0bb03b0b3f30f13f87dee467ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=E2=98=BA=EF=B8=8F?= <251288+lorensr@users.noreply.github.com> Date: Tue, 23 Aug 2022 15:13:24 -0400 Subject: [PATCH 16/27] Update typescript/schedules.md --- typescript/schedules.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index c7ff3ec..a6cfc66 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -54,8 +54,9 @@ const schedule = await client.create({ timezone: 'US/Pacific', }, action: { + type: 'startWorkflow', workflowId: 'wf-biz-id', - type: myWorkflow, + workflowType: myWorkflow, args: ['arg1', 'arg2'], }, overlap: ScheduleOverlapPolicy.BUFFER_ONE, From fc2fa816460c796698446508176ffac5739e0854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=E2=98=BA=EF=B8=8F?= <251288+lorensr@users.noreply.github.com> Date: Tue, 23 Aug 2022 15:13:24 -0400 Subject: [PATCH 17/27] Update typescript/schedules.md --- typescript/schedules.md | 232 ++++++++++++++++++++++++++++------------ 1 file changed, 161 insertions(+), 71 deletions(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index a6cfc66..5b512f1 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -98,12 +98,9 @@ const newSimilarSchedule = await client.create(scheduleDescription.copyOptions({ await schedule.update( (schedule) => { schedule.spec.intervals[0].every = '1d'; - // unset with: - // delete schedule.spec.intervals; return schedule // to not update or stop retrying early: - // import { CancelUpdate } from '@temporalio/client' - // `throw CancelUpdate` + // return }, { maximumAttempts: 10 } ); @@ -119,15 +116,6 @@ for await (const schedule: Schedule of client.list()) { const { id, memo, searchAttributes } = schedule; // ... } - -const calender = fromCronStringToCalendarSpec('0 0 12 * * MON-WED,FRI') -// calendar = { -// hour: 12, -// dayOfWeek: [{ -// start: 'MONDAY' -// end: 'WEDNESDAY' -// }, 'FRIDAY'] -// } ``` ### Types @@ -141,7 +129,7 @@ import { Replace, RetryPolicy, SearchAttributes, - Workflow, + Workflow, // type Workflow = (...args: any[]) => WorkflowReturnType; } from '@temporalio/internal-workflow-common'; import { temporal } from '@temporalio/proto'; import os from 'os'; @@ -161,11 +149,6 @@ export interface UpdateScheduleOptions { maximumAttempts?: number; } -class CancelUpdate extends Error { - public readonly name: string = 'CancelUpdate'; -} -export const CancelUpdate = new CancelUpdate() - /** * Handle to a single Schedule */ @@ -182,9 +165,9 @@ export interface ScheduleHandle { * between the time of `.describe()` and the update, the Server throws an error, and the SDK retries, up to * {@link UpdateScheduleOptions.maximumAttempts}. * - * If, inside `updateFn`, you no longer want the SDK to try sending the update to the Server, `throw CancelUpdate`. + * If, inside `updateFn`, you no longer want the SDK to try sending the update to the Server, return undefined. */ - update(updateFn: (schedule: ScheduleDescription) => ScheduleDescription, options?: UpdateScheduleOptions): Promise; + update(updateFn: (schedule: ScheduleDescription) => ScheduleDescription | undefined, options?: UpdateScheduleOptions): Promise; /** * Delete the Schedule @@ -260,7 +243,6 @@ export interface Schedule { /** Number of Actions taken so far. */ numActionsTaken: number; - // TODO or numberOfActionsTaken? /** Number of times a scheduled Action was skipped due to missing the catchup window. */ numActionsMissedCatchupWindow: number; @@ -296,7 +278,7 @@ export interface Schedule { */ export type ScheduleDescription = Schedule & { /** When Actions should be taken */ - spec: RequireAtLeastOne; + spec: RequireAtLeastOne; /** * Which Action to take @@ -522,8 +504,8 @@ export interface Backfill { * {start, end, step} -> from start to end, by step * {step} -> all values, by step (implies start and end are full range for this field) * {end} and {end, step} are not allowed - * ``` * TODO is there a way to express not allowed ^ in type? + * ``` * * For example: * @@ -543,34 +525,19 @@ export interface Range { step?: PositiveInteger; } -export type Second = Range | number; -export type Minute = Range | number; -export type Hour = Range | number; +export type NumberRange = Range | number +export type NumberSpec = NumberRange | NumberRange[] | string; +export type NumberSpecDescription = Range[]; -/** - * Valid values: 1-31 - */ -export type DayOfMonth = Range | number; +export type Month = 'JANUARY' | 'FEBRUARY' | 'MARCH' | 'APRIL' | 'MAY' | 'JUNE' | 'JULY' | 'AUGUST' | 'SEPTEMBER' | 'OCTOBER' | 'NOVEMBER' | 'DECEMBER'; +export type MonthRange = Range | Month; +export type MonthSpec = MonthRange | MonthRange[] | string; +export type MonthSpecDescription = Range[]; -/** - * Recommend using these strings: JANUARY | FEBRUARY | MARCH | APRIL | MAY | JUNE | JULY | AUGUST | SEPTEMBER | OCTOBER | NOVEMBER | DECEMBER - * - * Currently, the server accepts variations, but this may change in future. - */ -export type Month = Range | string; -// TODO we could throw error from SDK if not one of recommended values ? - -/** - * Use full years, like 2030 - */ -export type Year = Range | number; - -/** - * Recommend using these strings: SUNDAY | MONDAY | TUESDAY | WEDNESDAY | THURSDAY | FRIDAY | SATURDAY - * - * Currently, the server accepts variations, but this may change in future. - */ -export type DayOfWeek = Range | string; +export type Day = 'SUNDAY' | 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY'; +export type DayRange = Range | Day; +export type DaySpec = DayRange | DayRange[] | string; +export type DaySpecDescription = Range[]; /** * An event specification relative to the calendar, similar to a traditional cron specification. @@ -579,43 +546,99 @@ export type DayOfWeek = Range | string; */ export interface CalendarSpec { /** + * Valid values: 0–59 + * * @default 0 */ - second?: Second | Second[] | '*'; + second?: NumberSpec; /** + * Valid values: 0–59 + * * @default 0 */ - minute?: Minute | Minute[] | '*'; + minute?: NumberSpec; /** + * Valid values: 0–59 + * * @default 0 */ - hour?: Hour | Hour[] | '*'; + hour?: NumberSpec; /** + * Valid values: 1–31 + * * @default '*' */ - dayOfMonth?: DayOfMonth | DayOfMonth[] | '*'; + dayOfMonth?: NumberSpec; /** * @default '*' */ - month?: Month | Month[] | '*'; + month?: MonthSpec; /** + * Use full years, like `2030` + * * @default '*' */ - year?: Year | Year[] | '*'; + year?: NumberSpec; /** * @default '*' */ - dayOfWeek?: DayOfWeek | DayOfWeek[] | '*'; + dayOfWeek?: DaySpec; } -export function fromCronStringToCalendarSpec(cron: string): CalendarSpec { - ... +/** + * The version of {@link CalendarSpec} that you get back from {@link ScheduleHandle.describe} + */ +export interface CalendarSpecDescription { + /** + * Valid values: 0–59 + * + * @default `[{ start: 0 }]` + */ + second?: NumberSpecDescription; + + /** + * Valid values: 0–59 + * + * @default `[{ start: 0 }]` + */ + minute?: NumberSpecDescription; + + /** + * Valid values: 0–59 + * + * @default `[{ start: 0 }]` + */ + hour?: NumberSpecDescription; + + /** + * Valid values: 1–31 + * + * @default `[{ start: 1, end: 31 }]` + */ + dayOfMonth?: NumberSpecDescription; + + /** + * @default `[{ start: 'JANUARY' , end: 'DECEMBER' }]` + */ + month?: MonthSpecDescription; + + /** + * Use full years, like `2030` + * + * @default All possible values + */ + year?: NumberSpecDescription; + + /** + * @default `[{ start: 'SUNDAY' , end: 'SATURDAY' }]` + */ + dayOfWeek?: DaySpecDescription; } /** @@ -648,7 +671,27 @@ export interface IntervalSpec { } /** - * A complete description of a set of absolute timestamps (possibly infinite) that an Action should occur at. These times + * The version of {@link IntervalSpec} that you get back from {@link ScheduleHandle.describe} + */ +export interface IntervalSpecDescription { + /** + * Value is rounded to the nearest second. + * + * @format number of milliseconds + */ + every: number; + + /** + * Value is rounded to the nearest second. + * + * @default 0 + * @format number of milliseconds + */ + offset?: number; +} + +/** + * A complete description of a set of absolute times (possibly infinite) that an Action should occur at. These times * never change, except that the definition of a time zone can change over time (most commonly, when daylight saving * time policy changes for an area). To create a totally self-contained `ScheduleSpec`, use UTC. */ @@ -659,25 +702,42 @@ export interface ScheduleSpec { /** Interval-based specifications of times. */ intervals?: IntervalSpec[]; - /** Any timestamps matching any of the exclude_calendar specs will be skipped. */ + /** + * [Cron expressions](https://crontab.guru/) + * + * For example, `0 12 * * MON-WED,FRI` is every M/Tu/W/F at noon, and is equivalent to this {@link CalendarSpec}: + * + * ```ts + * { + * hour: 12, + * dayOfWeek: [{ + * start: 'MONDAY' + * end: 'WEDNESDAY' + * }, 'FRIDAY'] + * } + * ``` + */ + cronExpressions?: string[]; + + /** Any matching times will be skipped. */ skip?: CalendarSpec[]; /** - * Any timestamps before `startAt` will be skipped. Together, `startAt` and `endAt` make an inclusive interval. + * Any times before `startAt` will be skipped. Together, `startAt` and `endAt` make an inclusive interval. * * @default The beginning of time */ startAt?: Date; /** - * Any timestamps after `endAt` will be skipped. + * Any times after `endAt` will be skipped. * * @default The end of time */ endAt?: Date; /** - * All timestamps will be incremented by a random value from 0 to this amount of jitter. + * All times will be incremented by a random value from 0 to this amount of jitter. * * @default 1 second * @format number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string} @@ -719,10 +779,43 @@ export interface ScheduleSpec { // fires at 1:30am will be triggered twice on the day that has two 1:30s. } +/** + * The version of {@link ScheduleSpec} that you get back from {@link ScheduleHandle.describe} + */ +export type ScheduleSpecDescription = Omit & { + /** Calendar-based specifications of times. */ + calendars?: CalendarSpecDescription[]; + + /** Interval-based specifications of times. */ + intervals?: IntervalSpecDescription[]; + + /** Any matching times will be skipped. */ + skip?: CalendarSpecDescription[]; + + /** + * All times will be incremented by a random value from 0 to this amount of jitter. + * + * @default 1 second + * @format number of milliseconds + */ + jitter?: number; +} + export type StartWorkflowAction = Omit< WorkflowStartOptions, 'workflowIdReusePolicy' | 'cronSchedule' | 'followRuns' > & { + // This is most convenient for TS typing. Other SDKs may want to implement this differently, for example nesting: + // action: { + // startWorkflow: { + // workflowId: 'wf-biz-id', + // ... + // } + // } + type: 'startWorkflow', + + workflowType: string | Action; + /** * Metadata that's propagated between workflows and activities? TODO someone explain headers to me so I can improve * this apidoc and maybe: @@ -739,15 +832,12 @@ export type ScheduleActionType = Workflow; */ export type ScheduleActionOptions = StartWorkflowAction; // in future: -// type SomethingElse = 'hi' -// type ScheduleAction = Workflow | SomethingElse +// type SomethingElse = { fieldFoo: string, type: 'startFoo' } +// type ScheduleActionType = Workflow | SomethingElse // type ExpectsSomethingElse = Action extends SomethingElse ? Action : number; // type StartSomethingElseAction = ExpectsSomethingElse -// type ScheduleActionOptions = Action extends Workflow -// ? StartWorkflowAction -// : Action extends SomethingElse -// ? StartSomethingElseAction -// : never; +// type ScheduleActionOptions = +// StartWorkflowAction | StartSomethingElseAction export type HandleFor = T extends Workflow ? WorkflowHandle : never; From b8434d8f3fcedbf67e2c06c5e65ad195d08f8206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Wed, 24 Aug 2022 13:47:04 -0400 Subject: [PATCH 18/27] comments --- typescript/schedules.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index 5b512f1..629351e 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -208,9 +208,7 @@ export interface ScheduleHandle { */ describe(): Promise; - /** - * Get a handle to the most recent Action started - */ + // we won't have this helper in TS because it involves an implicit client, but other SDKs may want this lastAction(): Promise>; /** From f5aa6044c349a588afa2d0238be7f85823d326cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Thu, 25 Aug 2022 14:56:13 -0400 Subject: [PATCH 19/27] fix ListScheduleEntry, add comments --- typescript/schedules.md | 106 +++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 49 deletions(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index 629351e..1a87d25 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -167,7 +167,7 @@ export interface ScheduleHandle { * * If, inside `updateFn`, you no longer want the SDK to try sending the update to the Server, return undefined. */ - update(updateFn: (schedule: ScheduleDescription) => ScheduleDescription | undefined, options?: UpdateScheduleOptions): Promise; + update(updateFn: (schedule: Schedule) => Schedule | undefined, options?: UpdateScheduleOptions): Promise; /** * Delete the Schedule @@ -190,7 +190,7 @@ export interface ScheduleHandle { /** * Pause the Schedule * - * @param note A new {@link ScheduleDescription.note}. Defaults to `"Paused via TypeScript SDK"` + * @param note A new {@link Schedule.note}. Defaults to `"Paused via TypeScript SDK"` * @throws {@link ValueError} if empty string is passed */ pause(note?: string): Promise; @@ -198,7 +198,7 @@ export interface ScheduleHandle { /** * Unpause the Schedule * - * @param note A new {@link ScheduleDescription.note}. Defaults to `"Unpaused via TypeScript SDK"` + * @param note A new {@link Schedule.note}. Defaults to `"Unpaused via TypeScript SDK"` * @throws {@link ValueError} if empty string is passed */ unpause(note?: string): Promise; @@ -206,7 +206,7 @@ export interface ScheduleHandle { /** * Fetch the Schedule's description from the Server */ - describe(): Promise; + describe(): Promise; // we won't have this helper in TS because it involves an implicit client, but other SDKs may want this lastAction(): Promise>; @@ -217,7 +217,7 @@ export interface ScheduleHandle { readonly client: ScheduleClient; } -export interface Schedule { +export interface ListScheduleEntry { /** * Schedule Id * @@ -225,6 +225,9 @@ export interface Schedule { */ id: string; + /** When Actions are taken */ + spec: RequireAtLeastOne; + /** * Additional non-indexed information attached to the Schedule. The values can be anything that is * serializable by the {@link DataConverter}. @@ -239,20 +242,24 @@ export interface Schedule { */ searchAttributes: SearchAttributes; - /** Number of Actions taken so far. */ - numActionsTaken: number; - - /** Number of times a scheduled Action was skipped due to missing the catchup window. */ - numActionsMissedCatchupWindow: number; + /** + * Informative human-readable message with contextual notes, e.g. the reason + * a Schedule is paused. The system may overwrite this message on certain + * conditions, e.g. when pause-on-failure happens. + */ + note?: string; - /** Number of Actions skipped due to overlap. */ - numActionsSkippedOverlap: number; + /** + * Whether Schedule is currently paused. + * + * @default false + */ + paused: boolean; /** - * Currently-running workflows started by this schedule. (There might be - * more than one if the overlap policy allows overlaps.) + * Present if action is a {@link StartWorkflowAction}. */ - runningWorkflows: WorkflowExecutionWithFirstExecutionRunId[]; + workflowType?: string; /** * Most recent 10 Actions started (including manual triggers). @@ -263,9 +270,6 @@ export interface Schedule { /** Next 10 scheduled Action times */ nextActionTimes: Date[]; - - createdAt: Date; - lastUpdatedAt: Date; } /** @@ -274,10 +278,7 @@ export interface Schedule { * - the schedule may have been modified by {@link ScheduleHandle.update} or * {@link ScheduleHandle.pause}/{@link ScheduleHandle.unpause} */ -export type ScheduleDescription = Schedule & { - /** When Actions should be taken */ - spec: RequireAtLeastOne; - +export type Schedule = ListScheduleEntry & { /** * Which Action to take */ @@ -314,20 +315,6 @@ export type ScheduleDescription = Schedule & { */ pauseOnFailure: boolean; - /** - * Informative human-readable message with contextual notes, e.g. the reason - * a Schedule is paused. The system may overwrite this message on certain - * conditions, e.g. when pause-on-failure happens. - */ - note?: string; - - /** - * Is currently paused. - * - * @default false - */ - paused: boolean; - /** * The Actions remaining in this Schedule. Once this number hits `0`, no further Actions are taken (unless {@link * ScheduleHandle.trigger} is called). @@ -336,8 +323,27 @@ export type ScheduleDescription = Schedule & { */ remainingActions?: number; + + /** Number of Actions taken so far. */ + numActionsTaken: number; + + /** Number of times a scheduled Action was skipped due to missing the catchup window. */ + numActionsMissedCatchupWindow: number; + + /** Number of Actions skipped due to overlap. */ + numActionsSkippedOverlap: number; + /** - * Create a {@link ScheduleOptions} object based on this `ScheduleDescription` to use with {@link ScheduleClient.create}. + * Currently-running workflows started by this schedule. (There might be + * more than one if the overlap policy allows overlaps.) + */ + runningWorkflows: WorkflowExecutionWithFirstExecutionRunId[]; + + createdAt: Date; + lastUpdatedAt: Date; + + /** + * Create a {@link ScheduleOptions} object based on this `Schedule` to use with {@link ScheduleClient.create}. */ copyOptions(overrides: ScheduleOptionsOverrides): ScheduleOptions; } @@ -737,7 +743,7 @@ export interface ScheduleSpec { /** * All times will be incremented by a random value from 0 to this amount of jitter. * - * @default 1 second + * @default 0 * @format number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string} */ jitter?: number | string; @@ -747,6 +753,16 @@ export interface ScheduleSpec { * * https://en.wikipedia.org/wiki/List_of_tz_database_time_zones * + * The definition will be loaded by Temporal Server from the environment it runs in. + * + * Calendar spec matching is based on literal matching of the clock time + * with no special handling of DST: if you write a calendar spec that fires + * at 2:30am and specify a time zone that follows DST, that action will not + * be triggered on the day that has no 2:30am. Similarly, an action that + * fires at 1:30am will be triggered twice on the day that has two 1:30s. + * + * Also note that no actions are taken on leap-seconds (e.g. 23:59:60 UTC). + * * @default UTC */ timezone?: string; @@ -757,9 +773,6 @@ export interface ScheduleSpec { // // Time zone to interpret all CalendarSpecs in. // - // If unset, defaults to UTC. We recommend using UTC for your application if - // at all possible, to avoid various surprising properties of time zones. - // // Time zones may be provided by name, corresponding to names in the IANA // time zone database (see https://www.iana.org/time-zones). The definition // will be loaded by the Temporal server from the environment it runs in. @@ -769,12 +782,6 @@ export interface ScheduleSpec { // from the time zone database. If present, this will be used instead of // loading anything from the environment. You are then responsible for // updating timezone_data when the definition changes. - // - // Calendar spec matching is based on literal matching of the clock time - // with no special handling of DST: if you write a calendar spec that fires - // at 2:30am and specify a time zone that follows DST, that action will not - // be triggered on the day that has no 2:30am. Similarly, an action that - // fires at 1:30am will be triggered twice on the day that has two 1:30s. } /** @@ -860,7 +867,8 @@ export interface ScheduleOptions { /** * Controls what happens when an Action would be started by a Schedule at the same time that an older Action is still - * running. + * running. This can be changed after a Schedule has taken some Actions, and some changes might produce + * unintuitive results. In general, the later policy overrides the earlier policy. * * @default {@link ScheduleOverlapPolicy.SKIP} */ @@ -1026,7 +1034,7 @@ export class ScheduleClient { * await { schedules, nextPageToken } = client.scheduleService.listSchedules() * ``` */ - public list(options?: ListScheduleOptions): AsyncIterator {} + public list(options?: ListScheduleOptions): AsyncIterator {} /** * Get a handle to a Schedule From 80e1d78813bf01c4b1424f43e32923423e3d7d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Fri, 16 Sep 2022 01:29:33 -0400 Subject: [PATCH 20/27] Updates from API update --- typescript/schedules.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index 1a87d25..809828a 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -635,12 +635,12 @@ export interface CalendarSpecDescription { /** * Use full years, like `2030` * - * @default All possible values + * @default `[{ start: 2000, step: 1 }]` */ year?: NumberSpecDescription; /** - * @default `[{ start: 'SUNDAY' , end: 'SATURDAY' }]` + * @default `[{ start: 'SUNDAY', end: 'SATURDAY' }]` */ dayOfWeek?: DaySpecDescription; } @@ -723,8 +723,14 @@ export interface ScheduleSpec { */ cronExpressions?: string[]; - /** Any matching times will be skipped. */ + /** + * Any matching times will be skipped. + * + * All aspects of the CalendarSpec—including seconds—must match a time for the time to be skipped. + */ skip?: CalendarSpec[]; + // TODO see if users want to be able to skip an IntervalSpec + // https://github.com/temporalio/api/pull/230/files#r956434347 /** * Any times before `startAt` will be skipped. Together, `startAt` and `endAt` make an inclusive interval. From 87821f7738da2ace5682d0e3efb91ee7b2bb877c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Fri, 16 Sep 2022 01:30:48 -0400 Subject: [PATCH 21/27] prettier --- typescript/schedules.md | 135 +++++++++++++++++++++------------------- 1 file changed, 70 insertions(+), 65 deletions(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index 809828a..48271f3 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -81,7 +81,7 @@ const scheduleDescription = await schedule.describe(); const sameSchedule = client.getHandle('schedule-biz-id'); -const newSimilarSchedule = await client.create(scheduleDescription.copyOptions({ +const newSimilarSchedule = await client.create(scheduleDescription.copyOptions({ id: 'new-id', action: { workflowId: 'wf-biz-id-2', @@ -120,7 +120,7 @@ for await (const schedule: Schedule of client.list()) { ### Types -```ts +````ts import { DataConverter, LoadedDataConverter } from '@temporalio/common'; import { loadDataConverter } from '@temporalio/internal-non-workflow-common'; import { @@ -141,9 +141,9 @@ import { WorkflowHandle, WorkflowStartOptions } from './workflow-client'; export interface UpdateScheduleOptions { /** * How many times to retry the update. - * + * * Set to `1` if you don't want to retry. - * + * * @default 3 */ maximumAttempts?: number; @@ -160,12 +160,12 @@ export interface ScheduleHandle { /** * Update the Schedule - * - * This function calls `.describe()` and then tries to send the update to the Server. If the Schedule has changed - * between the time of `.describe()` and the update, the Server throws an error, and the SDK retries, up to + * + * This function calls `.describe()` and then tries to send the update to the Server. If the Schedule has changed + * between the time of `.describe()` and the update, the Server throws an error, and the SDK retries, up to * {@link UpdateScheduleOptions.maximumAttempts}. - * - * If, inside `updateFn`, you no longer want the SDK to try sending the update to the Server, return undefined. + * + * If, inside `updateFn`, you no longer want the SDK to try sending the update to the Server, return undefined. */ update(updateFn: (schedule: Schedule) => Schedule | undefined, options?: UpdateScheduleOptions): Promise; @@ -176,7 +176,7 @@ export interface ScheduleHandle { /** * Trigger an Action to be taken immediately - * + * * @param overlap Override the Overlap Policy for this one trigger. Defaults to {@link ScheduleOverlapPolicy.ALLOW_ALL}. */ trigger(overlap?: ScheduleOverlapPolicy;): Promise; @@ -189,7 +189,7 @@ export interface ScheduleHandle { /** * Pause the Schedule - * + * * @param note A new {@link Schedule.note}. Defaults to `"Paused via TypeScript SDK"` * @throws {@link ValueError} if empty string is passed */ @@ -197,7 +197,7 @@ export interface ScheduleHandle { /** * Unpause the Schedule - * + * * @param note A new {@link Schedule.note}. Defaults to `"Unpaused via TypeScript SDK"` * @throws {@link ValueError} if empty string is passed */ @@ -251,7 +251,7 @@ export interface ListScheduleEntry { /** * Whether Schedule is currently paused. - * + * * @default false */ paused: boolean; @@ -316,9 +316,9 @@ export type Schedule = ListScheduleEntry & { pauseOnFailure: boolean; /** - * The Actions remaining in this Schedule. Once this number hits `0`, no further Actions are taken (unless {@link + * The Actions remaining in this Schedule. Once this number hits `0`, no further Actions are taken (unless {@link * ScheduleHandle.trigger} is called). - * + * * @default undefined (unlimited) */ remainingActions?: number; @@ -349,8 +349,8 @@ export type Schedule = ListScheduleEntry & { } /** - * Make all properties optional. - * + * Make all properties optional. + * * If original Schedule is deleted, okay to use same `id`. */ export type ScheduleOptionsOverrides = Partial> @@ -360,7 +360,7 @@ export interface WorkflowExecutionWithFirstExecutionRunId { workflowId: string; /** - * The Run Id of the original execution that was started by the Schedule. If the Workflow retried, did + * The Run Id of the original execution that was started by the Schedule. If the Workflow retried, did * Continue-As-New, or was Reset, the following runs would have different Run Ids. */ firstExecutionRunId: string; @@ -440,11 +440,11 @@ export function defaultScheduleClientOptions(): ScheduleClientOptionsWithDefault export enum ScheduleOverlapPolicy { /** * Use server default (currently SKIP). - * + * * TODO remove this field if this issue is implemented: https://github.com/temporalio/temporal/issues/3240 */ UNSPECIFIED = 0, - + /** * Don't start a new Action. */ @@ -551,28 +551,28 @@ export type DaySpecDescription = Range[]; export interface CalendarSpec { /** * Valid values: 0–59 - * + * * @default 0 */ second?: NumberSpec; /** * Valid values: 0–59 - * + * * @default 0 */ minute?: NumberSpec; /** * Valid values: 0–59 - * + * * @default 0 */ hour?: NumberSpec; /** * Valid values: 1–31 - * + * * @default '*' */ dayOfMonth?: NumberSpec; @@ -584,7 +584,7 @@ export interface CalendarSpec { /** * Use full years, like `2030` - * + * * @default '*' */ year?: NumberSpec; @@ -601,28 +601,28 @@ export interface CalendarSpec { export interface CalendarSpecDescription { /** * Valid values: 0–59 - * + * * @default `[{ start: 0 }]` */ second?: NumberSpecDescription; /** * Valid values: 0–59 - * + * * @default `[{ start: 0 }]` */ minute?: NumberSpecDescription; /** * Valid values: 0–59 - * + * * @default `[{ start: 0 }]` */ hour?: NumberSpecDescription; /** * Valid values: 1–31 - * + * * @default `[{ start: 1, end: 31 }]` */ dayOfMonth?: NumberSpecDescription; @@ -634,7 +634,7 @@ export interface CalendarSpecDescription { /** * Use full years, like `2030` - * + * * @default `[{ start: 2000, step: 1 }]` */ year?: NumberSpecDescription; @@ -660,14 +660,14 @@ export interface CalendarSpecDescription { export interface IntervalSpec { /** * Value is rounded to the nearest second. - * + * * @format number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string} */ every: number | string; /** * Value is rounded to the nearest second. - * + * * @default 0 * @format number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string} */ @@ -680,14 +680,14 @@ export interface IntervalSpec { export interface IntervalSpecDescription { /** * Value is rounded to the nearest second. - * + * * @format number of milliseconds */ every: number; /** * Value is rounded to the nearest second. - * + * * @default 0 * @format number of milliseconds */ @@ -708,9 +708,9 @@ export interface ScheduleSpec { /** * [Cron expressions](https://crontab.guru/) - * + * * For example, `0 12 * * MON-WED,FRI` is every M/Tu/W/F at noon, and is equivalent to this {@link CalendarSpec}: - * + * * ```ts * { * hour: 12, @@ -720,12 +720,12 @@ export interface ScheduleSpec { * }, 'FRIDAY'] * } * ``` - */ + */ cronExpressions?: string[]; - /** - * Any matching times will be skipped. - * + /** + * Any matching times will be skipped. + * * All aspects of the CalendarSpec—including seconds—must match a time for the time to be skipped. */ skip?: CalendarSpec[]; @@ -756,11 +756,11 @@ export interface ScheduleSpec { /** * IANA timezone name, for example `US/Pacific`. - * + * * https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - * + * * The definition will be loaded by Temporal Server from the environment it runs in. - * + * * Calendar spec matching is based on literal matching of the clock time * with no special handling of DST: if you write a calendar spec that fires * at 2:30am and specify a time zone that follows DST, that action will not @@ -768,7 +768,7 @@ export interface ScheduleSpec { * fires at 1:30am will be triggered twice on the day that has two 1:30s. * * Also note that no actions are taken on leap-seconds (e.g. 23:59:60 UTC). - * + * * @default UTC */ timezone?: string; @@ -847,7 +847,7 @@ export type ScheduleActionOptions = Action extends SomethingElse ? Action : number; // type StartSomethingElseAction = ExpectsSomethingElse -// type ScheduleActionOptions = +// type ScheduleActionOptions = // StartWorkflowAction | StartSomethingElseAction export type HandleFor = T extends Workflow ? WorkflowHandle : never; @@ -873,7 +873,7 @@ export interface ScheduleOptions { /** * Controls what happens when an Action would be started by a Schedule at the same time that an older Action is still - * running. This can be changed after a Schedule has taken some Actions, and some changes might produce + * running. This can be changed after a Schedule has taken some Actions, and some changes might produce * unintuitive results. In general, the later policy overrides the earlier policy. * * @default {@link ScheduleOverlapPolicy.SKIP} @@ -967,7 +967,7 @@ export interface ListScheduleOptions { * Thrown from {@link ScheduleClient.create} if there's a running (not deleted) Schedule with the given `id`. */ export class ScheduleAlreadyRunning extends Error { - public readonly name: string = 'ScheduleAlreadyRunning'; + public readonly name: string = 'ScheduleAlreadyRunning'; constructor(message: string, public readonly scheduleId: string) { super(message); @@ -1019,7 +1019,7 @@ export class ScheduleClient { /** * Create a new Schedule. - * + * * @throws {@link ScheduleAlreadyRunning} if there's a running (not deleted) Schedule with the given `id` */ public async create(options: ScheduleOptions): Promise { } @@ -1033,9 +1033,9 @@ export class ScheduleClient { * // ... * } * ``` - * + * * To list one page at a time, instead use the raw gRPC method {@link WorkflowService.listSchedules}: - * + * * ```ts * await { schedules, nextPageToken } = client.scheduleService.listSchedules() * ``` @@ -1050,7 +1050,7 @@ export class ScheduleClient { */ public getHandle(scheduleId: string): ScheduleHandle { } } -``` +```` ### Higher-level TS client @@ -1060,20 +1060,20 @@ import { Client } from '@temporalio/client' const client = new Client(options) client.schedule.create() -client.workflow.start() +client.workflow.start() client.asyncCompletion.heartbeat() client.operator.addSearchAttributes() interface ClientOptions { - dataConverter?: DataConverter; + dataConverter?: DataConverter interceptors?: { - workflow: WorkflowClientInterceptors, - schedule: ScheduleClientInterceptors, - }; - identity?: string; - connection?: ConnectionLike; - namespace?: string; - queryRejectCondition?: temporal.api.enums.v1.QueryRejectCondition; + workflow: WorkflowClientInterceptors + schedule: ScheduleClientInterceptors + } + identity?: string + connection?: ConnectionLike + namespace?: string + queryRejectCondition?: temporal.api.enums.v1.QueryRejectCondition } ``` @@ -1084,24 +1084,29 @@ interface ScheduleClientCallsInterceptor { /** * Intercept a service call to CreateSchedule */ - create?: (input: ScheduleStartInput, next: Next) => Promise; + create?: ( + input: ScheduleStartInput, + next: Next + ) => Promise } interface ScheduleClientCallsInterceptorFactoryInput { - id: string; + id: string } /** * A function that takes a {@link ScheduleClientCallsInterceptorFactoryInput} and returns an interceptor */ export interface ScheduleClientCallsInterceptorFactory { - (input: ScheduleClientCallsInterceptorFactoryInput): ScheduleClientCallsInterceptor; + ( + input: ScheduleClientCallsInterceptorFactoryInput + ): ScheduleClientCallsInterceptor } /** * A mapping of interceptor type of a list of factory functions */ export interface ScheduleClientInterceptors { - calls?: ScheduleClientCallsInterceptorFactory[]; + calls?: ScheduleClientCallsInterceptorFactory[] } -``` \ No newline at end of file +``` From aec444413bf6885df235635e18ba61a752d51f60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Fri, 16 Sep 2022 02:41:54 -0400 Subject: [PATCH 22/27] Update --- typescript/schedules.md | 47 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index 48271f3..352e263 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -695,7 +695,8 @@ export interface IntervalSpecDescription { } /** - * A complete description of a set of absolute times (possibly infinite) that an Action should occur at. These times + * A complete description of a set of absolute times (possibly infinite) that an Action should occur at. + * The times are the union of `calendars`, `intervals`, and `cronExpressions`, minus the `skip` times. These times * never change, except that the definition of a time zone can change over time (most commonly, when daylight saving * time policy changes for an area). To create a totally self-contained `ScheduleSpec`, use UTC. */ @@ -720,6 +721,29 @@ export interface ScheduleSpec { * }, 'FRIDAY'] * } * ``` + * + * The string can have 5, 6, or 7 fields, separated by spaces, and they are interpreted in the + * same way as a {@link CalendarSpec}. + * + * - 5 fields: minute, hour, day_of_month, month, day_of_week + * - 6 fields: minute, hour, day_of_month, month, day_of_week, year + * - 7 fields: second, minute, hour, day_of_month, month, day_of_week, year + * + * Notes: + * + * - If year is not given, it defaults to *. + * - If second is not given, it defaults to 0. + * - Shorthands @yearly, @monthly, @weekly, @daily, and @hourly are also + * accepted instead of the 5-7 time fields. + * - @every [/] is accepted and gets compiled into an + * IntervalSpec instead. and should be a decimal integer + * with a unit suffix s, m, h, or d. + * - Optionally, the string can be preceded by CRON_TZ= or + * TZ=, which will get copied to {@link timezone}. (In which case the {@link timezone} field should be left empty.) + * - Optionally, "#" followed by a comment can appear at the end of the string. + * - Note that the special case that some cron implementations have for + * treating day_of_month and day_of_week as "or" instead of "and" when both + * are set is not implemented. */ cronExpressions?: string[]; @@ -1110,3 +1134,24 @@ export interface ScheduleClientInterceptors { calls?: ScheduleClientCallsInterceptorFactory[] } ``` + +- cron_string holds a traditional cron specification as a string. It +- accepts 5, 6, or 7 fields, separated by spaces, and interprets them the +- same way as CalendarSpec. +- 5 fields: minute, hour, day_of_month, month, day_of_week +- 6 fields: minute, hour, day_of_month, month, day_of_week, year +- 7 fields: second, minute, hour, day_of_month, month, day_of_week, year +- If year is not given, it defaults to \*. If second is not given, it +- defaults to 0. +- Shorthands @yearly, @monthly, @weekly, @daily, and @hourly are also +- accepted instead of the 5-7 time fields. +- Optionally, the string can be preceded by CRON_TZ= or +- TZ=, which will get copied to timezone_name. (There must +- not also be a timezone_name present.) +- Optionally "#" followed by a comment can appear at the end of the string. +- Note that the special case that some cron implementations have for +- treating day_of_month and day_of_week as "or" instead of "and" when both +- are set is not implemented. +- @every [/] is accepted and gets compiled into an +- IntervalSpec instead. and should be a decimal integer +- with a unit suffix s, m, h, or d. From 1bca6db96183dd706b2dfe6e0e41a97073c27db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Fri, 16 Sep 2022 15:05:29 -0400 Subject: [PATCH 23/27] update year default per dnr --- typescript/schedules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index 352e263..10e6122 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -635,7 +635,7 @@ export interface CalendarSpecDescription { /** * Use full years, like `2030` * - * @default `[{ start: 2000, step: 1 }]` + * @default `undefined` (meaning any year) */ year?: NumberSpecDescription; From 8438de91e724f95f22f0cdf4744cc7850f1753fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Fri, 16 Sep 2022 16:27:29 -0400 Subject: [PATCH 24/27] address review --- typescript/schedules.md | 40 +++++++++++++++------------------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index 10e6122..942b542 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -530,17 +530,17 @@ export interface Range { } export type NumberRange = Range | number -export type NumberSpec = NumberRange | NumberRange[] | string; +export type NumberSpec = NumberRange | NumberRange[] | '*'; export type NumberSpecDescription = Range[]; export type Month = 'JANUARY' | 'FEBRUARY' | 'MARCH' | 'APRIL' | 'MAY' | 'JUNE' | 'JULY' | 'AUGUST' | 'SEPTEMBER' | 'OCTOBER' | 'NOVEMBER' | 'DECEMBER'; export type MonthRange = Range | Month; -export type MonthSpec = MonthRange | MonthRange[] | string; +export type MonthSpec = MonthRange | MonthRange[] | '*'; export type MonthSpecDescription = Range[]; export type Day = 'SUNDAY' | 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY'; export type DayRange = Range | Day; -export type DaySpec = DayRange | DayRange[] | string; +export type DaySpec = DayRange | DayRange[] | '*'; export type DaySpecDescription = Range[]; /** @@ -593,6 +593,11 @@ export interface CalendarSpec { * @default '*' */ dayOfWeek?: DaySpec; + + /** + * Description of the intention of this spec. + */ + comment?: string; } /** @@ -643,6 +648,11 @@ export interface CalendarSpecDescription { * @default `[{ start: 'SUNDAY', end: 'SATURDAY' }]` */ dayOfWeek?: DaySpecDescription; + + /** + * Description of the intention of this spec. + */ + comment?: string; } /** @@ -708,7 +718,8 @@ export interface ScheduleSpec { intervals?: IntervalSpec[]; /** - * [Cron expressions](https://crontab.guru/) + * [Cron expressions](https://crontab.guru/). This is provided for easy migration from legacy Cron Workflows. For new + * use cases, we recommend using {@link calendars} or {@link intervals} for readability and maintainability. * * For example, `0 12 * * MON-WED,FRI` is every M/Tu/W/F at noon, and is equivalent to this {@link CalendarSpec}: * @@ -1134,24 +1145,3 @@ export interface ScheduleClientInterceptors { calls?: ScheduleClientCallsInterceptorFactory[] } ``` - -- cron_string holds a traditional cron specification as a string. It -- accepts 5, 6, or 7 fields, separated by spaces, and interprets them the -- same way as CalendarSpec. -- 5 fields: minute, hour, day_of_month, month, day_of_week -- 6 fields: minute, hour, day_of_month, month, day_of_week, year -- 7 fields: second, minute, hour, day_of_month, month, day_of_week, year -- If year is not given, it defaults to \*. If second is not given, it -- defaults to 0. -- Shorthands @yearly, @monthly, @weekly, @daily, and @hourly are also -- accepted instead of the 5-7 time fields. -- Optionally, the string can be preceded by CRON_TZ= or -- TZ=, which will get copied to timezone_name. (There must -- not also be a timezone_name present.) -- Optionally "#" followed by a comment can appear at the end of the string. -- Note that the special case that some cron implementations have for -- treating day_of_month and day_of_week as "or" instead of "and" when both -- are set is not implemented. -- @every [/] is accepted and gets compiled into an -- IntervalSpec instead. and should be a decimal integer -- with a unit suffix s, m, h, or d. From de7fa8794c03ee96208ca9331196992deac8075c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Mon, 19 Sep 2022 23:18:47 -0400 Subject: [PATCH 25/27] fix Range --- typescript/schedules.md | 46 +++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index 942b542..da26fd9 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -498,27 +498,25 @@ export interface Backfill { } /** - * The range of values depends on which, if any, fields are set: + * Example Ranges: * * ``` - * {} -> all values - * {start} -> start - * {start, end} -> every value from start to end (implies step = 1) - * {start, step} -> from start, by step (implies end is max for this field) - * {start, end, step} -> from start to end, by step - * {step} -> all values, by step (implies start and end are full range for this field) - * {end} and {end, step} are not allowed - * TODO is there a way to express not allowed ^ in type? - * ``` - * - * For example: - * - * ``` - * {start: 2, end: 10, step: 3} -> 2, 5, 8 + * { start: 2 } ➡️ 2 + * { start: 2, end: 4 } ➡️ 2, 3, 4 + * { start: 2, end: 10, step: 3 } ➡️ 2, 5, 8 * ``` */ export interface Range { - start?: Unit; + /** + * Start of range (inclusive) + */ + start: Unit; + + /** + * End of range (inclusive) + * + * @default `start` + */ end?: Unit; /** @@ -601,7 +599,8 @@ export interface CalendarSpec { } /** - * The version of {@link CalendarSpec} that you get back from {@link ScheduleHandle.describe} + * The version of {@link CalendarSpec} that you get back from {@link ScheduleHandle.describe} and + * {@link ScheduleClient.list} */ export interface CalendarSpecDescription { /** @@ -861,14 +860,6 @@ export type StartWorkflowAction = Omit< type: 'startWorkflow', workflowType: string | Action; - - /** - * Metadata that's propagated between workflows and activities? TODO someone explain headers to me so I can improve - * this apidoc and maybe: - * - remove from here and only expose to interceptor? - * - or make it Record and apply client's data converter? - */ - headers: Headers; }; export type ScheduleActionType = Workflow; @@ -1125,6 +1116,11 @@ interface ScheduleClientCallsInterceptor { ) => Promise } +interface ScheduleStartInput extends StartOptions { + ...StartOptions todo see other interceptors + headers: Headers +} + interface ScheduleClientCallsInterceptorFactoryInput { id: string } From b08cfbed4e9f43ad93166985b5f834979e56db42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Mon, 26 Sep 2022 22:36:29 -0400 Subject: [PATCH 26/27] fix interceptor input --- typescript/schedules.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index da26fd9..70febc1 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -1110,15 +1110,16 @@ interface ScheduleClientCallsInterceptor { /** * Intercept a service call to CreateSchedule */ - create?: ( - input: ScheduleStartInput, + create?: ( + input: CreateScheduleInput, next: Next ) => Promise } -interface ScheduleStartInput extends StartOptions { - ...StartOptions todo see other interceptors - headers: Headers +/** Input for {@link ScheduleClientCallsInterceptor.create} */ +interface CreateScheduleInput { + readonly headers: Headers + readonly options: ScheduleOptions; } interface ScheduleClientCallsInterceptorFactoryInput { From a49341383d50248bb79f767c6d0d265caff0dc78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=F0=9F=A4=93?= Date: Tue, 27 Sep 2022 16:38:23 -0400 Subject: [PATCH 27/27] Update default CalendarSpecDescription responses --- typescript/schedules.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/typescript/schedules.md b/typescript/schedules.md index 70febc1..7b158f4 100644 --- a/typescript/schedules.md +++ b/typescript/schedules.md @@ -606,47 +606,50 @@ export interface CalendarSpecDescription { /** * Valid values: 0–59 * - * @default `[{ start: 0 }]` + * If the default input it used, the default output will be `[{}]`. */ second?: NumberSpecDescription; /** * Valid values: 0–59 * - * @default `[{ start: 0 }]` + * If the default input it used, the default output will be `[{}]`. */ minute?: NumberSpecDescription; /** * Valid values: 0–59 * - * @default `[{ start: 0 }]` + * If the default input it used, the default output will be `[{}]`. */ hour?: NumberSpecDescription; /** * Valid values: 1–31 * - * @default `[{ start: 1, end: 31 }]` + * If the default input it used, the default output will be `[{ start: 1, end: 31, step: 1 }]`. */ dayOfMonth?: NumberSpecDescription; + // step will be 0/default over wire /** - * @default `[{ start: 'JANUARY' , end: 'DECEMBER' }]` + * If the default input it used, the default output will be `[{ start: 'JANUARY' , end: 'DECEMBER', step: 1 }]`. */ month?: MonthSpecDescription; + // will get { start: 1, end: 12, step 0 } from server /** * Use full years, like `2030` * - * @default `undefined` (meaning any year) + * If the default input it used, the default output will be `undefined` (meaning any year). */ year?: NumberSpecDescription; /** - * @default `[{ start: 'SUNDAY', end: 'SATURDAY' }]` + * If the default input it used, the default output will be `[{ start: 'SUNDAY', end: 'SATURDAY', step: 1 }]`. */ dayOfWeek?: DaySpecDescription; + // will get { start: 0, end: 6, step 0 } from server /** * Description of the intention of this spec.