From 4e7e2a308aaed1fec976d1043f70aa1f9d3ebe79 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 4 Dec 2025 14:25:14 +0100 Subject: [PATCH 1/9] Implement weekly financial report schedule setup in worker initialization - Added a new function `setupWeeklyReportSchedule` in `schedules.ts` to create a weekly schedule for financial reports, running every Tuesday at 1 PM EST/EDT. - Integrated the schedule setup into the worker's `run` function in `index.ts`, ensuring it is established before the worker starts. - Enhanced error handling during schedule setup to log failures appropriately. These changes improve the automation of financial reporting, ensuring timely execution of workflows. --- workers/main/src/configs/schedules.ts | 66 +++++++++++++++++++++++++++ workers/main/src/configs/temporal.ts | 20 ++++++++ workers/main/src/index.ts | 18 ++++++++ 3 files changed, 104 insertions(+) create mode 100644 workers/main/src/configs/schedules.ts diff --git a/workers/main/src/configs/schedules.ts b/workers/main/src/configs/schedules.ts new file mode 100644 index 0000000..d1c3cb1 --- /dev/null +++ b/workers/main/src/configs/schedules.ts @@ -0,0 +1,66 @@ +import { Client } from '@temporalio/client'; + +import { logger } from '../index'; +import { weeklyFinancialReportsWorkflow } from '../workflows'; +import { workerConfig } from './worker'; + +const SCHEDULE_ID = 'weekly-financial-report-schedule'; + +/** + * Sets up the weekly financial report schedule + * Schedule runs every Tuesday at 1 PM America/New_York time (EST/EDT) + * @param client - Temporal client instance + */ +export async function setupWeeklyReportSchedule(client: Client): Promise { + try { + const scheduleHandle = client.schedule.getHandle(SCHEDULE_ID); + + // Check if schedule already exists + try { + await scheduleHandle.describe(); + logger.info(`Schedule ${SCHEDULE_ID} already exists, skipping creation`); + + return; + } catch (error) { + // Schedule doesn't exist, create it + logger.info(`Creating schedule ${SCHEDULE_ID}`); + } + + await client.schedule.create({ + scheduleId: SCHEDULE_ID, + spec: { + cronExpressions: ['0 13 * * 2'], // Every Tuesday at 1 PM + timezone: 'America/New_York', // Automatically handles EST/EDT transitions + }, + action: { + type: 'startWorkflow', + workflowType: weeklyFinancialReportsWorkflow, + taskQueue: workerConfig.taskQueue, + workflowId: `weekly-financial-report-scheduled`, + }, + policies: { + overlap: 'SKIP', // Skip if previous run is still in progress + catchupWindow: '1 day', // Catch up missed runs within 1 day + }, + }); + + logger.info( + `Successfully created schedule ${SCHEDULE_ID} for weekly financial reports`, + ); + } catch (error) { + logger.error( + `Failed to setup schedule ${SCHEDULE_ID}: ${error instanceof Error ? error.message : String(error)}`, + ); + throw error; + } +} + +/** + * Schedule configuration exported for documentation and testing + */ +export const scheduleConfig = { + scheduleId: SCHEDULE_ID, + cronExpression: '0 13 * * 2', + timezone: 'America/New_York', + description: 'Runs every Tuesday at 1 PM EST/EDT', +} as const; diff --git a/workers/main/src/configs/temporal.ts b/workers/main/src/configs/temporal.ts index 5058a0d..0db94c7 100644 --- a/workers/main/src/configs/temporal.ts +++ b/workers/main/src/configs/temporal.ts @@ -3,6 +3,10 @@ import { z } from 'zod'; const DEFAULT_TEMPORAL_ADDRESS = 'temporal:7233'; +/** + * Temporal connection configuration + * Used by both workers and clients to connect to Temporal server + */ export const temporalConfig: NativeConnectionOptions = { address: process.env.TEMPORAL_ADDRESS || DEFAULT_TEMPORAL_ADDRESS, }; @@ -10,3 +14,19 @@ export const temporalConfig: NativeConnectionOptions = { export const temporalSchema = z.object({ TEMPORAL_ADDRESS: z.string().default(DEFAULT_TEMPORAL_ADDRESS), }); + +/** + * Schedule Configuration Documentation + * + * Weekly Financial Report Schedule: + * - Schedule ID: 'weekly-financial-report-schedule' + * - Cron Expression: '0 13 * * 2' (Every Tuesday at 1 PM) + * - Timezone: 'America/New_York' (automatically handles EST/EDT transitions) + * - Workflow: weeklyFinancialReportsWorkflow + * - Task Queue: 'main-queue' + * - Overlap Policy: SKIP (prevents concurrent runs) + * - Catchup Window: 1 day (runs missed schedules within 24 hours) + * + * The schedule is automatically created/verified when the worker starts. + * See src/configs/schedules.ts for implementation details. + */ diff --git a/workers/main/src/index.ts b/workers/main/src/index.ts index 5aa8523..10b1a1c 100644 --- a/workers/main/src/index.ts +++ b/workers/main/src/index.ts @@ -1,7 +1,9 @@ +import { Client, Connection } from '@temporalio/client'; import { DefaultLogger, NativeConnection, Worker } from '@temporalio/worker'; import * as activities from './activities'; import { validateEnv } from './common/utils'; +import { setupWeeklyReportSchedule } from './configs/schedules'; import { temporalConfig } from './configs/temporal'; import { workerConfig } from './configs/worker'; @@ -25,6 +27,22 @@ export async function createWorker(connection: NativeConnection) { } export async function run(): Promise { + // Setup weekly report schedule before starting worker + const clientConnection = await Connection.connect(temporalConfig); + + try { + const client = new Client({ connection: clientConnection }); + + await setupWeeklyReportSchedule(client); + } catch (err) { + logger.error( + `Failed to setup schedule: ${err instanceof Error ? err.message : String(err)}`, + ); + } finally { + await clientConnection.close(); + } + + // Create and run worker const connection = await createConnection(); try { From 9eb335dbeccdf481ba1b7087058907390543972d Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 4 Dec 2025 14:29:36 +0100 Subject: [PATCH 2/9] Enhance error logging during weekly report schedule creation - Improved error handling in the `setupWeeklyReportSchedule` function by capturing and logging the error message when a schedule creation fails. - Updated log message to include the specific error encountered, aiding in debugging and monitoring. These changes enhance the visibility of issues during schedule setup, facilitating quicker resolution of potential problems. --- workers/main/src/configs/schedules.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/workers/main/src/configs/schedules.ts b/workers/main/src/configs/schedules.ts index d1c3cb1..be44790 100644 --- a/workers/main/src/configs/schedules.ts +++ b/workers/main/src/configs/schedules.ts @@ -22,8 +22,13 @@ export async function setupWeeklyReportSchedule(client: Client): Promise { return; } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + // Schedule doesn't exist, create it - logger.info(`Creating schedule ${SCHEDULE_ID}`); + logger.info( + `Creating schedule ${SCHEDULE_ID} with error: ${errorMessage}`, + ); } await client.schedule.create({ From 95a45d0a82624ea2e14362b56aeabcb09fbd5f47 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 4 Dec 2025 14:32:30 +0100 Subject: [PATCH 3/9] Update temporal configuration documentation for clarity - Removed unnecessary blank lines in the schedule configuration comments to enhance readability. - Ensured that the documentation clearly outlines the weekly financial report schedule details. These changes improve the clarity and presentation of the schedule configuration documentation. --- workers/main/src/configs/temporal.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workers/main/src/configs/temporal.ts b/workers/main/src/configs/temporal.ts index 0db94c7..4531cc6 100644 --- a/workers/main/src/configs/temporal.ts +++ b/workers/main/src/configs/temporal.ts @@ -17,7 +17,7 @@ export const temporalSchema = z.object({ /** * Schedule Configuration Documentation - * + * * Weekly Financial Report Schedule: * - Schedule ID: 'weekly-financial-report-schedule' * - Cron Expression: '0 13 * * 2' (Every Tuesday at 1 PM) @@ -26,7 +26,7 @@ export const temporalSchema = z.object({ * - Task Queue: 'main-queue' * - Overlap Policy: SKIP (prevents concurrent runs) * - Catchup Window: 1 day (runs missed schedules within 24 hours) - * + * * The schedule is automatically created/verified when the worker starts. * See src/configs/schedules.ts for implementation details. */ From 63945a5c7da41c9e30fb5db80e697d8c51c12025 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 4 Dec 2025 14:42:17 +0100 Subject: [PATCH 4/9] Refine logging and documentation for schedule setup - Updated the log message in `setupWeeklyReportSchedule` to clarify the reason for creating a new schedule when it is not found. - Enhanced the documentation in `temporal.ts` to direct users to the `scheduleConfig` object for detailed schedule configuration information. These changes improve the clarity of error messages and documentation, aiding in better understanding and maintenance of the scheduling system. --- workers/main/src/configs/schedules.ts | 2 +- workers/main/src/configs/temporal.ts | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/workers/main/src/configs/schedules.ts b/workers/main/src/configs/schedules.ts index be44790..c64a80b 100644 --- a/workers/main/src/configs/schedules.ts +++ b/workers/main/src/configs/schedules.ts @@ -27,7 +27,7 @@ export async function setupWeeklyReportSchedule(client: Client): Promise { // Schedule doesn't exist, create it logger.info( - `Creating schedule ${SCHEDULE_ID} with error: ${errorMessage}`, + `Schedule ${SCHEDULE_ID} not found, creating schedule. Reason: ${errorMessage}`, ); } diff --git a/workers/main/src/configs/temporal.ts b/workers/main/src/configs/temporal.ts index 4531cc6..6f4967d 100644 --- a/workers/main/src/configs/temporal.ts +++ b/workers/main/src/configs/temporal.ts @@ -19,14 +19,10 @@ export const temporalSchema = z.object({ * Schedule Configuration Documentation * * Weekly Financial Report Schedule: - * - Schedule ID: 'weekly-financial-report-schedule' - * - Cron Expression: '0 13 * * 2' (Every Tuesday at 1 PM) - * - Timezone: 'America/New_York' (automatically handles EST/EDT transitions) - * - Workflow: weeklyFinancialReportsWorkflow - * - Task Queue: 'main-queue' - * - Overlap Policy: SKIP (prevents concurrent runs) - * - Catchup Window: 1 day (runs missed schedules within 24 hours) - * * The schedule is automatically created/verified when the worker starts. - * See src/configs/schedules.ts for implementation details. + * + * For schedule configuration details (schedule ID, cron expression, timezone, etc.), + * see the exported `scheduleConfig` object in ./schedules.ts + * + * Implementation: ./schedules.ts */ From 4a8ecda148692964ff32a0db38795e9dfd605449 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 4 Dec 2025 15:01:34 +0100 Subject: [PATCH 5/9] Refactor logging implementation and update imports - Moved the logger instance to a new `logger.ts` file for better organization and reusability. - Updated import paths in `index.ts`, `index.test.ts`, and `schedules.ts` to reference the new logger module. - Ensured that the logger is consistently used across the application, improving maintainability and clarity. These changes streamline the logging setup and enhance code structure. --- workers/main/src/configs/schedules.ts | 2 +- workers/main/src/index.test.ts | 3 ++- workers/main/src/index.ts | 5 +++-- workers/main/src/logger.ts | 7 +++++++ 4 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 workers/main/src/logger.ts diff --git a/workers/main/src/configs/schedules.ts b/workers/main/src/configs/schedules.ts index c64a80b..704d403 100644 --- a/workers/main/src/configs/schedules.ts +++ b/workers/main/src/configs/schedules.ts @@ -1,6 +1,6 @@ import { Client } from '@temporalio/client'; -import { logger } from '../index'; +import { logger } from '../logger'; import { weeklyFinancialReportsWorkflow } from '../workflows'; import { workerConfig } from './worker'; diff --git a/workers/main/src/index.test.ts b/workers/main/src/index.test.ts index 4f5af83..c22d686 100644 --- a/workers/main/src/index.test.ts +++ b/workers/main/src/index.test.ts @@ -1,6 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { handleRunError, logger } from './index'; +import { handleRunError } from './index'; +import { logger } from './logger'; describe('handleRunError', () => { let processExitSpy: ReturnType; diff --git a/workers/main/src/index.ts b/workers/main/src/index.ts index 10b1a1c..27a3b17 100644 --- a/workers/main/src/index.ts +++ b/workers/main/src/index.ts @@ -1,13 +1,14 @@ import { Client, Connection } from '@temporalio/client'; -import { DefaultLogger, NativeConnection, Worker } from '@temporalio/worker'; +import { NativeConnection, Worker } from '@temporalio/worker'; import * as activities from './activities'; import { validateEnv } from './common/utils'; import { setupWeeklyReportSchedule } from './configs/schedules'; import { temporalConfig } from './configs/temporal'; import { workerConfig } from './configs/worker'; +import { logger } from './logger'; -export const logger = new DefaultLogger('ERROR'); +export { logger }; validateEnv(); diff --git a/workers/main/src/logger.ts b/workers/main/src/logger.ts new file mode 100644 index 0000000..a266c18 --- /dev/null +++ b/workers/main/src/logger.ts @@ -0,0 +1,7 @@ +import { DefaultLogger } from '@temporalio/worker'; + +/** + * Shared logger instance for the worker + * Using ERROR level to reduce noise in production + */ +export const logger = new DefaultLogger('ERROR'); From 3eb305d74d2b6a5ee4bc48413236cdeca9f290f0 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 4 Dec 2025 15:20:53 +0100 Subject: [PATCH 6/9] Update workflow type in weekly report schedule configuration - Changed the workflowType in the setupWeeklyReportSchedule function from a direct import to a string reference for 'weeklyFinancialReportsWorkflow'. - This adjustment improves clarity and consistency in the schedule setup process. These changes enhance the maintainability of the scheduling configuration. --- workers/main/src/configs/schedules.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/workers/main/src/configs/schedules.ts b/workers/main/src/configs/schedules.ts index 704d403..41a03e4 100644 --- a/workers/main/src/configs/schedules.ts +++ b/workers/main/src/configs/schedules.ts @@ -1,7 +1,6 @@ import { Client } from '@temporalio/client'; import { logger } from '../logger'; -import { weeklyFinancialReportsWorkflow } from '../workflows'; import { workerConfig } from './worker'; const SCHEDULE_ID = 'weekly-financial-report-schedule'; @@ -39,7 +38,7 @@ export async function setupWeeklyReportSchedule(client: Client): Promise { }, action: { type: 'startWorkflow', - workflowType: weeklyFinancialReportsWorkflow, + workflowType: 'weeklyFinancialReportsWorkflow', taskQueue: workerConfig.taskQueue, workflowId: `weekly-financial-report-scheduled`, }, From 13ff085ae83b85caabb7d3599ad95a586572afe7 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 4 Dec 2025 15:52:10 +0100 Subject: [PATCH 7/9] Update logger level and enhance schedule validation logic - Changed logger level from ERROR to INFO to capture important operational messages, including schedule setup, errors, and warnings. - Introduced validation functions to check if a schedule exists and handle "not found" errors more gracefully, improving error handling in the setupWeeklyReportSchedule function. These changes enhance logging clarity and improve the robustness of schedule management. --- workers/main/src/configs/schedules.ts | 56 ++++++++++++++++++--------- workers/main/src/logger.ts | 5 ++- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/workers/main/src/configs/schedules.ts b/workers/main/src/configs/schedules.ts index 41a03e4..39a4025 100644 --- a/workers/main/src/configs/schedules.ts +++ b/workers/main/src/configs/schedules.ts @@ -5,6 +5,38 @@ import { workerConfig } from './worker'; const SCHEDULE_ID = 'weekly-financial-report-schedule'; +/** + * Checks if an error is a "not found" error + */ +function validateIsScheduleNotFoundError(error: unknown): boolean { + return ( + (error as { code?: number }).code === 5 || + (error instanceof Error && + error.message.toLowerCase().includes('not found')) + ); +} + +/** + * Checks if schedule exists, returns true if it exists + */ +async function validateScheduleExists(client: Client): Promise { + try { + const scheduleHandle = client.schedule.getHandle(SCHEDULE_ID); + + await scheduleHandle.describe(); + logger.info(`Schedule ${SCHEDULE_ID} already exists, skipping creation`); + + return true; + } catch (error) { + if (!validateIsScheduleNotFoundError(error)) { + throw error; + } + logger.info(`Schedule ${SCHEDULE_ID} not found, creating new schedule`); + + return false; + } +} + /** * Sets up the weekly financial report schedule * Schedule runs every Tuesday at 1 PM America/New_York time (EST/EDT) @@ -12,29 +44,17 @@ const SCHEDULE_ID = 'weekly-financial-report-schedule'; */ export async function setupWeeklyReportSchedule(client: Client): Promise { try { - const scheduleHandle = client.schedule.getHandle(SCHEDULE_ID); - - // Check if schedule already exists - try { - await scheduleHandle.describe(); - logger.info(`Schedule ${SCHEDULE_ID} already exists, skipping creation`); + const isScheduleExists = await validateScheduleExists(client); + if (isScheduleExists) { return; - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : String(error); - - // Schedule doesn't exist, create it - logger.info( - `Schedule ${SCHEDULE_ID} not found, creating schedule. Reason: ${errorMessage}`, - ); } await client.schedule.create({ scheduleId: SCHEDULE_ID, spec: { - cronExpressions: ['0 13 * * 2'], // Every Tuesday at 1 PM - timezone: 'America/New_York', // Automatically handles EST/EDT transitions + cronExpressions: ['0 13 * * 2'], + timezone: 'America/New_York', }, action: { type: 'startWorkflow', @@ -43,8 +63,8 @@ export async function setupWeeklyReportSchedule(client: Client): Promise { workflowId: `weekly-financial-report-scheduled`, }, policies: { - overlap: 'SKIP', // Skip if previous run is still in progress - catchupWindow: '1 day', // Catch up missed runs within 1 day + overlap: 'SKIP', + catchupWindow: '1 day', }, }); diff --git a/workers/main/src/logger.ts b/workers/main/src/logger.ts index a266c18..fd3afbd 100644 --- a/workers/main/src/logger.ts +++ b/workers/main/src/logger.ts @@ -2,6 +2,7 @@ import { DefaultLogger } from '@temporalio/worker'; /** * Shared logger instance for the worker - * Using ERROR level to reduce noise in production + * Using INFO level to capture important operational messages + * including schedule setup, errors, and warnings */ -export const logger = new DefaultLogger('ERROR'); +export const logger = new DefaultLogger('INFO'); From 7b07b524cff51ce5427ed9f4bec45de09380288c Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 4 Dec 2025 15:58:58 +0100 Subject: [PATCH 8/9] Remove export of logger from index.ts to streamline module exports --- workers/main/src/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/workers/main/src/index.ts b/workers/main/src/index.ts index 27a3b17..1124d8f 100644 --- a/workers/main/src/index.ts +++ b/workers/main/src/index.ts @@ -8,8 +8,6 @@ import { temporalConfig } from './configs/temporal'; import { workerConfig } from './configs/worker'; import { logger } from './logger'; -export { logger }; - validateEnv(); export async function createConnection() { From 65944695d1c7ae64f27545b50457a8960840c533 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 4 Dec 2025 16:03:06 +0100 Subject: [PATCH 9/9] Refactor schedule creation to include race condition protection - Renamed `setupWeeklyReportSchedule` to `createScheduleWithRaceProtection` to better reflect its functionality. - Added logic to handle race conditions when creating schedules, allowing the function to gracefully handle cases where a schedule already exists. - Updated the `setupWeeklyReportSchedule` function to utilize the new schedule creation method, maintaining its original purpose while enhancing robustness. These changes improve the reliability of the scheduling system by preventing conflicts during schedule creation. --- workers/main/src/configs/schedules.ts | 46 +++++++++++++++++++++------ 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/workers/main/src/configs/schedules.ts b/workers/main/src/configs/schedules.ts index 39a4025..c47b4d7 100644 --- a/workers/main/src/configs/schedules.ts +++ b/workers/main/src/configs/schedules.ts @@ -38,18 +38,10 @@ async function validateScheduleExists(client: Client): Promise { } /** - * Sets up the weekly financial report schedule - * Schedule runs every Tuesday at 1 PM America/New_York time (EST/EDT) - * @param client - Temporal client instance + * Creates schedule with race condition protection */ -export async function setupWeeklyReportSchedule(client: Client): Promise { +async function createScheduleWithRaceProtection(client: Client): Promise { try { - const isScheduleExists = await validateScheduleExists(client); - - if (isScheduleExists) { - return; - } - await client.schedule.create({ scheduleId: SCHEDULE_ID, spec: { @@ -71,6 +63,40 @@ export async function setupWeeklyReportSchedule(client: Client): Promise { logger.info( `Successfully created schedule ${SCHEDULE_ID} for weekly financial reports`, ); + } catch (createError) { + // Handle race condition: schedule was created by another worker + const isAlreadyExists = + (createError as { code?: number }).code === 6 || + (createError instanceof Error && + (createError.message.toLowerCase().includes('already exists') || + createError.message.toLowerCase().includes('already running'))); + + if (isAlreadyExists) { + logger.info( + `Schedule ${SCHEDULE_ID} already exists (created by another worker), treating as success`, + ); + + return; + } + + throw createError; + } +} + +/** + * Sets up the weekly financial report schedule + * Schedule runs every Tuesday at 1 PM America/New_York time (EST/EDT) + * @param client - Temporal client instance + */ +export async function setupWeeklyReportSchedule(client: Client): Promise { + try { + const isScheduleExists = await validateScheduleExists(client); + + if (isScheduleExists) { + return; + } + + await createScheduleWithRaceProtection(client); } catch (error) { logger.error( `Failed to setup schedule ${SCHEDULE_ID}: ${error instanceof Error ? error.message : String(error)}`,