UNPKG

temporal-time-utils

Version:

This is a library with some reusable functions for [Temporal.io TypeScript SDK](https://docs.temporal.io/docs/typescript/introduction):

121 lines (113 loc) 4 kB
import * as wf from "@temporalio/workflow"; import { WithWorkflowArgs, Workflow, // WorkflowResultType, } from "@temporalio/common"; const parser = require("cron-parser"); // es6 import doesnt work bc their types are bad import { ParserOptions } from "cron-parser"; import differenceInMilliseconds from "date-fns/differenceInMilliseconds"; import { sleepUntil } from "./sleepUntil"; // queries export const numInvocationsQuery = wf.defineQuery("numInvocationsQuery"); export const futureScheduleQuery = wf.defineQuery("futureScheduleQuery"); export const manualTriggerSignal = wf.defineSignal("manualTriggerSignal"); export type ScheduleWorkflowState = "RUNNING" | "PAUSED" | "STOPPED"; export const stateSignal = wf.defineSignal<[ScheduleWorkflowState]>("stateSignal"); export const stateQuery = wf.defineQuery<ScheduleWorkflowState>("stateQuery"); export type ScheduleOptions = { cronParser: { expression: string; options?: ParserOptions; }; maxInvocations?: number; jitterMs?: number; }; export async function ScheduleWorkflow<T extends Workflow>( /** Name of the child workflow to start */ workflowToSchedule: string, /** Options to start the child workflow with, including ParentClosePolicy */ workflowOptions: WithWorkflowArgs<T, wf.ChildWorkflowOptions>, /** Options that control how the scheduling is done */ scheduleOptions: ScheduleOptions, invocations: number = 1 ) { // signal and query handlers wf.setHandler(numInvocationsQuery, () => invocations); wf.setHandler(manualTriggerSignal, () => // note that we increment invocations after call wf.executeChild(workflowToSchedule, { args: workflowOptions.args, workflowId: `scheduled-${invocations++}-${nextTime.toString()}`, ...workflowOptions, }) ); let scheduleWorkflowState = "RUNNING" as ScheduleWorkflowState; wf.setHandler(stateQuery, () => scheduleWorkflowState); wf.setHandler(stateSignal, (state) => void (scheduleWorkflowState = state)); const interval = parser.parseExpression( scheduleOptions.cronParser.expression, scheduleOptions.cronParser.options ); const nextTime = interval.next().toString(); wf.setHandler(futureScheduleQuery, (numEntriesInFutureSchedule?: number) => { const interval = parser.parseExpression( scheduleOptions.cronParser.expression, scheduleOptions.cronParser.options ); // reset interval return { futureSchedule: genNextTimes(numEntriesInFutureSchedule, () => interval.next().toString() ), timeLeft: differenceInMilliseconds(new Date(nextTime), new Date()), }; }); // timer logic try { await sleepUntil(nextTime); if (scheduleOptions.jitterMs) { await wf.sleep( Math.floor(Math.random() * (scheduleOptions.jitterMs + 1)) ); } if (scheduleWorkflowState === "PAUSED") { await wf.condition(() => scheduleWorkflowState === "RUNNING"); } wf.executeChild(workflowToSchedule, { args: workflowOptions.args, workflowId: `scheduled-${invocations}-${nextTime.toString()}`, ...workflowOptions, // // regular workflow options apply here, with two additions (defaults shown): // cancellationType: ChildWorkflowCancellationType.WAIT_CANCELLATION_COMPLETED, // parentClosePolicy: ParentClosePolicy.PARENT_CLOSE_POLICY_TERMINATE }); if ( scheduleOptions.maxInvocations && scheduleOptions.maxInvocations > invocations ) { await wf.continueAsNew( workflowToSchedule, workflowOptions, scheduleOptions, invocations + 1 ); } else { scheduleWorkflowState = "STOPPED"; } } catch (err) { if (wf.isCancellation(err)) scheduleWorkflowState = "STOPPED"; else throw err; } } // shared function genNextTimes<T extends string | Date>( number = 5, getNextTimes: () => T ): T[] { const times = []; for (let i = 0; i < number; i++) { times.push(getNextTimes()); } return times; }