diff --git a/package.json b/package.json index 9b7211be5..37bca7333 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "bcrypt": "^5.1.1", "better-sqlite3": "^9.2.2", "body-parser": "^1.20.2", - "bullmq": "^4.15.0", + "bullmq": "^5.40.0", "bytes": "^3.1.2", "class-transformer": "^0.5.1", "clsx": "^2.1.1", diff --git a/packages/twenty-server/src/database/commands/data-seed-demo-workspace/crons/start-data-seed-demo-workspace.cron.command.ts b/packages/twenty-server/src/database/commands/data-seed-demo-workspace/crons/start-data-seed-demo-workspace.cron.command.ts index bc2c1c395..f634f402d 100644 --- a/packages/twenty-server/src/database/commands/data-seed-demo-workspace/crons/start-data-seed-demo-workspace.cron.command.ts +++ b/packages/twenty-server/src/database/commands/data-seed-demo-workspace/crons/start-data-seed-demo-workspace.cron.command.ts @@ -19,14 +19,14 @@ export class StartDataSeedDemoWorkspaceCronCommand extends CommandRunner { } async run(): Promise { - await this.messageQueueService.addCron( - DataSeedDemoWorkspaceJob.name, - undefined, - { + await this.messageQueueService.addCron({ + jobName: DataSeedDemoWorkspaceJob.name, + data: undefined, + options: { repeat: { pattern: dataSeedDemoWorkspaceCronPattern, }, }, - ); + }); } } diff --git a/packages/twenty-server/src/database/commands/data-seed-demo-workspace/crons/stop-data-seed-demo-workspace.cron.command.ts b/packages/twenty-server/src/database/commands/data-seed-demo-workspace/crons/stop-data-seed-demo-workspace.cron.command.ts index a5fbf20dd..8f028783a 100644 --- a/packages/twenty-server/src/database/commands/data-seed-demo-workspace/crons/stop-data-seed-demo-workspace.cron.command.ts +++ b/packages/twenty-server/src/database/commands/data-seed-demo-workspace/crons/stop-data-seed-demo-workspace.cron.command.ts @@ -1,6 +1,5 @@ import { Command, CommandRunner } from 'nest-commander'; -import { dataSeedDemoWorkspaceCronPattern } from 'src/database/commands/data-seed-demo-workspace/crons/data-seed-demo-workspace-cron-pattern'; import { DataSeedDemoWorkspaceJob } from 'src/database/commands/data-seed-demo-workspace/jobs/data-seed-demo-workspace.job'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; @@ -19,9 +18,8 @@ export class StopDataSeedDemoWorkspaceCronCommand extends CommandRunner { } async run(): Promise { - await this.messageQueueService.removeCron( - DataSeedDemoWorkspaceJob.name, - dataSeedDemoWorkspaceCronPattern, - ); + await this.messageQueueService.removeCron({ + jobName: DataSeedDemoWorkspaceJob.name, + }); } } diff --git a/packages/twenty-server/src/engine/core-modules/message-queue/drivers/bullmq.driver.ts b/packages/twenty-server/src/engine/core-modules/message-queue/drivers/bullmq.driver.ts index 95d4568e6..972cf8ef3 100644 --- a/packages/twenty-server/src/engine/core-modules/message-queue/drivers/bullmq.driver.ts +++ b/packages/twenty-server/src/engine/core-modules/message-queue/drivers/bullmq.driver.ts @@ -1,8 +1,8 @@ import { OnModuleDestroy } from '@nestjs/common'; import { JobsOptions, Queue, QueueOptions, Worker } from 'bullmq'; -import omitBy from 'lodash.omitby'; import { v4 } from 'uuid'; +import { isDefined } from 'twenty-shared'; import { QueueCronJobOptions, @@ -13,6 +13,7 @@ import { MessageQueueJob } from 'src/engine/core-modules/message-queue/interface import { MessageQueueWorkerOptions } from 'src/engine/core-modules/message-queue/interfaces/message-queue-worker-options.interface'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; +import { getJobKey } from 'src/engine/core-modules/message-queue/utils/get-job-key.util'; export type BullMQDriverOptions = QueueOptions; @@ -49,54 +50,72 @@ export class BullMQDriver implements MessageQueueDriver, OnModuleDestroy { handler: (job: MessageQueueJob) => Promise, options?: MessageQueueWorkerOptions, ) { - const worker = new Worker( + const workerOptions = isDefined(options?.concurrency) + ? { + ...this.options, + concurrency: options.concurrency, + } + : this.options; + + this.workerMap[queueName] = new Worker( queueName, async (job) => { // TODO: Correctly support for job.id await handler({ data: job.data, id: job.id ?? '', name: job.name }); }, - omitBy( - { - ...this.options, - concurrency: options?.concurrency, - }, - (value) => value === undefined, - ), + workerOptions, ); - - this.workerMap[queueName] = worker; } - async addCron( - queueName: MessageQueue, - jobName: string, - data: T, - options?: QueueCronJobOptions, - ): Promise { + async addCron({ + queueName, + jobName, + data, + options, + jobId, + }: { + queueName: MessageQueue; + jobName: string; + data: T; + options: QueueCronJobOptions; + jobId?: string; + }): Promise { if (!this.queueMap[queueName]) { throw new Error( `Queue ${queueName} is not registered, make sure you have added it as a queue provider`, ); } + const queueOptions: JobsOptions = { - jobId: options?.id, priority: options?.priority, repeat: options?.repeat, removeOnComplete: true, removeOnFail: 100, }; - await this.queueMap[queueName].add(jobName, data, queueOptions); + await this.queueMap[queueName].upsertJobScheduler( + getJobKey({ jobName, jobId }), + options?.repeat, + { + name: jobName, + data, + opts: queueOptions, + }, + ); } - async removeCron( - queueName: MessageQueue, - jobName: string, - pattern: string, - ): Promise { - await this.queueMap[queueName].removeRepeatable(jobName, { - pattern, - }); + async removeCron({ + queueName, + jobName, + jobId, + }: { + queueName: MessageQueue; + jobName: string; + jobId?: string; + }): Promise { + await this.queueMap[queueName].removeJobScheduler( + getJobKey({ jobName, jobId }), + ); } async add( diff --git a/packages/twenty-server/src/engine/core-modules/message-queue/drivers/interfaces/job-options.interface.ts b/packages/twenty-server/src/engine/core-modules/message-queue/drivers/interfaces/job-options.interface.ts index f475ec2c4..869f31785 100644 --- a/packages/twenty-server/src/engine/core-modules/message-queue/drivers/interfaces/job-options.interface.ts +++ b/packages/twenty-server/src/engine/core-modules/message-queue/drivers/interfaces/job-options.interface.ts @@ -5,7 +5,7 @@ export interface QueueJobOptions { } export interface QueueCronJobOptions extends QueueJobOptions { - repeat?: { + repeat: { every?: number; pattern?: string; limit?: number; diff --git a/packages/twenty-server/src/engine/core-modules/message-queue/drivers/interfaces/message-queue-driver.interface.ts b/packages/twenty-server/src/engine/core-modules/message-queue/drivers/interfaces/message-queue-driver.interface.ts index 49647c021..5cdc357e5 100644 --- a/packages/twenty-server/src/engine/core-modules/message-queue/drivers/interfaces/message-queue-driver.interface.ts +++ b/packages/twenty-server/src/engine/core-modules/message-queue/drivers/interfaces/message-queue-driver.interface.ts @@ -19,12 +19,27 @@ export interface MessageQueueDriver { handler: ({ data, id }: { data: T; id: string }) => Promise | void, options?: MessageQueueWorkerOptions, ); - addCron( - queueName: MessageQueue, - jobName: string, - data: T, - options?: QueueCronJobOptions, - ); - removeCron(queueName: MessageQueue, jobName: string, pattern?: string); + addCron({ + queueName, + jobName, + data, + options, + jobId, + }: { + queueName: MessageQueue; + jobName: string; + data: T; + options: QueueCronJobOptions; + jobId?: string; + }); + removeCron({ + queueName, + jobName, + jobId, + }: { + queueName: MessageQueue; + jobName: string; + jobId?: string; + }); register?(queueName: MessageQueue): void; } diff --git a/packages/twenty-server/src/engine/core-modules/message-queue/drivers/pg-boss.driver.ts b/packages/twenty-server/src/engine/core-modules/message-queue/drivers/pg-boss.driver.ts index 1c08c030a..ac3cedff9 100644 --- a/packages/twenty-server/src/engine/core-modules/message-queue/drivers/pg-boss.driver.ts +++ b/packages/twenty-server/src/engine/core-modules/message-queue/drivers/pg-boss.driver.ts @@ -11,6 +11,7 @@ import { MessageQueueWorkerOptions } from 'src/engine/core-modules/message-queue import { MessageQueueDriver } from 'src/engine/core-modules/message-queue/drivers/interfaces/message-queue-driver.interface'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; +import { getJobKey } from 'src/engine/core-modules/message-queue/utils/get-job-key.util'; export type PgBossDriverOptions = PgBoss.ConstructorOptions; @@ -62,27 +63,40 @@ export class PgBossDriver ); } - async addCron( - queueName: MessageQueue, - jobName: string, - data: T, - options?: QueueCronJobOptions, - ): Promise { + async addCron({ + queueName, + jobName, + data, + options, + jobId, + }: { + queueName: MessageQueue; + jobName: string; + data: T; + options: QueueCronJobOptions; + jobId?: string; + }): Promise { + const name = `${queueName}.${getJobKey({ jobName, jobId })}`; + await this.pgBoss.schedule( - `${queueName}.${jobName}`, - options?.repeat?.pattern ?? - DEFAULT_PG_BOSS_CRON_PATTERN_WHEN_NOT_PROVIDED, + name, + options.repeat.pattern ?? DEFAULT_PG_BOSS_CRON_PATTERN_WHEN_NOT_PROVIDED, data as object, - options - ? { - singletonKey: options?.id, - } - : {}, ); } - async removeCron(queueName: MessageQueue, jobName: string): Promise { - await this.pgBoss.unschedule(`${queueName}.${jobName}`); + async removeCron({ + queueName, + jobName, + jobId, + }: { + queueName: MessageQueue; + jobName: string; + jobId?: string; + }): Promise { + const name = `${queueName}.${getJobKey({ jobName, jobId })}`; + + await this.pgBoss.unschedule(name); } async add( diff --git a/packages/twenty-server/src/engine/core-modules/message-queue/drivers/sync.driver.ts b/packages/twenty-server/src/engine/core-modules/message-queue/drivers/sync.driver.ts index 1d7db2953..489599286 100644 --- a/packages/twenty-server/src/engine/core-modules/message-queue/drivers/sync.driver.ts +++ b/packages/twenty-server/src/engine/core-modules/message-queue/drivers/sync.driver.ts @@ -24,11 +24,15 @@ export class SyncDriver implements MessageQueueDriver { await this.processJob(queueName, { id: '', name: jobName, data }); } - async addCron( - queueName: MessageQueue, - jobName: string, - data: T, - ): Promise { + async addCron({ + queueName, + jobName, + data, + }: { + queueName: MessageQueue; + jobName: string; + data: T; + }): Promise { this.logger.log(`Running cron job with SyncDriver`); await this.processJob(queueName, { id: '', @@ -38,7 +42,7 @@ export class SyncDriver implements MessageQueueDriver { }); } - async removeCron(queueName: MessageQueue) { + async removeCron({ queueName }: { queueName: MessageQueue }) { this.logger.log(`Removing '${queueName}' cron job with SyncDriver`); } diff --git a/packages/twenty-server/src/engine/core-modules/message-queue/services/message-queue.service.ts b/packages/twenty-server/src/engine/core-modules/message-queue/services/message-queue.service.ts index 2030ca8dd..4f5f4e24b 100644 --- a/packages/twenty-server/src/engine/core-modules/message-queue/services/message-queue.service.ts +++ b/packages/twenty-server/src/engine/core-modules/message-queue/services/message-queue.service.ts @@ -35,16 +35,38 @@ export class MessageQueueService { return this.driver.add(this.queueName, jobName, data, options); } - addCron( - jobName: string, - data: T, - options?: QueueCronJobOptions, - ): Promise { - return this.driver.addCron(this.queueName, jobName, data, options); + addCron({ + jobName, + data, + options, + jobId, + }: { + jobName: string; + data: T; + options: QueueCronJobOptions; + jobId?: string; + }): Promise { + return this.driver.addCron({ + queueName: this.queueName, + jobName, + data, + options, + jobId, + }); } - removeCron(jobName: string, pattern: string): Promise { - return this.driver.removeCron(this.queueName, jobName, pattern); + removeCron({ + jobName, + jobId, + }: { + jobName: string; + jobId?: string; + }): Promise { + return this.driver.removeCron({ + queueName: this.queueName, + jobName, + jobId, + }); } work( diff --git a/packages/twenty-server/src/engine/core-modules/message-queue/utils/get-job-key.util.ts b/packages/twenty-server/src/engine/core-modules/message-queue/utils/get-job-key.util.ts new file mode 100644 index 000000000..3f16c307c --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/message-queue/utils/get-job-key.util.ts @@ -0,0 +1,9 @@ +export const getJobKey = ({ + jobName, + jobId, +}: { + jobName: string; + jobId?: string; +}) => { + return `${jobName}${jobId ? `.${jobId}` : ''}`; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/commands/clean-suspended-workspaces.cron.command.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/commands/clean-suspended-workspaces.cron.command.ts index ab5eeb553..3b79822ed 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/commands/clean-suspended-workspaces.cron.command.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/commands/clean-suspended-workspaces.cron.command.ts @@ -19,12 +19,12 @@ export class CleanSuspendedWorkspacesCronCommand extends CommandRunner { } async run(): Promise { - await this.messageQueueService.addCron( - CleanSuspendedWorkspacesJob.name, - undefined, - { + await this.messageQueueService.addCron({ + jobName: CleanSuspendedWorkspacesJob.name, + data: undefined, + options: { repeat: { pattern: cleanSuspendedWorkspaceCronPattern }, }, - ); + }); } } diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-event-list-fetch.cron.command.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-event-list-fetch.cron.command.ts index 80f341279..97e7fe0e4 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-event-list-fetch.cron.command.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-event-list-fetch.cron.command.ts @@ -20,14 +20,14 @@ export class CalendarEventListFetchCronCommand extends CommandRunner { } async run(): Promise { - await this.messageQueueService.addCron( - CalendarEventListFetchCronJob.name, - undefined, - { + await this.messageQueueService.addCron({ + jobName: CalendarEventListFetchCronJob.name, + data: undefined, + options: { repeat: { pattern: CALENDAR_EVENTS_LIST_CRON_PATTERN, }, }, - ); + }); } } diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-import.cron.command.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-import.cron.command.ts index a73bda1e9..d5bbd7cbc 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-import.cron.command.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-import.cron.command.ts @@ -21,12 +21,12 @@ export class CalendarEventsImportCronCommand extends CommandRunner { } async run(): Promise { - await this.messageQueueService.addCron( - CalendarEventsImportCronJob.name, - undefined, - { + await this.messageQueueService.addCron({ + jobName: CalendarEventsImportCronJob.name, + data: undefined, + options: { repeat: { pattern: CALENDAR_EVENTS_IMPORT_CRON_PATTERN }, }, - ); + }); } } diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-ongoing-stale.cron.command.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-ongoing-stale.cron.command.ts index 133077ce6..88c3d2cd0 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-ongoing-stale.cron.command.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-ongoing-stale.cron.command.ts @@ -22,12 +22,12 @@ export class CalendarOngoingStaleCronCommand extends CommandRunner { } async run(): Promise { - await this.messageQueueService.addCron( - CalendarOngoingStaleCronJob.name, - undefined, - { + await this.messageQueueService.addCron({ + jobName: CalendarOngoingStaleCronJob.name, + data: undefined, + options: { repeat: { pattern: CALENDAR_ONGOING_STALE_CRON_PATTERN }, }, - ); + }); } } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/crons/commands/messaging-message-list-fetch.cron.command.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/crons/commands/messaging-message-list-fetch.cron.command.ts index d0a721ccc..c82e9755d 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/crons/commands/messaging-message-list-fetch.cron.command.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/crons/commands/messaging-message-list-fetch.cron.command.ts @@ -22,12 +22,12 @@ export class MessagingMessageListFetchCronCommand extends CommandRunner { } async run(): Promise { - await this.messageQueueService.addCron( - MessagingMessageListFetchCronJob.name, - undefined, - { + await this.messageQueueService.addCron({ + jobName: MessagingMessageListFetchCronJob.name, + data: undefined, + options: { repeat: { pattern: MESSAGING_MESSAGE_LIST_FETCH_CRON_PATTERN }, }, - ); + }); } } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/crons/commands/messaging-messages-import.cron.command.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/crons/commands/messaging-messages-import.cron.command.ts index 9e115565f..c5a3c88a2 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/crons/commands/messaging-messages-import.cron.command.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/crons/commands/messaging-messages-import.cron.command.ts @@ -21,14 +21,14 @@ export class MessagingMessagesImportCronCommand extends CommandRunner { } async run(): Promise { - await this.messageQueueService.addCron( - MessagingMessagesImportCronJob.name, - undefined, - { + await this.messageQueueService.addCron({ + jobName: MessagingMessagesImportCronJob.name, + data: undefined, + options: { repeat: { pattern: MESSAGING_MESSAGES_IMPORT_CRON_PATTERN, }, }, - ); + }); } } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/crons/commands/messaging-ongoing-stale.cron.command.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/crons/commands/messaging-ongoing-stale.cron.command.ts index 1df4ef8ce..27778b25c 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/crons/commands/messaging-ongoing-stale.cron.command.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/crons/commands/messaging-ongoing-stale.cron.command.ts @@ -22,12 +22,12 @@ export class MessagingOngoingStaleCronCommand extends CommandRunner { } async run(): Promise { - await this.messageQueueService.addCron( - MessagingOngoingStaleCronJob.name, - undefined, - { + await this.messageQueueService.addCron({ + jobName: MessagingOngoingStaleCronJob.name, + data: undefined, + options: { repeat: { pattern: MESSAGING_ONGOING_STALE_CRON_PATTERN }, }, - ); + }); } } diff --git a/packages/twenty-server/src/modules/messaging/monitoring/crons/commands/messaging-message-channel-sync-status-monitoring.cron.command.ts b/packages/twenty-server/src/modules/messaging/monitoring/crons/commands/messaging-message-channel-sync-status-monitoring.cron.command.ts index 8553d7cb8..c19cc1f6c 100644 --- a/packages/twenty-server/src/modules/messaging/monitoring/crons/commands/messaging-message-channel-sync-status-monitoring.cron.command.ts +++ b/packages/twenty-server/src/modules/messaging/monitoring/crons/commands/messaging-message-channel-sync-status-monitoring.cron.command.ts @@ -22,15 +22,15 @@ export class MessagingMessageChannelSyncStatusMonitoringCronCommand extends Comm } async run(): Promise { - await this.messageQueueService.addCron( - MessagingMessageChannelSyncStatusMonitoringCronJob.name, - undefined, - { + await this.messageQueueService.addCron({ + jobName: MessagingMessageChannelSyncStatusMonitoringCronJob.name, + data: undefined, + options: { repeat: { pattern: MESSAGING_MESSAGE_CHANNEL_SYNC_STATUS_MONITORING_CRON_PATTERN, }, }, - ); + }); } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/database-event-trigger/listeners/database-event-trigger.listener.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/database-event-trigger/listeners/database-event-trigger.listener.ts index ca4b54362..ea6646514 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/database-event-trigger/listeners/database-event-trigger.listener.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/database-event-trigger/listeners/database-event-trigger.listener.ts @@ -13,9 +13,9 @@ import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global. import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type'; import { WorkflowEventListenerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-event-listener.workspace-entity'; import { - WorkflowEventTriggerJob, - WorkflowEventTriggerJobData, -} from 'src/modules/workflow/workflow-trigger/jobs/workflow-event-trigger.job'; + WorkflowTriggerJob, + WorkflowTriggerJobData, +} from 'src/modules/workflow/workflow-trigger/jobs/workflow-trigger.job'; import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator'; import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; @@ -103,8 +103,8 @@ export class DatabaseEventTriggerListener { for (const eventListener of eventListeners) { for (const eventPayload of payload.events) { - this.messageQueueService.add( - WorkflowEventTriggerJob.name, + this.messageQueueService.add( + WorkflowTriggerJob.name, { workspaceId, workflowId: eventListener.workflowId, diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/jobs/workflow-event-trigger.job.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/jobs/workflow-event-trigger.job.ts deleted file mode 100644 index 12391290f..000000000 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/jobs/workflow-event-trigger.job.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Scope } from '@nestjs/common'; - -import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; -import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; -import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; -import { - WorkflowVersionStatus, - WorkflowVersionWorkspaceEntity, -} from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; -import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity'; -import { WorkflowRunnerWorkspaceService } from 'src/modules/workflow/workflow-runner/workspace-services/workflow-runner.workspace-service'; -import { - WorkflowTriggerException, - WorkflowTriggerExceptionCode, -} from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception'; - -export type WorkflowEventTriggerJobData = { - workspaceId: string; - workflowId: string; - payload: object; -}; - -@Processor({ queueName: MessageQueue.workflowQueue, scope: Scope.REQUEST }) -export class WorkflowEventTriggerJob { - constructor( - private readonly twentyORMManager: TwentyORMManager, - private readonly workflowRunnerWorkspaceService: WorkflowRunnerWorkspaceService, - ) {} - - @Process(WorkflowEventTriggerJob.name) - async handle(data: WorkflowEventTriggerJobData): Promise { - const workflowRepository = - await this.twentyORMManager.getRepository( - 'workflow', - ); - - const workflow = await workflowRepository.findOneByOrFail({ - id: data.workflowId, - }); - - if (!workflow.lastPublishedVersionId) { - throw new WorkflowTriggerException( - 'Workflow has no published version', - WorkflowTriggerExceptionCode.INTERNAL_ERROR, - ); - } - - const workflowVersionRepository = - await this.twentyORMManager.getRepository( - 'workflowVersion', - ); - - const workflowVersion = await workflowVersionRepository.findOneByOrFail({ - id: workflow.lastPublishedVersionId, - }); - - if (workflowVersion.status !== WorkflowVersionStatus.ACTIVE) { - throw new WorkflowTriggerException( - 'Workflow version is not active', - WorkflowTriggerExceptionCode.INTERNAL_ERROR, - ); - } - - await this.workflowRunnerWorkspaceService.run( - data.workspaceId, - workflow.lastPublishedVersionId, - data.payload, - { - source: FieldActorSource.WORKFLOW, - name: workflow.name, - }, - ); - } -} diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/jobs/workflow-trigger.job.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/jobs/workflow-trigger.job.ts new file mode 100644 index 000000000..bb2658e79 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/jobs/workflow-trigger.job.ts @@ -0,0 +1,89 @@ +import { Scope } from '@nestjs/common'; + +import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; +import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; +import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; +import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; +import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { + WorkflowVersionStatus, + WorkflowVersionWorkspaceEntity, +} from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; +import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity'; +import { WorkflowRunnerWorkspaceService } from 'src/modules/workflow/workflow-runner/workspace-services/workflow-runner.workspace-service'; +import { + WorkflowTriggerException, + WorkflowTriggerExceptionCode, +} from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception'; +import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; +import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; + +export type WorkflowTriggerJobData = { + workspaceId: string; + workflowId: string; + payload: object; +}; + +@Processor({ queueName: MessageQueue.workflowQueue, scope: Scope.REQUEST }) +export class WorkflowTriggerJob { + constructor( + private readonly twentyORMManager: TwentyORMManager, + private readonly workflowRunnerWorkspaceService: WorkflowRunnerWorkspaceService, + @InjectMessageQueue(MessageQueue.workflowQueue) + private readonly messageQueueService: MessageQueueService, + ) {} + + @Process(WorkflowTriggerJob.name) + async handle(data: WorkflowTriggerJobData): Promise { + try { + const workflowRepository = + await this.twentyORMManager.getRepository( + 'workflow', + ); + + const workflow = await workflowRepository.findOneByOrFail({ + id: data.workflowId, + }); + + if (!workflow.lastPublishedVersionId) { + throw new WorkflowTriggerException( + 'Workflow has no published version', + WorkflowTriggerExceptionCode.INTERNAL_ERROR, + ); + } + + const workflowVersionRepository = + await this.twentyORMManager.getRepository( + 'workflowVersion', + ); + + const workflowVersion = await workflowVersionRepository.findOneByOrFail({ + id: workflow.lastPublishedVersionId, + }); + + if (workflowVersion.status !== WorkflowVersionStatus.ACTIVE) { + throw new WorkflowTriggerException( + 'Workflow version is not active', + WorkflowTriggerExceptionCode.INTERNAL_ERROR, + ); + } + + await this.workflowRunnerWorkspaceService.run( + data.workspaceId, + workflow.lastPublishedVersionId, + data.payload, + { + source: FieldActorSource.WORKFLOW, + name: workflow.name, + }, + ); + } catch (e) { + // We remove cron if it exists when no valid workflowVersion exists + await this.messageQueueService.removeCron({ + jobName: WorkflowTriggerJob.name, + jobId: data.workflowId, + }); + throw e; + } + } +} diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/types/workflow-trigger.type.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/types/workflow-trigger.type.ts index 75f091c51..c58277a7f 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/types/workflow-trigger.type.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/types/workflow-trigger.type.ts @@ -3,6 +3,7 @@ import { OutputSchema } from 'src/modules/workflow/workflow-builder/types/output export enum WorkflowTriggerType { DATABASE_EVENT = 'DATABASE_EVENT', MANUAL = 'MANUAL', + CRON = 'CRON', } type BaseWorkflowTriggerSettings = { @@ -35,8 +36,16 @@ export type WorkflowManualTrigger = BaseTrigger & { }; }; +export type WorkflowCronTrigger = BaseTrigger & { + type: WorkflowTriggerType.CRON; + settings: { + pattern: string; + }; +}; + export type WorkflowManualTriggerSettings = WorkflowManualTrigger['settings']; export type WorkflowTrigger = | WorkflowDatabaseEventTrigger - | WorkflowManualTrigger; + | WorkflowManualTrigger + | WorkflowCronTrigger; diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/workflow-trigger.module.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/workflow-trigger.module.ts index 9aa43a080..103f677de 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/workflow-trigger.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/workflow-trigger.module.ts @@ -6,7 +6,7 @@ import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/s import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module'; import { WorkflowRunnerModule } from 'src/modules/workflow/workflow-runner/workflow-runner.module'; import { DatabaseEventTriggerModule } from 'src/modules/workflow/workflow-trigger/database-event-trigger/database-event-trigger.module'; -import { WorkflowEventTriggerJob } from 'src/modules/workflow/workflow-trigger/jobs/workflow-event-trigger.job'; +import { WorkflowTriggerJob } from 'src/modules/workflow/workflow-trigger/jobs/workflow-trigger.job'; import { WorkflowTriggerWorkspaceService } from 'src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; @@ -20,7 +20,7 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat providers: [ WorkflowTriggerWorkspaceService, ScopedWorkspaceContextFactory, - WorkflowEventTriggerJob, + WorkflowTriggerJob, ], exports: [WorkflowTriggerWorkspaceService], }) diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts index 070a690b2..46e397588 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts @@ -29,6 +29,13 @@ import { import { WorkflowTriggerType } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type'; import { assertVersionCanBeActivated } from 'src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util'; import { assertNever } from 'src/utils/assert'; +import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; +import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; +import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; +import { + WorkflowTriggerJob, + WorkflowTriggerJobData, +} from 'src/modules/workflow/workflow-trigger/jobs/workflow-trigger.job'; @Injectable() export class WorkflowTriggerWorkspaceService { @@ -41,6 +48,8 @@ export class WorkflowTriggerWorkspaceService { private readonly workspaceEventEmitter: WorkspaceEventEmitter, @InjectRepository(ObjectMetadataEntity, 'metadata') private readonly objectMetadataRepository: Repository, + @InjectMessageQueue(MessageQueue.workflowQueue) + private readonly messageQueueService: MessageQueueService, ) {} private getWorkspaceId() { @@ -329,6 +338,23 @@ export class WorkflowTriggerWorkspaceService { return; case WorkflowTriggerType.MANUAL: + return; + case WorkflowTriggerType.CRON: + await this.messageQueueService.addCron({ + jobName: WorkflowTriggerJob.name, + jobId: workflowVersion.workflowId, + data: { + workspaceId: this.getWorkspaceId(), + workflowId: workflowVersion.workflowId, + payload: {}, + }, + options: { + repeat: { + pattern: workflowVersion.trigger.settings.pattern, + }, + }, + }); + return; default: { assertNever(workflowVersion.trigger); @@ -351,6 +377,13 @@ export class WorkflowTriggerWorkspaceService { return; case WorkflowTriggerType.MANUAL: + return; + case WorkflowTriggerType.CRON: + await this.messageQueueService.removeCron({ + jobName: WorkflowTriggerJob.name, + jobId: workflowVersion.workflowId, + }); + return; default: assertNever(workflowVersion.trigger); diff --git a/packages/twenty-shared/src/constants/index.ts b/packages/twenty-shared/src/constants/index.ts new file mode 100644 index 000000000..03be41cc6 --- /dev/null +++ b/packages/twenty-shared/src/constants/index.ts @@ -0,0 +1,5 @@ +export * from './FieldForTotalCountAggregateOperation'; +export * from './Locales'; +export * from './TwentyCompaniesBaseUrl'; +export * from './TwentyIconsBaseUrl'; +export * from './SettingsFeatures'; diff --git a/packages/twenty-shared/src/index.ts b/packages/twenty-shared/src/index.ts index a0a1a2985..5679503b8 100644 --- a/packages/twenty-shared/src/index.ts +++ b/packages/twenty-shared/src/index.ts @@ -1,13 +1,4 @@ -export * from './constants/FieldForTotalCountAggregateOperation'; -export * from './constants/Locales'; -export * from './constants/SettingsFeatures'; -export * from './constants/TwentyCompaniesBaseUrl'; -export * from './constants/TwentyIconsBaseUrl'; -export * from './types/ConnectedAccountProvider'; -export * from './types/FieldMetadataType'; -export * from './utils/fieldMetadata/isFieldMetadataDateKind'; -export * from './utils/image/getImageAbsoluteURI'; -export * from './utils/isDefined'; -export * from './utils/isValidLocale'; -export * from './utils/strings'; +export * from './constants'; +export * from './types'; +export * from './utils'; export * from './workspace'; diff --git a/packages/twenty-shared/src/types/index.ts b/packages/twenty-shared/src/types/index.ts new file mode 100644 index 000000000..92bece5db --- /dev/null +++ b/packages/twenty-shared/src/types/index.ts @@ -0,0 +1,2 @@ +export * from './ConnectedAccountProvider'; +export * from './FieldMetadataType'; diff --git a/packages/twenty-shared/src/utils/fieldMetadata/index.ts b/packages/twenty-shared/src/utils/fieldMetadata/index.ts new file mode 100644 index 000000000..892577070 --- /dev/null +++ b/packages/twenty-shared/src/utils/fieldMetadata/index.ts @@ -0,0 +1 @@ +export * from './isFieldMetadataDateKind'; diff --git a/packages/twenty-shared/src/utils/image/index.ts b/packages/twenty-shared/src/utils/image/index.ts new file mode 100644 index 000000000..937d6ea94 --- /dev/null +++ b/packages/twenty-shared/src/utils/image/index.ts @@ -0,0 +1 @@ +export * from './getImageAbsoluteURI'; diff --git a/packages/twenty-shared/src/utils/index.ts b/packages/twenty-shared/src/utils/index.ts new file mode 100644 index 000000000..0536286be --- /dev/null +++ b/packages/twenty-shared/src/utils/index.ts @@ -0,0 +1,5 @@ +export * from './fieldMetadata'; +export * from './image'; +export * from './strings'; +export * from './validation'; +export * from './validation'; diff --git a/packages/twenty-shared/src/utils/strings/__tests__/capitalize.test.ts b/packages/twenty-shared/src/utils/strings/__tests__/capitalize.test.ts index cda70b03a..907ec565b 100644 --- a/packages/twenty-shared/src/utils/strings/__tests__/capitalize.test.ts +++ b/packages/twenty-shared/src/utils/strings/__tests__/capitalize.test.ts @@ -1,4 +1,4 @@ -import { capitalize } from '../capitalize.util'; +import { capitalize } from '../capitalize'; describe('capitalize', () => { it('should capitalize a string', () => { expect(capitalize('test')).toBe('Test'); diff --git a/packages/twenty-shared/src/utils/strings/__tests__/isValidUuid.test.ts b/packages/twenty-shared/src/utils/strings/__tests__/isValidUuid.test.ts deleted file mode 100644 index 1eb494e9b..000000000 --- a/packages/twenty-shared/src/utils/strings/__tests__/isValidUuid.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { isValidUuid } from '../isValidUuid.util'; - -describe('isValidUuid', () => { - it('should return true for a valid UUID', () => { - expect(isValidUuid('123e4567-e89b-12d3-a456-426614174000')).toBe(true); - }); - - it('should return false for an invalid UUID', () => { - expect(isValidUuid('123e4567-e89b-12d3-a456-426614174000')).toBe(false); - }); -}); diff --git a/packages/twenty-shared/src/utils/strings/capitalize.util.ts b/packages/twenty-shared/src/utils/strings/capitalize.ts similarity index 100% rename from packages/twenty-shared/src/utils/strings/capitalize.util.ts rename to packages/twenty-shared/src/utils/strings/capitalize.ts diff --git a/packages/twenty-shared/src/utils/strings/index.ts b/packages/twenty-shared/src/utils/strings/index.ts index ef55c7241..89859f65b 100644 --- a/packages/twenty-shared/src/utils/strings/index.ts +++ b/packages/twenty-shared/src/utils/strings/index.ts @@ -1,2 +1 @@ -export * from './capitalize.util'; -export * from './isValidUuid.util'; +export * from './capitalize'; diff --git a/packages/twenty-shared/src/utils/strings/isValidUuid.util.ts b/packages/twenty-shared/src/utils/strings/isValidUuid.util.ts deleted file mode 100644 index 322c982bd..000000000 --- a/packages/twenty-shared/src/utils/strings/isValidUuid.util.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const isValidUuid = (value: string) => { - return /^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i.test( - value, - ); -}; diff --git a/packages/twenty-shared/src/utils/__tests__/isDefined.test.ts b/packages/twenty-shared/src/utils/validation/__tests__/isDefined.test.ts similarity index 100% rename from packages/twenty-shared/src/utils/__tests__/isDefined.test.ts rename to packages/twenty-shared/src/utils/validation/__tests__/isDefined.test.ts diff --git a/packages/twenty-shared/src/utils/validation/__tests__/isValidUuid.test.ts b/packages/twenty-shared/src/utils/validation/__tests__/isValidUuid.test.ts new file mode 100644 index 000000000..ee69ddf50 --- /dev/null +++ b/packages/twenty-shared/src/utils/validation/__tests__/isValidUuid.test.ts @@ -0,0 +1,19 @@ +import { isValidUuid } from '../isValidUuid'; + +describe('isValidUuid', () => { + it('should return true for a valid UUID', () => { + expect(isValidUuid('123e4567-e89b-12d3-a456-426614174000')).toBe(true); + expect(isValidUuid('550e8400-e29b-41d4-a716-446655440000')).toBe(true); + }); + + it('should return false for an invalid UUID', () => { + expect(isValidUuid('invalid-uuid')).toBe(false); + expect(isValidUuid('12345')).toBe(false); + expect(isValidUuid('550e8400e29b41d4a716446655440000')).toBe(false); + expect(isValidUuid('')).toBe(false); + expect(isValidUuid('123e4567-e89b-12d3-a456-42661417400-')).toBe(false); + expect(isValidUuid('123e4567-e89b-12d3-a456-42661417400')).toBe(false); + expect(isValidUuid('123e4567-e89b-12d3-a456-42661417400)')).toBe(false); + expect(isValidUuid('123e4567-e89b-12d3-a456-4266141740001')).toBe(false); + }); +}); diff --git a/packages/twenty-shared/src/utils/validation/__tests__/isValideLocale.test.ts b/packages/twenty-shared/src/utils/validation/__tests__/isValideLocale.test.ts new file mode 100644 index 000000000..92d48184a --- /dev/null +++ b/packages/twenty-shared/src/utils/validation/__tests__/isValideLocale.test.ts @@ -0,0 +1,15 @@ +import { isValidLocale } from '../isValidLocale'; +import { APP_LOCALES } from 'src/constants/Locales'; + +describe('isValidLocale', () => { + it('should return true for valid locales', () => { + Object.keys(APP_LOCALES).forEach((locale) => { + expect(isValidLocale(locale)).toBe(true); + }); + }); + + it('should return false for invalid locales', () => { + expect(isValidLocale('invalidLocale')).toBe(false); + expect(isValidLocale(null)).toBe(false); + }); +}); diff --git a/packages/twenty-shared/src/utils/validation/index.ts b/packages/twenty-shared/src/utils/validation/index.ts new file mode 100644 index 000000000..767a68751 --- /dev/null +++ b/packages/twenty-shared/src/utils/validation/index.ts @@ -0,0 +1,3 @@ +export * from './isValidUuid'; +export * from './isDefined'; +export * from './isValidLocale'; diff --git a/packages/twenty-shared/src/utils/isDefined.ts b/packages/twenty-shared/src/utils/validation/isDefined.ts similarity index 100% rename from packages/twenty-shared/src/utils/isDefined.ts rename to packages/twenty-shared/src/utils/validation/isDefined.ts diff --git a/packages/twenty-shared/src/utils/isValidLocale.ts b/packages/twenty-shared/src/utils/validation/isValidLocale.ts similarity index 100% rename from packages/twenty-shared/src/utils/isValidLocale.ts rename to packages/twenty-shared/src/utils/validation/isValidLocale.ts diff --git a/packages/twenty-shared/src/utils/validation/isValidUuid.ts b/packages/twenty-shared/src/utils/validation/isValidUuid.ts new file mode 100644 index 000000000..21d67b412 --- /dev/null +++ b/packages/twenty-shared/src/utils/validation/isValidUuid.ts @@ -0,0 +1,5 @@ +export const isValidUuid = (value: string): boolean => { + const uuidRegex = + /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + return uuidRegex.test(value); +}; diff --git a/packages/twenty-shared/src/workspace/index.ts b/packages/twenty-shared/src/workspace/index.ts index c9907f187..6d5a6ef49 100644 --- a/packages/twenty-shared/src/workspace/index.ts +++ b/packages/twenty-shared/src/workspace/index.ts @@ -1,2 +1,2 @@ -export * from './types/WorkspaceActivationStatus'; -export * from './utils/isWorkspaceActiveOrSuspended'; +export * from './types'; +export * from './utils'; diff --git a/packages/twenty-shared/src/workspace/types/index.ts b/packages/twenty-shared/src/workspace/types/index.ts new file mode 100644 index 000000000..28be5828b --- /dev/null +++ b/packages/twenty-shared/src/workspace/types/index.ts @@ -0,0 +1 @@ +export * from './WorkspaceActivationStatus'; diff --git a/packages/twenty-shared/src/workspace/utils/index.ts b/packages/twenty-shared/src/workspace/utils/index.ts new file mode 100644 index 000000000..9b865d9d0 --- /dev/null +++ b/packages/twenty-shared/src/workspace/utils/index.ts @@ -0,0 +1 @@ +export * from './isWorkspaceActiveOrSuspended'; diff --git a/yarn.lock b/yarn.lock index 5dd84b79b..213988038 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22018,20 +22018,18 @@ __metadata: languageName: node linkType: hard -"bullmq@npm:^4.15.0": - version: 4.18.2 - resolution: "bullmq@npm:4.18.2" +"bullmq@npm:^5.40.0": + version: 5.40.0 + resolution: "bullmq@npm:5.40.0" dependencies: - cron-parser: "npm:^4.6.0" - glob: "npm:^8.0.3" - ioredis: "npm:^5.3.2" - lodash: "npm:^4.17.21" - msgpackr: "npm:^1.6.2" + cron-parser: "npm:^4.9.0" + ioredis: "npm:^5.4.1" + msgpackr: "npm:^1.11.2" node-abort-controller: "npm:^3.1.1" semver: "npm:^7.5.4" tslib: "npm:^2.0.0" uuid: "npm:^9.0.0" - checksum: 10c0/09371a6d53377e556a37e3e046576bb20056a14ac26d29528bd0a4054c33f458f1764ae696ae6a39fda688e8215ef34f1ba94fe56ba89db5b9ad2c9aa0082f2f + checksum: 10c0/00ca72939af44815cfd33e366d2bf650232ac14bc2e3afe32c3aa4bbd74e6b23bec6622f1ecf4a0da684c548ac5553738457a9780bba9bfe23bb831efb766eb8 languageName: node linkType: hard @@ -24025,7 +24023,7 @@ __metadata: languageName: node linkType: hard -"cron-parser@npm:^4.0.0, cron-parser@npm:^4.6.0": +"cron-parser@npm:^4.0.0, cron-parser@npm:^4.9.0": version: 4.9.0 resolution: "cron-parser@npm:4.9.0" dependencies: @@ -29005,7 +29003,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^8.0.1, glob@npm:^8.0.3, glob@npm:^8.1.0": +"glob@npm:^8.0.1, glob@npm:^8.1.0": version: 8.1.0 resolution: "glob@npm:8.1.0" dependencies: @@ -31081,9 +31079,9 @@ __metadata: languageName: node linkType: hard -"ioredis@npm:^5.3.2": - version: 5.4.1 - resolution: "ioredis@npm:5.4.1" +"ioredis@npm:^5.4.1": + version: 5.4.2 + resolution: "ioredis@npm:5.4.2" dependencies: "@ioredis/commands": "npm:^1.1.1" cluster-key-slot: "npm:^1.1.0" @@ -31094,7 +31092,7 @@ __metadata: redis-errors: "npm:^1.2.0" redis-parser: "npm:^3.0.0" standard-as-callback: "npm:^2.1.0" - checksum: 10c0/5d28b7c89a3cab5b76d75923d7d4ce79172b3a1ca9be690133f6e8e393a7a4b4ffd55513e618bbb5504fed80d9e1395c9d9531a7c5c5c84aa4c4e765cca75456 + checksum: 10c0/e59d2cceb43ed74b487d7b50fa91b93246e734e5d4835c7e62f64e44da072f12ab43b044248012e6f8b76c61a7c091a2388caad50e8ad69a8ce5515a730b23b8 languageName: node linkType: hard @@ -36774,15 +36772,15 @@ __metadata: languageName: node linkType: hard -"msgpackr@npm:^1.6.2": - version: 1.11.0 - resolution: "msgpackr@npm:1.11.0" +"msgpackr@npm:^1.11.2": + version: 1.11.2 + resolution: "msgpackr@npm:1.11.2" dependencies: msgpackr-extract: "npm:^3.0.2" dependenciesMeta: msgpackr-extract: optional: true - checksum: 10c0/a7edc36754ec9f8469bc14c896f0f36e0e3de595c0bb5ac7b2ab8c2a72a2e188c12f1345d71a127f8537d9bbc880407a7073ac1d29c27822178bc0b81ae7370e + checksum: 10c0/7d2e81ca82c397b2352d470d6bc8f4a967fe4fe14f8fc1fc9906b23009fdfb543999b1ad29c700b8861581e0b6bf903d6f0fefb69a09375cbca6d4d802e6c906 languageName: node linkType: hard @@ -46188,7 +46186,7 @@ __metadata: bcrypt: "npm:^5.1.1" better-sqlite3: "npm:^9.2.2" body-parser: "npm:^1.20.2" - bullmq: "npm:^4.15.0" + bullmq: "npm:^5.40.0" bytes: "npm:^3.1.2" chromatic: "npm:^6.18.0" class-transformer: "npm:^0.5.1"