(env: Env, atMs: number)
| 258 | * quietly skipping a day; every write here is idempotent, so a retry is safe. |
| 259 | */ |
| 260 | export async function runNightly(env: Env, atMs: number): Promise<void> { |
| 261 | const started = Date.now(); |
| 262 | const keepDays = retentionDays(env); |
| 263 | const cutoff = retentionCutoff(atMs, keepDays); |
| 264 | |
| 265 | const rolled: string[] = []; |
| 266 | const failed: string[] = []; |
| 267 | let rows = 0; |
| 268 | for (let back = 1; back <= ROLLUP_LOOKBACK_DAYS; back++) { |
| 269 | const day = utcDay(atMs - back * DAY_MS); |
| 270 | try { |
| 271 | rows += (await rollupDay(env, day, { cutoff })).rows; |
| 272 | rolled.push(day); |
| 273 | } catch (err) { |
| 274 | failed.push(day); |
| 275 | console.error(JSON.stringify({ msg: 'rollup day failed', day, err: String(err) })); |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | let purge: PurgeResult | null = null; |
| 280 | try { |
| 281 | purge = await purgeOldEvents(env, cutoff); |
| 282 | } catch (err) { |
| 283 | console.error(JSON.stringify({ msg: 'purge failed', cutoff, err: String(err) })); |
| 284 | } |
| 285 | |
| 286 | console.log( |
| 287 | JSON.stringify({ |
| 288 | msg: 'nightly rollup', |
| 289 | days: rolled, |
| 290 | rows, |
| 291 | failed: failed.length, |
| 292 | retention_days: keepDays, |
| 293 | purged_before: cutoff, |
| 294 | purged: purge?.deleted ?? null, |
| 295 | purge_batches: purge?.batches ?? null, |
| 296 | purge_capped: purge?.capped ?? null, |
| 297 | ms: Date.now() - started, |
| 298 | }), |
| 299 | ); |
| 300 | |
| 301 | if (failed.length > 0 || purge === null) { |
| 302 | throw new Error(`nightly rollup incomplete: ${failed.length} day(s) failed, purge ${purge ? 'ok' : 'failed'}`); |
| 303 | } |
| 304 | } |
| 305 | |
| 306 | // --------------------------------------------------------------------------- |
| 307 | // POST /admin/rollup — manual backfill / repair |
no test coverage detected