diff --git a/apps/api/src/broker/outboxWorker.ts b/apps/api/src/broker/outboxWorker.ts index 9d20189..1d73a25 100644 --- a/apps/api/src/broker/outboxWorker.ts +++ b/apps/api/src/broker/outboxWorker.ts @@ -17,13 +17,12 @@ * T-03-14: create-before-delete ordering; create-fail aborts delete. * * runOutboxDrain is exported for unit testing. - * startOutboxWorker wraps it in a 15-second node-cron schedule. + * startOutboxWorker wraps it in a 15-second setInterval. + * (node-cron 4.2.1 silently skipped scheduled executions in the long-running server process; + * setInterval fires reliably in the same process — replaced to fix the silent skip.) * * Source: poller.ts pattern (runPoll/startBrokerPoller) - * Source: https://github.com/node-cron/node-cron (v4 stable) */ - -import { schedule } from 'node-cron' import { z } from 'zod' import { and, eq, lte } from 'drizzle-orm' import { db } from '../db/client.js' @@ -752,11 +751,13 @@ export async function runOutboxDrain(): Promise { /** * Starts the 15-second background outbox drain schedule. * Call once at API startup (wired in index.ts beside startBrokerPoller). + * Uses setInterval instead of node-cron: node-cron 4.2.1 silently skipped executions + * in the long-running server process; setInterval fires reliably. */ export function startOutboxWorker(): void { - schedule('*/15 * * * * *', () => { + setInterval(() => { runOutboxDrain().catch((err: unknown) => { console.error('[outboxWorker] Unhandled runOutboxDrain error:', err) }) - }) + }, 15 * 1000) } diff --git a/apps/api/src/broker/poller.ts b/apps/api/src/broker/poller.ts index 1c85a3f..64bc6c2 100644 --- a/apps/api/src/broker/poller.ts +++ b/apps/api/src/broker/poller.ts @@ -1,5 +1,5 @@ /** - * CalDAV broker poller — runs every 5 minutes via node-cron. + * CalDAV broker poller — runs every 5 minutes via setInterval. * * Responsibilities (D-13, D-02): * - Load all member_credentials (N-credential per-member model) @@ -10,12 +10,10 @@ * → ctag changed or null: call syncCalendar (REPORT → ical.js → DB upsert) * * runPoll is exported for unit testing (inject mocks via vi.mock at the module level). - * startBrokerPoller wraps it in node-cron's 5-minute schedule. - * - * Source: https://github.com/node-cron/node-cron (v4 stable basic API) + * startBrokerPoller wraps it in a 5-minute setInterval. + * (node-cron 4.2.1 silently skipped scheduled executions in the long-running server process; + * setInterval fires reliably in the same process — replaced to fix the silent skip.) */ - -import { schedule } from 'node-cron' import { and, eq } from 'drizzle-orm' import { db } from '../db/client.js' import { memberCredentials, calendars } from '../db/schema.js' @@ -91,11 +89,13 @@ export async function runPoll(): Promise { /** * Starts the 5-minute background polling schedule. * Call once at API startup (Plan 04 wires this into index.ts). + * Uses setInterval instead of node-cron: node-cron 4.2.1 silently skipped executions + * in the long-running server process; setInterval fires reliably. */ export function startBrokerPoller(): void { - schedule('*/5 * * * *', () => { + setInterval(() => { runPoll().catch((err: unknown) => { console.error('[broker/poller] Unhandled runPoll error:', err) }) - }) + }, 5 * 60 * 1000) } diff --git a/apps/api/src/broker/reminderScheduler.ts b/apps/api/src/broker/reminderScheduler.ts index 5697ebf..91ab3ea 100644 --- a/apps/api/src/broker/reminderScheduler.ts +++ b/apps/api/src/broker/reminderScheduler.ts @@ -1,7 +1,9 @@ /** * Reminder scheduler — shared-timed-event 15-min reminder scan. * - * Fires every minute via node-cron. Each tick calls runReminderCheck() which: + * Fires every minute via setInterval. Each tick calls runReminderCheck() which: + * (node-cron 4.2.1 silently skipped scheduled executions in the long-running server + * process; setInterval fires reliably in the same process — replaced to fix the silent skip.) * 1. Queries shared (isShared=true) timed (allDay=false) events whose dtstartUtc * falls in (now, now+16min] — strictly after now (future only; excludes * already-started events) and at most 16 min out. Because the lower bound @@ -31,7 +33,6 @@ * T-05-19 — per-subscription try/catch; dispatchPush already swallows 410/404. */ -import { schedule } from 'node-cron' import { and, eq, gt, lte, sql } from 'drizzle-orm' import { db } from '../db/client.js' import { calendars, calendarEvents, pushSubscriptions } from '../db/schema.js' @@ -201,12 +202,14 @@ export async function runReminderCheck(now = new Date()): Promise { /** * Start the 1-minute reminder scan schedule. * Call once from index.ts's isMainModule() guard — NOT at import time - * (keeps the cron out of the test process; mirrors startBrokerPoller pattern). + * (keeps the setInterval out of the test process; mirrors startBrokerPoller pattern). + * Uses setInterval instead of node-cron: node-cron 4.2.1 silently skipped executions + * in the long-running server process; setInterval fires reliably. */ export function startReminderScheduler(): void { - schedule('* * * * *', () => { + setInterval(() => { runReminderCheck().catch((err: unknown) => { console.error('[broker/reminderScheduler] Unhandled runReminderCheck error:', err) }) - }) + }, 60 * 1000) }