UNPKG

@sidequest/mysql-backend

Version:

@sidequest/mysql-backend is a MySQL backend for Sidequest, a distributed background job queue system.

162 lines (157 loc) 7.25 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var backend = require('@sidequest/backend'); var core = require('@sidequest/core'); var createKnex = require('knex'); var path = require('path'); var util = require('util'); const defaultKnexConfig = { client: "mysql2", migrations: { directory: path.join(__dirname, "..", "migrations"), tableName: "sidequest_migrations", extension: "cjs", }, }; class MysqlBackend extends backend.SQLBackend { constructor(dbConfig) { const knexConfig = { ...defaultKnexConfig, ...(typeof dbConfig === "string" ? { connection: dbConfig } : dbConfig), }; const knex = createKnex(knexConfig); super(knex); } async createNewQueue(queueConfig) { if (queueConfig.concurrency !== undefined && queueConfig.concurrency < 1) { throw new Error("Concurrency must be at least 1"); } const data = { ...backend.QUEUE_FALLBACK, ...queueConfig, }; core.logger("Backend").debug(`Inserting new queue config: ${util.inspect(data)}`); const result = await this.knex.transaction(async (trx) => { const [insertedId] = await trx("sidequest_queues").insert(data); const inserted = await trx("sidequest_queues").where({ id: insertedId }).first(); if (!inserted) throw new Error("Failed to insert queue config."); return inserted; }); core.logger("Backend").debug(`Queue inserted successfully: ${util.inspect(result)}`); return result; } async updateQueue(queueData) { if (queueData.concurrency !== undefined && queueData.concurrency < 1) { throw new Error("Concurrency must be at least 1"); } const { id, ...updates } = queueData; core.logger("Backend").debug(`Updating queue: ${util.inspect(queueData)}`); if (!id) throw new Error("Queue id is required for update."); const result = await this.knex.transaction(async (trx) => { await trx("sidequest_queues").where({ id }).update(updates); const updated = await trx("sidequest_queues").where({ id }).first(); if (!updated) throw new Error("Cannot update queue, not found."); return updated; }); core.logger("Backend").debug(`Queue updated successfully: ${util.inspect(result)}`); return result; } async createNewJob(job) { const data = { queue: job.queue, script: job.script, class: job.class, args: JSON.stringify(job.args ?? backend.JOB_FALLBACK.args), constructor_args: JSON.stringify(job.constructor_args ?? backend.JOB_FALLBACK.constructor_args), state: job.state, attempt: job.attempt, max_attempts: job.max_attempts ?? backend.JOB_FALLBACK.max_attempts, available_at: job.available_at ?? backend.JOB_FALLBACK.available_at, timeout: job.timeout ?? backend.JOB_FALLBACK.timeout, unique_digest: job.unique_digest ?? backend.JOB_FALLBACK.unique_digest, uniqueness_config: job.uniqueness_config ? JSON.stringify(job.uniqueness_config) : backend.JOB_FALLBACK.uniqueness_config, inserted_at: new Date(), backoff_strategy: job.backoff_strategy ?? backend.JOB_FALLBACK.backoff_strategy, retry_delay: job.retry_delay ?? backend.JOB_FALLBACK.retry_delay, }; core.logger("Backend").debug(`Creating new job: ${util.inspect(data)}`); try { const insertedJob = await this.knex.transaction(async (trx) => { const [insertedId] = await trx("sidequest_jobs").insert(data); const inserted = await trx("sidequest_jobs").where({ id: insertedId }).first(); if (!inserted) throw new Error("Failed to create job."); core.logger("Backend").debug(`Job created successfully: ${util.inspect(inserted)}`); return backend.safeParseJobData(inserted); }); return insertedJob; } catch (error) { if (error instanceof Error && (error.message?.includes("sidequest_jobs.unique_digest") || ("constraint" in error && error.constraint === "sidequest_jobs_unique_digest_active_idx"))) { throw new core.DuplicatedJobError(job); } throw error; } } async updateJob(job) { const data = { ...job, args: job.args ? JSON.stringify(job.args) : job.args, constructor_args: job.constructor_args ? JSON.stringify(job.constructor_args) : job.constructor_args, result: job.result ? JSON.stringify(job.result) : job.result, errors: job.errors ? JSON.stringify(job.errors) : job.errors, uniqueness_config: job.uniqueness_config ? JSON.stringify(job.uniqueness_config) : job.uniqueness_config, }; core.logger("Backend").debug(`Updating job: ${util.inspect(data)}`); const updatedJob = await this.knex.transaction(async (trx) => { await trx("sidequest_jobs").where({ id: job.id }).update(data); const updated = await trx("sidequest_jobs").where({ id: job.id }).first(); if (!updated) throw new Error("Cannot update job, not found."); return backend.safeParseJobData(updated); }); core.logger("Backend").debug(`Job updated successfully: ${util.inspect(updatedJob)}`); return updatedJob; } async listJobs(params) { const limit = params?.limit ?? 50; const offset = params?.offset ?? 0; const query = this.knex("sidequest_jobs").select("*").orderBy("id", "desc").limit(limit).offset(offset); if (params) { const { queue, jobClass, state, timeRange, args } = params; backend.whereOrWhereIn(query, "queue", queue); backend.whereOrWhereIn(query, "class", jobClass); backend.whereOrWhereIn(query, "state", state); if (args) query.whereRaw("JSON_CONTAINS(args, ?)", [JSON.stringify(args)]); if (timeRange?.from) query.andWhere("attempted_at", ">=", timeRange.from); if (timeRange?.to) query.andWhere("attempted_at", "<=", timeRange.to); } const rawJobs = (await query); return rawJobs.map(backend.safeParseJobData); } truncDate(date, unit) { let format; switch (unit) { case "m": format = "%Y-%m-%dT%H:%i:00.000"; // Truncate to minute break; case "h": format = "%Y-%m-%dT%H:00:00.000"; // Truncate to hour break; case "d": format = "%Y-%m-%dT00:00:00.000"; // Truncate to day break; } return this.knex.raw(`DATE_FORMAT(${date}, ?)`, [format]).toQuery(); } } exports.default = MysqlBackend; //# sourceMappingURL=mysql-backend.cjs.map