From 6a3f92baef15bb766e0611f1c7119add75cee9cf Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Mon, 20 Apr 2026 11:40:03 -0400 Subject: [PATCH] cron: simplify split store hydration --- src/cron/store.ts | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/cron/store.ts b/src/cron/store.ts index b0c9aa1c168..3921dce8c78 100644 --- a/src/cron/store.ts +++ b/src/cron/store.ts @@ -97,6 +97,19 @@ function hasInlineState(jobs: Array | null | undefined>) ); } +function ensureJobStateObject(job: CronStoreFile["jobs"][number]): void { + if (!job.state || typeof job.state !== "object") { + job.state = {} as never; + } +} + +function backfillMissingRuntimeFields(job: CronStoreFile["jobs"][number]): void { + ensureJobStateObject(job); + if (typeof job.updatedAtMs !== "number") { + job.updatedAtMs = typeof job.createdAtMs === "number" ? job.createdAtMs : Date.now(); + } +} + export async function loadCronStore(storePath: string): Promise { try { const raw = await fs.promises.readFile(storePath, "utf-8"); @@ -130,32 +143,20 @@ export async function loadCronStore(storePath: string): Promise { job.updatedAtMs = entry.updatedAtMs ?? job.updatedAtMs; job.state = (entry.state ?? {}) as never; } else { - // Job exists in config but not in state file: default to empty state. - if (!job.state || typeof job.state !== "object") { - job.state = {} as never; - } - // Backfill updatedAtMs for manually-added jobs that have no state entry yet. - if (typeof job.updatedAtMs !== "number") { - job.updatedAtMs = typeof job.createdAtMs === "number" ? job.createdAtMs : Date.now(); - } + backfillMissingRuntimeFields(job); } } } else if (!hasInlineState(jobs as unknown as Array>)) { // No state file, no inline state: fresh clone or first run. for (const job of store.jobs) { - job.state = (job.state && typeof job.state === "object" ? job.state : {}) as never; - if (typeof job.updatedAtMs !== "number") { - job.updatedAtMs = typeof job.createdAtMs === "number" ? job.createdAtMs : Date.now(); - } + backfillMissingRuntimeFields(job); } } // else: migration mode — no state file but jobs.json has inline state. Use as-is. // Ensure every job has a state object (defensive). for (const job of store.jobs) { - if (!job.state || typeof job.state !== "object") { - job.state = {} as never; - } + ensureJobStateObject(job); } const configJson = JSON.stringify(stripRuntimeOnlyCronFields(store), null, 2); @@ -203,10 +204,9 @@ export async function saveCronStore( const stateJson = JSON.stringify(stateFile, null, 2); const statePath = resolveStatePath(storePath); - const configCacheKey = storePath; const stateCacheKey = `${storePath}:state`; - const cachedConfig = serializedStoreCache.get(configCacheKey); + const cachedConfig = serializedStoreCache.get(storePath); const cachedState = serializedStoreCache.get(stateCacheKey); const configChanged = cachedConfig !== configJson; @@ -245,7 +245,7 @@ export async function saveCronStore( } } await atomicWrite(storePath, configJson); - serializedStoreCache.set(configCacheKey, configJson); + serializedStoreCache.set(storePath, configJson); } }