"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WorkerRecorderService = void 0;
function __assignType(fn, args) {
    fn.__type = args;
    return fn;
}
const orm_1 = require("@deepkit/orm");
const bullmq_1 = require("bullmq");
const resolver_1 = require("../../app/resolver");
const state_1 = require("../../app/state");
const database_1 = require("../../database");
const redis_1 = require("../../helpers/redis/redis");
const logger_1 = require("../logger");
const entity_1 = require("./entity");
const queue_1 = require("./queue");
const app_1 = require("../../app");
class WorkerRecorderService {
    constructor(dbProvider) {
        this.dbProvider = dbProvider;
        this.db = this.dbProvider.db;
        this.appConfig = (0, resolver_1.getAppConfig)();
        this.queueName = this.appConfig.BULL_QUEUE;
        this.logger = (0, logger_1.createLogger)(this, { queueName: this.queueName });
    }
    async ensureTableExists() {
        const dialect = (0, database_1.getDialect)(this.db.adapter);
        const tableInfoRows = await this.db.rawQuery((0, database_1.tableExistsSql)(dialect, '_jobs'));
        if (tableInfoRows.length)
            return;
        if (dialect === 'postgres') {
            await this.db.rawExecute(`
                CREATE TABLE "_jobs" (
                    "id" varchar(255) NOT NULL,
                    "queue" varchar(255) NOT NULL,
                    "queueId" varchar(255) NOT NULL,
                    "attempt" smallint NOT NULL,
                    "name" varchar(255) NOT NULL,
                    "data" jsonb DEFAULT NULL,
                    "traceId" char(32) DEFAULT NULL,
                    "status" varchar(20) NOT NULL CHECK ("status" IN ('completed','failed')),
                    "result" jsonb DEFAULT NULL,
                    "createdAt" timestamp NOT NULL,
                    "shouldExecuteAt" timestamp NOT NULL,
                    "executedAt" timestamp NOT NULL,
                    "completedAt" timestamp NOT NULL,
                    PRIMARY KEY ("id","attempt")
                );
            `);
        }
        else {
            await this.db.rawExecute(`
                CREATE TABLE \`_jobs\` (
                    \`id\` varchar(255) NOT NULL,
                    \`queue\` varchar(255) NOT NULL,
                    \`queueId\` varchar(255) NOT NULL,
                    \`attempt\` tinyint unsigned NOT NULL,
                    \`name\` varchar(255) NOT NULL,
                    \`data\` json DEFAULT NULL,
                    \`traceId\` char(32) DEFAULT NULL,
                    \`status\` enum('completed','failed') NOT NULL,
                    \`result\` json DEFAULT NULL,
                    \`createdAt\` datetime NOT NULL,
                    \`shouldExecuteAt\` datetime NOT NULL,
                    \`executedAt\` datetime NOT NULL,
                    \`completedAt\` datetime NOT NULL,
                    PRIMARY KEY (\`id\`,\`attempt\`)
                ) ENGINE=InnoDB;
            `);
        }
    }
    async start() {
        const { options, prefix } = (0, redis_1.createRedisOptions)('BULL');
        this.observer = new bullmq_1.QueueEvents(this.queueName, {
            connection: options,
            prefix: `${prefix}:bmq`
        });
        const queue = queue_1.WorkerQueueRegistry.getQueue(this.appConfig.BULL_QUEUE);
        this.observer.on('added', __assignType(args => {
            this.logger.info('Job added', {
                jobId: args.jobId,
                jobName: args.name
            });
        }, ['args', '', 'P"2!"/"']));
        this.observer.on('active', __assignType(args => {
            this.logger.info('Job activated', { jobId: args.jobId });
        }, ['args', '', 'P"2!"/"']));
        this.observer.on('stalled', __assignType(args => {
            this.logger.warn('Job stalled', { jobId: args.jobId });
        }, ['args', '', 'P"2!"/"']));
        this.observer.on('delayed', __assignType(args => {
            this.logger.info('Job delayed', { jobId: args.jobId, delay: args.delay });
        }, ['args', '', 'P"2!"/"']));
        this.observer.on('completed', __assignType(async (args) => {
            this.logger.info('Job completed', { jobId: args.jobId });
            const job = await queue.getJob(args.jobId);
            if (job) {
                await this.logJob(job, 'completed', args.returnvalue);
                await queue.remove(job.id);
            }
        }, ['args', '', 'P"2!"/"']));
        this.observer.on('failed', __assignType(async (args) => {
            this.logger.info('Job failed', { jobId: args.jobId });
            const job = await queue.getJob(args.jobId);
            if (job) {
                await this.logJob(job, 'failed', { reason: job.failedReason, stack: job.failedReason });
                await queue.remove(job.id);
            }
        }, ['args', '', 'P"2!"/"']));
        this.observer.on('error', __assignType(err => {
            this.logger.error('Recorder error:', err);
        }, ['err', '', 'P"2!"/"']));
        this.logger.info('Recorder started');
        const completedJobs = await queue.getCompleted();
        for (const job of completedJobs) {
            this.logger.info('Logging previously completed job', { jobId: job.id });
            await this.logJob(job, 'completed', job.returnvalue);
            await queue.remove(job.id);
        }
        const failedJobs = await queue.getFailed();
        for (const job of failedJobs) {
            this.logger.error('Logging previously failed job', { jobId: job.id });
            await this.logJob(job, 'failed', { reason: job.failedReason, stack: job.stacktrace });
            await queue.remove(job.id);
        }
    }
    async stop() {
        if (this.observer) {
            await this.observer.close();
            this.observer = undefined;
            this.logger.info('Recorder stopped');
        }
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    async logJob(job, status, result) {
        try {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            const traceparent = job.opts.traceparent;
            const traceId = traceparent ? traceparent.split('-')[1] : null;
            await (0, database_1.createPersistedEntity)(entity_1.JobEntity, {
                id: `${this.queueName}:${job.id}`,
                queue: this.queueName,
                queueId: job.id,
                attempt: job.attemptsMade,
                name: job.name,
                data: job.data,
                traceId,
                status,
                result,
                createdAt: new Date(job.timestamp),
                shouldExecuteAt: new Date(job.timestamp + (job.opts.delay ?? 0)),
                executedAt: new Date(job.processedOn),
                completedAt: new Date(job.finishedOn)
            });
        }
        catch (err) {
            if (err instanceof orm_1.UniqueConstraintFailure) {
                this.logger.warn('Job already logged', { jobId: job.id });
            }
            else {
                throw err;
            }
        }
    }
}
exports.WorkerRecorderService = WorkerRecorderService;
WorkerRecorderService.__type = [() => app_1.BaseAppConfig, 'appConfig', 'queueName', () => logger_1.ExtendedLogger, 'logger', () => bullmq_1.QueueEvents, 'observer', 'db', function () { return this.dbProvider.db; }, () => state_1.DBProvider, 'dbProvider', 'constructor', 'ensureTableExists', 'start', 'stop', () => bullmq_1.Job, 'job', "completed", "failed", 'status', 'result', 'logJob', 'WorkerRecorderService', 'P7!3";&3#;P7$3%;P7&3\'8;!3(;>)PP7*2+;"0,P"0-P"0.P"0/PP7021P.2.3J24"25"065w7'];
//# sourceMappingURL=recorder.js.map