@@ -14,7 +14,7 @@ import { run as graphileRun, parseCronItems } from "graphile-worker";
14
14
import omit from "lodash.omit" ;
15
15
import { z } from "zod" ;
16
16
import { PrismaClient , PrismaClientOrTransaction } from "~/db.server" ;
17
- import { logger } from "~/services/logger.server" ;
17
+ import { workerLogger as logger } from "~/services/logger.server" ;
18
18
19
19
export interface MessageCatalogSchema {
20
20
[ key : string ] : z . ZodFirstPartySchemaTypes | z . ZodDiscriminatedUnion < any , any > ;
@@ -81,13 +81,22 @@ export type ZodWorkerDequeueOptions = {
81
81
tx ?: PrismaClientOrTransaction ;
82
82
} ;
83
83
84
+ const CLEANUP_TASK_NAME = "__cleanupOldJobs" ;
85
+
86
+ export type ZodWorkerCleanupOptions = {
87
+ frequencyExpression : string ; // cron expression
88
+ ttl : number ;
89
+ taskOptions ?: CronItemOptions ;
90
+ } ;
91
+
84
92
export type ZodWorkerOptions < TMessageCatalog extends MessageCatalogSchema > = {
85
93
name : string ;
86
94
runnerOptions : RunnerOptions ;
87
95
prisma : PrismaClient ;
88
96
schema : TMessageCatalog ;
89
97
tasks : ZodTasks < TMessageCatalog > ;
90
98
recurringTasks ?: ZodRecurringTasks ;
99
+ cleanup ?: ZodWorkerCleanupOptions ;
91
100
} ;
92
101
93
102
export class ZodWorker < TMessageCatalog extends MessageCatalogSchema > {
@@ -98,6 +107,7 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
98
107
#tasks: ZodTasks < TMessageCatalog > ;
99
108
#recurringTasks?: ZodRecurringTasks ;
100
109
#runner?: GraphileRunner ;
110
+ #cleanup: ZodWorkerCleanupOptions | undefined ;
101
111
102
112
constructor ( options : ZodWorkerOptions < TMessageCatalog > ) {
103
113
this . #name = options . name ;
@@ -106,6 +116,7 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
106
116
this . #runnerOptions = options . runnerOptions ;
107
117
this . #tasks = options . tasks ;
108
118
this . #recurringTasks = options . recurringTasks ;
119
+ this . #cleanup = options . cleanup ;
109
120
}
110
121
111
122
get graphileWorkerSchema ( ) {
@@ -337,12 +348,29 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
337
348
taskList [ key ] = task ;
338
349
}
339
350
351
+ if ( this . #cleanup) {
352
+ const task : Task = ( payload , helpers ) => {
353
+ return this . #handleCleanup( payload , helpers ) ;
354
+ } ;
355
+
356
+ taskList [ CLEANUP_TASK_NAME ] = task ;
357
+ }
358
+
340
359
return taskList ;
341
360
}
342
361
343
362
#createCronItemsFromRecurringTasks( ) {
344
363
const cronItems : CronItem [ ] = [ ] ;
345
364
365
+ if ( this . #cleanup) {
366
+ cronItems . push ( {
367
+ pattern : this . #cleanup. frequencyExpression ,
368
+ identifier : CLEANUP_TASK_NAME ,
369
+ task : CLEANUP_TASK_NAME ,
370
+ options : this . #cleanup. taskOptions ,
371
+ } ) ;
372
+ }
373
+
346
374
if ( ! this . #recurringTasks) {
347
375
return cronItems ;
348
376
}
@@ -434,6 +462,50 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
434
462
}
435
463
}
436
464
465
+ async #handleCleanup( rawPayload : unknown , helpers : JobHelpers ) : Promise < void > {
466
+ if ( ! this . #cleanup) {
467
+ return ;
468
+ }
469
+
470
+ const job = helpers . job ;
471
+
472
+ logger . debug ( "Received cleanup task" , {
473
+ payload : rawPayload ,
474
+ job,
475
+ } ) ;
476
+
477
+ const parsedPayload = RawCronPayloadSchema . safeParse ( rawPayload ) ;
478
+
479
+ if ( ! parsedPayload . success ) {
480
+ throw new Error (
481
+ `Failed to parse cleanup task payload: ${ JSON . stringify ( parsedPayload . error ) } `
482
+ ) ;
483
+ }
484
+
485
+ const payload = parsedPayload . data ;
486
+
487
+ // Add the this.#cleanup.ttl to the payload._cron.ts
488
+ const expirationDate = new Date ( payload . _cron . ts . getTime ( ) - this . #cleanup. ttl ) ;
489
+
490
+ logger . debug ( "Cleaning up old jobs" , {
491
+ expirationDate,
492
+ payload,
493
+ } ) ;
494
+
495
+ const rawResults = await this . #prisma. $queryRawUnsafe (
496
+ `DELETE FROM ${ this . graphileWorkerSchema } .jobs WHERE run_at < $1 AND locked_at IS NULL AND max_attempts = attempts RETURNING id` ,
497
+ expirationDate
498
+ ) ;
499
+
500
+ const results = Array . isArray ( rawResults ) ? rawResults : [ ] ;
501
+
502
+ logger . debug ( "Cleaned up old jobs" , {
503
+ count : results . length ,
504
+ expirationDate,
505
+ payload,
506
+ } ) ;
507
+ }
508
+
437
509
#logDebug( message : string , args ?: any ) {
438
510
logger . debug ( `[worker][${ this . #name} ] ${ message } ` , args ) ;
439
511
}
0 commit comments