"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.StorageKnex = void 0;
const tables_1 = require("./schema/tables");
const KnexMigrations_1 = require("./schema/KnexMigrations");
const StorageProvider_1 = require("./StorageProvider");
const purgeData_1 = require("./methods/purgeData");
const listActionsKnex_1 = require("./methods/listActionsKnex");
const listOutputsKnex_1 = require("./methods/listOutputsKnex");
const reviewStatus_1 = require("./methods/reviewStatus");
const WERR_errors_1 = require("../sdk/WERR_errors");
const utilityHelpers_1 = require("../utility/utilityHelpers");
class StorageKnex extends StorageProvider_1.StorageProvider {
    constructor(options) {
        super(options);
        this._verifiedReadyForDatabaseAccess = false;
        if (!options.knex)
            throw new WERR_errors_1.WERR_INVALID_PARAMETER('options.knex', `valid`);
        this.knex = options.knex;
    }
    async readSettings() {
        return this.validateEntity((0, utilityHelpers_1.verifyOne)(await this.toDb(undefined)('settings')));
    }
    async getProvenOrRawTx(txid, trx) {
        const k = this.toDb(trx);
        const r = {
            proven: undefined,
            rawTx: undefined,
            inputBEEF: undefined
        };
        r.proven = (0, utilityHelpers_1.verifyOneOrNone)(await this.findProvenTxs({ partial: { txid: txid } }));
        if (!r.proven) {
            const reqRawTx = (0, utilityHelpers_1.verifyOneOrNone)(await k('proven_tx_reqs')
                .where('txid', txid)
                .whereIn('status', ['unsent', 'unmined', 'unconfirmed', 'sending', 'nosend', 'completed'])
                .select('rawTx', 'inputBEEF'));
            if (reqRawTx) {
                r.rawTx = Array.from(reqRawTx.rawTx);
                r.inputBEEF = Array.from(reqRawTx.inputBEEF);
            }
        }
        return r;
    }
    dbTypeSubstring(source, fromOffset, forLength) {
        if (this.dbtype === 'MySQL')
            return `substring(${source} from ${fromOffset} for ${forLength})`;
        return `substr(${source}, ${fromOffset}, ${forLength})`;
    }
    async getRawTxOfKnownValidTransaction(txid, offset, length, trx) {
        if (!txid)
            return undefined;
        if (!this.isAvailable())
            await this.makeAvailable();
        let rawTx = undefined;
        if (Number.isInteger(offset) && Number.isInteger(length)) {
            let rs = await this.toDb(trx).raw(`select ${this.dbTypeSubstring('rawTx', offset + 1, length)} as rawTx from proven_txs where txid = '${txid}'`);
            if (this.dbtype === 'MySQL')
                rs = rs[0];
            const r = (0, utilityHelpers_1.verifyOneOrNone)(rs);
            if (r && r.rawTx) {
                rawTx = Array.from(r.rawTx);
            }
            else {
                let rs = await this.toDb(trx).raw(`select ${this.dbTypeSubstring('rawTx', offset + 1, length)} as rawTx from proven_tx_reqs where txid = '${txid}' and status in ('unsent', 'nosend', 'sending', 'unmined', 'completed', 'unfail')`);
                if (this.dbtype === 'MySQL')
                    rs = rs[0];
                const r = (0, utilityHelpers_1.verifyOneOrNone)(rs);
                if (r && r.rawTx) {
                    rawTx = Array.from(r.rawTx);
                }
            }
        }
        else {
            const r = await this.getProvenOrRawTx(txid, trx);
            if (r.proven)
                rawTx = r.proven.rawTx;
            else
                rawTx = r.rawTx;
        }
        return rawTx;
    }
    getProvenTxsForUserQuery(args) {
        const k = this.toDb(args.trx);
        let q = k('proven_txs').where(function () {
            this.whereExists(k
                .select('*')
                .from('transactions')
                .whereRaw(`proven_txs.provenTxId = transactions.provenTxId and transactions.userId = ${args.userId}`));
        });
        if (args.paged) {
            q = q.limit(args.paged.limit);
            q = q.offset(args.paged.offset || 0);
        }
        if (args.since)
            q = q.where('updated_at', '>=', this.validateDateForWhere(args.since));
        return q;
    }
    async getProvenTxsForUser(args) {
        const q = this.getProvenTxsForUserQuery(args);
        const rs = await q;
        return this.validateEntities(rs);
    }
    getProvenTxReqsForUserQuery(args) {
        const k = this.toDb(args.trx);
        let q = k('proven_tx_reqs').where(function () {
            this.whereExists(k
                .select('*')
                .from('transactions')
                .whereRaw(`proven_tx_reqs.txid = transactions.txid and transactions.userId = ${args.userId}`));
        });
        if (args.paged) {
            q = q.limit(args.paged.limit);
            q = q.offset(args.paged.offset || 0);
        }
        if (args.since)
            q = q.where('updated_at', '>=', this.validateDateForWhere(args.since));
        return q;
    }
    async getProvenTxReqsForUser(args) {
        const q = this.getProvenTxReqsForUserQuery(args);
        const rs = await q;
        return this.validateEntities(rs, undefined, ['notified']);
    }
    getTxLabelMapsForUserQuery(args) {
        const k = this.toDb(args.trx);
        let q = k('tx_labels_map').whereExists(k
            .select('*')
            .from('tx_labels')
            .whereRaw(`tx_labels.txLabelId = tx_labels_map.txLabelId and tx_labels.userId = ${args.userId}`));
        if (args.since)
            q = q.where('updated_at', '>=', this.validateDateForWhere(args.since));
        if (args.paged) {
            q = q.limit(args.paged.limit);
            q = q.offset(args.paged.offset || 0);
        }
        return q;
    }
    async getTxLabelMapsForUser(args) {
        const q = this.getTxLabelMapsForUserQuery(args);
        const rs = await q;
        return this.validateEntities(rs, undefined, ['isDeleted']);
    }
    getOutputTagMapsForUserQuery(args) {
        const k = this.toDb(args.trx);
        let q = k('output_tags_map').whereExists(k
            .select('*')
            .from('output_tags')
            .whereRaw(`output_tags.outputTagId = output_tags_map.outputTagId and output_tags.userId = ${args.userId}`));
        if (args.since)
            q = q.where('updated_at', '>=', this.validateDateForWhere(args.since));
        if (args.paged) {
            q = q.limit(args.paged.limit);
            q = q.offset(args.paged.offset || 0);
        }
        return q;
    }
    async getOutputTagMapsForUser(args) {
        const q = this.getOutputTagMapsForUserQuery(args);
        const rs = await q;
        return this.validateEntities(rs, undefined, ['isDeleted']);
    }
    async listActions(auth, vargs) {
        if (!auth.userId)
            throw new WERR_errors_1.WERR_UNAUTHORIZED();
        return await (0, listActionsKnex_1.listActions)(this, auth, vargs);
    }
    async listOutputs(auth, vargs) {
        if (!auth.userId)
            throw new WERR_errors_1.WERR_UNAUTHORIZED();
        return await (0, listOutputsKnex_1.listOutputs)(this, auth, vargs);
    }
    async insertProvenTx(tx, trx) {
        const e = await this.validateEntityForInsert(tx, trx);
        if (e.provenTxId === 0)
            delete e.provenTxId;
        const [id] = await this.toDb(trx)('proven_txs').insert(e);
        tx.provenTxId = id;
        return tx.provenTxId;
    }
    async insertProvenTxReq(tx, trx) {
        const e = await this.validateEntityForInsert(tx, trx);
        if (e.provenTxReqId === 0)
            delete e.provenTxReqId;
        const [id] = await this.toDb(trx)('proven_tx_reqs').insert(e);
        tx.provenTxReqId = id;
        return tx.provenTxReqId;
    }
    async insertUser(user, trx) {
        const e = await this.validateEntityForInsert(user, trx);
        if (e.userId === 0)
            delete e.userId;
        const [id] = await this.toDb(trx)('users').insert(e);
        user.userId = id;
        return user.userId;
    }
    async insertCertificateAuth(auth, certificate) {
        if (!auth.userId || (certificate.userId && certificate.userId !== auth.userId))
            throw new WERR_errors_1.WERR_UNAUTHORIZED();
        certificate.userId = auth.userId;
        return await this.insertCertificate(certificate);
    }
    async insertCertificate(certificate, trx) {
        const e = await this.validateEntityForInsert(certificate, trx, undefined, ['isDeleted']);
        if (e.certificateId === 0)
            delete e.certificateId;
        const logger = e.logger;
        if (e.logger)
            delete e.logger;
        const fields = e.fields;
        if (e.fields)
            delete e.fields;
        const [id] = await this.toDb(trx)('certificates').insert(e);
        certificate.certificateId = id;
        if (fields) {
            for (const field of fields) {
                field.certificateId = id;
                field.userId = certificate.userId;
                await this.insertCertificateField(field, trx);
            }
        }
        return certificate.certificateId;
    }
    async insertCertificateField(certificateField, trx) {
        const e = await this.validateEntityForInsert(certificateField, trx);
        await this.toDb(trx)('certificate_fields').insert(e);
    }
    async insertOutputBasket(basket, trx) {
        const e = await this.validateEntityForInsert(basket, trx, undefined, ['isDeleted']);
        if (e.basketId === 0)
            delete e.basketId;
        const [id] = await this.toDb(trx)('output_baskets').insert(e);
        basket.basketId = id;
        return basket.basketId;
    }
    async insertTransaction(tx, trx) {
        const e = await this.validateEntityForInsert(tx, trx);
        if (e.transactionId === 0)
            delete e.transactionId;
        const [id] = await this.toDb(trx)('transactions').insert(e);
        tx.transactionId = id;
        return tx.transactionId;
    }
    async insertCommission(commission, trx) {
        const e = await this.validateEntityForInsert(commission, trx);
        if (e.commissionId === 0)
            delete e.commissionId;
        const [id] = await this.toDb(trx)('commissions').insert(e);
        commission.commissionId = id;
        return commission.commissionId;
    }
    async insertOutput(output, trx) {
        try {
            const e = await this.validateEntityForInsert(output, trx);
            if (e.outputId === 0)
                delete e.outputId;
            const [id] = await this.toDb(trx)('outputs').insert(e);
            output.outputId = id;
            return output.outputId;
        }
        catch (e) {
            throw e;
        }
    }
    async insertOutputTag(tag, trx) {
        const e = await this.validateEntityForInsert(tag, trx, undefined, ['isDeleted']);
        if (e.outputTagId === 0)
            delete e.outputTagId;
        const [id] = await this.toDb(trx)('output_tags').insert(e);
        tag.outputTagId = id;
        return tag.outputTagId;
    }
    async insertOutputTagMap(tagMap, trx) {
        const e = await this.validateEntityForInsert(tagMap, trx, undefined, ['isDeleted']);
        const [id] = await this.toDb(trx)('output_tags_map').insert(e);
    }
    async insertTxLabel(label, trx) {
        const e = await this.validateEntityForInsert(label, trx, undefined, ['isDeleted']);
        if (e.txLabelId === 0)
            delete e.txLabelId;
        const [id] = await this.toDb(trx)('tx_labels').insert(e);
        label.txLabelId = id;
        return label.txLabelId;
    }
    async insertTxLabelMap(labelMap, trx) {
        const e = await this.validateEntityForInsert(labelMap, trx, undefined, ['isDeleted']);
        const [id] = await this.toDb(trx)('tx_labels_map').insert(e);
    }
    async insertMonitorEvent(event, trx) {
        const e = await this.validateEntityForInsert(event, trx);
        if (e.id === 0)
            delete e.id;
        const [id] = await this.toDb(trx)('monitor_events').insert(e);
        event.id = id;
        return event.id;
    }
    async insertSyncState(syncState, trx) {
        const e = await this.validateEntityForInsert(syncState, trx, ['when'], ['init']);
        if (e.syncStateId === 0)
            delete e.syncStateId;
        const [id] = await this.toDb(trx)('sync_states').insert(e);
        syncState.syncStateId = id;
        return syncState.syncStateId;
    }
    async updateCertificateField(certificateId, fieldName, update, trx) {
        await this.verifyReadyForDatabaseAccess(trx);
        return await this.toDb(trx)('certificate_fields')
            .where({ certificateId, fieldName })
            .update(this.validatePartialForUpdate(update));
    }
    async updateCertificate(id, update, trx) {
        await this.verifyReadyForDatabaseAccess(trx);
        return await this.toDb(trx)('certificates')
            .where({ certificateId: id })
            .update(this.validatePartialForUpdate(update, undefined, ['isDeleted']));
    }
    async updateCommission(id, update, trx) {
        await this.verifyReadyForDatabaseAccess(trx);
        return await this.toDb(trx)('commissions')
            .where({ commissionId: id })
            .update(this.validatePartialForUpdate(update));
    }
    async updateOutputBasket(id, update, trx) {
        await this.verifyReadyForDatabaseAccess(trx);
        return await this.toDb(trx)('output_baskets')
            .where({ basketId: id })
            .update(this.validatePartialForUpdate(update, undefined, ['isDeleted']));
    }
    async updateOutput(id, update, trx) {
        await this.verifyReadyForDatabaseAccess(trx);
        return await this.toDb(trx)('outputs')
            .where({ outputId: id })
            .update(this.validatePartialForUpdate(update));
    }
    async updateOutputTagMap(outputId, tagId, update, trx) {
        await this.verifyReadyForDatabaseAccess(trx);
        return await this.toDb(trx)('output_tags_map')
            .where({ outputId, outputTagId: tagId })
            .update(this.validatePartialForUpdate(update, undefined, ['isDeleted']));
    }
    async updateOutputTag(id, update, trx) {
        await this.verifyReadyForDatabaseAccess(trx);
        return await this.toDb(trx)('output_tags')
            .where({ outputTagId: id })
            .update(this.validatePartialForUpdate(update, undefined, ['isDeleted']));
    }
    async updateProvenTxReq(id, update, trx) {
        await this.verifyReadyForDatabaseAccess(trx);
        let r;
        if (Array.isArray(id)) {
            r = await this.toDb(trx)('proven_tx_reqs')
                .whereIn('provenTxReqId', id)
                .update(this.validatePartialForUpdate(update));
        }
        else if (Number.isInteger(id)) {
            r = await this.toDb(trx)('proven_tx_reqs')
                .where({ provenTxReqId: id })
                .update(this.validatePartialForUpdate(update));
        }
        else {
            throw new WERR_errors_1.WERR_INVALID_PARAMETER('id', 'transactionId or array of transactionId');
        }
        return r;
    }
    async updateProvenTx(id, update, trx) {
        await this.verifyReadyForDatabaseAccess(trx);
        return await this.toDb(trx)('proven_txs')
            .where({ provenTxId: id })
            .update(this.validatePartialForUpdate(update));
    }
    async updateSyncState(id, update, trx) {
        await this.verifyReadyForDatabaseAccess(trx);
        return await this.toDb(trx)('sync_states')
            .where({ syncStateId: id })
            .update(this.validatePartialForUpdate(update, ['when'], ['init']));
    }
    async updateTransaction(id, update, trx) {
        await this.verifyReadyForDatabaseAccess(trx);
        let r;
        if (Array.isArray(id)) {
            r = await this.toDb(trx)('transactions')
                .whereIn('transactionId', id)
                .update(await this.validatePartialForUpdate(update));
        }
        else if (Number.isInteger(id)) {
            r = await this.toDb(trx)('transactions')
                .where({ transactionId: id })
                .update(await this.validatePartialForUpdate(update));
        }
        else {
            throw new WERR_errors_1.WERR_INVALID_PARAMETER('id', 'transactionId or array of transactionId');
        }
        return r;
    }
    async updateTxLabelMap(transactionId, txLabelId, update, trx) {
        await this.verifyReadyForDatabaseAccess(trx);
        return await this.toDb(trx)('tx_labels_map')
            .where({ transactionId, txLabelId })
            .update(this.validatePartialForUpdate(update, undefined, ['isDeleted']));
    }
    async updateTxLabel(id, update, trx) {
        await this.verifyReadyForDatabaseAccess(trx);
        return await this.toDb(trx)('tx_labels')
            .where({ txLabelId: id })
            .update(this.validatePartialForUpdate(update, undefined, ['isDeleted']));
    }
    async updateUser(id, update, trx) {
        await this.verifyReadyForDatabaseAccess(trx);
        return await this.toDb(trx)('users').where({ userId: id }).update(this.validatePartialForUpdate(update));
    }
    async updateMonitorEvent(id, update, trx) {
        await this.verifyReadyForDatabaseAccess(trx);
        return await this.toDb(trx)('monitor_events')
            .where({ id })
            .update(this.validatePartialForUpdate(update));
    }
    setupQuery(table, args) {
        let q = this.toDb(args.trx)(table);
        if (args.partial && Object.keys(args.partial).length > 0)
            q.where(args.partial);
        if (args.since)
            q.where('updated_at', '>=', this.validateDateForWhere(args.since));
        if (args.orderDescending) {
            let sortColumn = '';
            switch (table) {
                case 'certificates':
                    sortColumn = 'certificateId';
                    break;
                case 'commissions':
                    sortColumn = 'commissionId';
                    break;
                case 'output_baskets':
                    sortColumn = 'basketId';
                    break;
                case 'outputs':
                    sortColumn = 'outputId';
                    break;
                case 'output_tags':
                    sortColumn = 'outputTagId';
                    break;
                case 'proven_tx_reqs':
                    sortColumn = 'provenTxReqId';
                    break;
                case 'proven_txs':
                    sortColumn = 'provenTxId';
                    break;
                case 'sync_states':
                    sortColumn = 'syncStateId';
                    break;
                case 'transactions':
                    sortColumn = 'transactionId';
                    break;
                case 'tx_labels':
                    sortColumn = 'txLabelId';
                    break;
                case 'users':
                    sortColumn = 'userId';
                    break;
                case 'monitor_events':
                    sortColumn = 'id';
                    break;
                default:
                    break;
            }
            if (sortColumn !== '') {
                q.orderBy(sortColumn, 'desc');
            }
        }
        if (args.paged) {
            q.limit(args.paged.limit);
            q.offset(args.paged.offset || 0);
        }
        return q;
    }
    findCertificateFieldsQuery(args) {
        return this.setupQuery('certificate_fields', args);
    }
    findCertificatesQuery(args) {
        const q = this.setupQuery('certificates', args);
        if (args.certifiers && args.certifiers.length > 0)
            q.whereIn('certifier', args.certifiers);
        if (args.types && args.types.length > 0)
            q.whereIn('type', args.types);
        return q;
    }
    findCommissionsQuery(args) {
        if (args.partial.lockingScript)
            throw new WERR_errors_1.WERR_INVALID_PARAMETER('partial.lockingScript', `undefined. Commissions may not be found by lockingScript value.`);
        return this.setupQuery('commissions', args);
    }
    findOutputBasketsQuery(args) {
        return this.setupQuery('output_baskets', args);
    }
    findOutputsQuery(args, count) {
        if (args.partial.lockingScript)
            throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.lockingScript', `undefined. Outputs may not be found by lockingScript value.`);
        const q = this.setupQuery('outputs', args);
        if (args.txStatus && args.txStatus.length > 0) {
            q.whereRaw(`(select status from transactions where transactions.transactionId = outputs.transactionId) in (${args.txStatus.map(s => `'${s}'`).join(',')})`);
        }
        if (args.noScript && !count) {
            const columns = tables_1.outputColumnsWithoutLockingScript.map(c => `outputs.${c}`);
            q.select(columns);
        }
        return q;
    }
    findOutputTagMapsQuery(args) {
        const q = this.setupQuery('output_tags_map', args);
        if (args.tagIds && args.tagIds.length > 0)
            q.whereIn('outputTagId', args.tagIds);
        return q;
    }
    findOutputTagsQuery(args) {
        return this.setupQuery('output_tags', args);
    }
    findProvenTxReqsQuery(args) {
        if (args.partial.rawTx)
            throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.rawTx', `undefined. ProvenTxReqs may not be found by rawTx value.`);
        if (args.partial.inputBEEF)
            throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.inputBEEF', `undefined. ProvenTxReqs may not be found by inputBEEF value.`);
        const q = this.setupQuery('proven_tx_reqs', args);
        if (args.status && args.status.length > 0)
            q.whereIn('status', args.status);
        if (args.txids) {
            const txids = args.txids.filter(txid => txid !== undefined);
            if (txids.length > 0)
                q.whereIn('txid', txids);
        }
        return q;
    }
    findProvenTxsQuery(args) {
        if (args.partial.rawTx)
            throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.rawTx', `undefined. ProvenTxs may not be found by rawTx value.`);
        if (args.partial.merklePath)
            throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.merklePath', `undefined. ProvenTxs may not be found by merklePath value.`);
        return this.setupQuery('proven_txs', args);
    }
    findStaleMerkleRootsQuery(args) {
        let q = this.toDb(args.trx)('proven_txs');
        q.where('height', '=', args.height);
        q.where('merkleRoot', '!=', args.merkleRoot);
        q.select('merkleRoot');
        q.distinct('merkleRoot');
        return q;
    }
    findSyncStatesQuery(args) {
        return this.setupQuery('sync_states', args);
    }
    findTransactionsQuery(args, count) {
        if (args.partial.rawTx)
            throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.rawTx', `undefined. Transactions may not be found by rawTx value.`);
        if (args.partial.inputBEEF)
            throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.inputBEEF', `undefined. Transactions may not be found by inputBEEF value.`);
        const q = this.setupQuery('transactions', args);
        if (args.status && args.status.length > 0)
            q.whereIn('status', args.status);
        if (args.from)
            q.where('created_at', '>=', this.validateDateForWhere(args.from));
        if (args.to)
            q.where('created_at', '<', this.validateDateForWhere(args.to));
        if (args.noRawTx && !count) {
            const columns = tables_1.transactionColumnsWithoutRawTx.map(c => `transactions.${c}`);
            q.select(columns);
        }
        return q;
    }
    findTxLabelMapsQuery(args) {
        const q = this.setupQuery('tx_labels_map', args);
        if (args.labelIds && args.labelIds.length > 0)
            q.whereIn('txLabelId', args.labelIds);
        return q;
    }
    findTxLabelsQuery(args) {
        return this.setupQuery('tx_labels', args);
    }
    findUsersQuery(args) {
        return this.setupQuery('users', args);
    }
    findMonitorEventsQuery(args) {
        return this.setupQuery('monitor_events', args);
    }
    async findCertificatesAuth(auth, args) {
        if (!auth.userId || (args.partial.userId && args.partial.userId !== auth.userId))
            throw new WERR_errors_1.WERR_UNAUTHORIZED();
        args.partial.userId = auth.userId;
        return await this.findCertificates(args);
    }
    async findOutputBasketsAuth(auth, args) {
        if (!auth.userId || (args.partial.userId && args.partial.userId !== auth.userId))
            throw new WERR_errors_1.WERR_UNAUTHORIZED();
        args.partial.userId = auth.userId;
        return await this.findOutputBaskets(args);
    }
    async findOutputsAuth(auth, args) {
        if (!auth.userId || (args.partial.userId && args.partial.userId !== auth.userId))
            throw new WERR_errors_1.WERR_UNAUTHORIZED();
        args.partial.userId = auth.userId;
        return await this.findOutputs(args);
    }
    async findCertificateFields(args) {
        return this.validateEntities(await this.findCertificateFieldsQuery(args));
    }
    async findCertificates(args) {
        const q = this.findCertificatesQuery(args);
        let r = await q;
        r = this.validateEntities(r, undefined, ['isDeleted']);
        if (args.includeFields) {
            for (const c of r) {
                c.fields = this.validateEntities(await this.findCertificateFields({
                    partial: { certificateId: c.certificateId, userId: c.userId },
                    trx: args.trx
                }));
            }
        }
        return r;
    }
    async findCommissions(args) {
        const q = this.findCommissionsQuery(args);
        const r = await q;
        return this.validateEntities(r, undefined, ['isRedeemed']);
    }
    async findOutputBaskets(args) {
        const q = this.findOutputBasketsQuery(args);
        const r = await q;
        return this.validateEntities(r, undefined, ['isDeleted']);
    }
    async findOutputs(args) {
        const q = this.findOutputsQuery(args);
        const r = await q;
        if (!args.noScript) {
            for (const o of r) {
                await this.validateOutputScript(o, args.trx);
            }
        }
        return this.validateEntities(r, undefined, ['spendable', 'change']);
    }
    async findOutputTagMaps(args) {
        const q = this.findOutputTagMapsQuery(args);
        const r = await q;
        return this.validateEntities(r, undefined, ['isDeleted']);
    }
    async findOutputTags(args) {
        const q = this.findOutputTagsQuery(args);
        const r = await q;
        return this.validateEntities(r, undefined, ['isDeleted']);
    }
    async findProvenTxReqs(args) {
        const q = this.findProvenTxReqsQuery(args);
        const r = await q;
        return this.validateEntities(r, undefined, ['notified']);
    }
    async findProvenTxs(args) {
        const q = this.findProvenTxsQuery(args);
        const r = await q;
        return this.validateEntities(r);
    }
    async findStaleMerkleRoots(args) {
        const q = this.findStaleMerkleRootsQuery(args);
        const r = await q;
        return r.map((row) => row.merkleRoot);
    }
    async findSyncStates(args) {
        const q = this.findSyncStatesQuery(args);
        const r = await q;
        return this.validateEntities(r, ['when'], ['init']);
    }
    async findTransactions(args) {
        const q = this.findTransactionsQuery(args);
        const r = await q;
        if (!args.noRawTx) {
            for (const t of r) {
                await this.validateRawTransaction(t, args.trx);
            }
        }
        return this.validateEntities(r, undefined, ['isOutgoing']);
    }
    async findTxLabelMaps(args) {
        const q = this.findTxLabelMapsQuery(args);
        const r = await q;
        return this.validateEntities(r, undefined, ['isDeleted']);
    }
    async findTxLabels(args) {
        const q = this.findTxLabelsQuery(args);
        const r = await q;
        return this.validateEntities(r, undefined, ['isDeleted']);
    }
    async findUsers(args) {
        const q = this.findUsersQuery(args);
        const r = await q;
        return this.validateEntities(r);
    }
    async findMonitorEvents(args) {
        const q = this.findMonitorEventsQuery(args);
        const r = await q;
        return this.validateEntities(r, ['when'], undefined);
    }
    async getCount(q) {
        q.count();
        const r = await q;
        return r[0]['count(*)'];
    }
    async countCertificateFields(args) {
        return await this.getCount(this.findCertificateFieldsQuery(args));
    }
    async countCertificates(args) {
        return await this.getCount(this.findCertificatesQuery(args));
    }
    async countCommissions(args) {
        return await this.getCount(this.findCommissionsQuery(args));
    }
    async countOutputBaskets(args) {
        return await this.getCount(this.findOutputBasketsQuery(args));
    }
    async countOutputs(args) {
        return await this.getCount(this.findOutputsQuery(args, true));
    }
    async countOutputTagMaps(args) {
        return await this.getCount(this.findOutputTagMapsQuery(args));
    }
    async countOutputTags(args) {
        return await this.getCount(this.findOutputTagsQuery(args));
    }
    async countProvenTxReqs(args) {
        return await this.getCount(this.findProvenTxReqsQuery(args));
    }
    async countProvenTxs(args) {
        return await this.getCount(this.findProvenTxsQuery(args));
    }
    async countSyncStates(args) {
        return await this.getCount(this.findSyncStatesQuery(args));
    }
    async countTransactions(args) {
        return await this.getCount(this.findTransactionsQuery(args, true));
    }
    async countTxLabelMaps(args) {
        return await this.getCount(this.findTxLabelMapsQuery(args));
    }
    async countTxLabels(args) {
        return await this.getCount(this.findTxLabelsQuery(args));
    }
    async countUsers(args) {
        return await this.getCount(this.findUsersQuery(args));
    }
    async countMonitorEvents(args) {
        return await this.getCount(this.findMonitorEventsQuery(args));
    }
    async destroy() {
        var _a;
        await ((_a = this.knex) === null || _a === void 0 ? void 0 : _a.destroy());
    }
    async migrate(storageName, storageIdentityKey) {
        var _a;
        // Check if this is a SQLite database by looking at the Knex client config
        const clientName = ((_a = this.knex.client.config) === null || _a === void 0 ? void 0 : _a.client) || '';
        const isSQLite = clientName.includes('sqlite');
        // For SQLite, disable transactions during migrations and turn off foreign keys.
        // PRAGMA foreign_keys is silently ignored inside transactions, so we must
        // disable transactions for the migration to allow the PRAGMA to take effect.
        // See: https://github.com/knex/knex/issues/4155
        if (isSQLite) {
            await this.knex.raw('PRAGMA foreign_keys = OFF;');
        }
        const config = {
            migrationSource: new KnexMigrations_1.KnexMigrations(this.chain, storageName, storageIdentityKey, 1024),
            disableTransactions: isSQLite
        };
        await this.knex.migrate.latest(config);
        const version = await this.knex.migrate.currentVersion(config);
        // Re-enable foreign key checks for SQLite
        if (isSQLite) {
            await this.knex.raw('PRAGMA foreign_keys = ON;');
        }
        return version;
    }
    async dropAllData() {
        var _a;
        // Only using migrations to migrate down, don't need valid properties for settings table.
        const migrationSource = new KnexMigrations_1.KnexMigrations('test', '', '', 1024);
        // Check if this is a SQLite database by looking at the Knex client config
        const clientName = ((_a = this.knex.client.config) === null || _a === void 0 ? void 0 : _a.client) || '';
        const isSQLite = clientName.includes('sqlite');
        // For SQLite, disable transactions during migrations and turn off foreign keys.
        // PRAGMA foreign_keys is silently ignored inside transactions, so we must
        // disable transactions for the migration to allow the PRAGMA to take effect.
        // See: https://github.com/knex/knex/issues/4155
        const config = {
            migrationSource,
            disableTransactions: isSQLite
        };
        const count = Object.keys(migrationSource.migrations).length;
        // Disable foreign key checks for SQLite before dropping tables
        // This is necessary for better-sqlite3 which enforces FK constraints by default
        if (isSQLite) {
            await this.knex.raw('PRAGMA foreign_keys = OFF;');
        }
        for (let i = 0; i < count; i++) {
            try {
                const r = await this.knex.migrate.down(config);
                if (!r) {
                    console.error(`Migration returned falsy result await this.knex.migrate.down(config)`);
                    break;
                }
            }
            catch (eu) {
                break;
            }
        }
        // Re-enable foreign key checks for SQLite
        if (isSQLite) {
            await this.knex.raw('PRAGMA foreign_keys = ON;');
        }
    }
    async transaction(scope, trx) {
        if (trx)
            return await scope(trx);
        return await this.knex.transaction(async (knextrx) => {
            const trx = knextrx;
            return await scope(trx);
        });
    }
    /**
     * Convert the standard optional `TrxToken` parameter into either a direct knex database instance,
     * or a Knex.Transaction as appropriate.
     */
    toDb(trx) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const db = !trx ? this.knex : trx;
        this.whenLastAccess = new Date();
        return db;
    }
    async validateRawTransaction(t, trx) {
        // if there is no txid or there is a rawTransaction return what we have.
        if (t.rawTx || !t.txid)
            return;
        // rawTransaction is missing, see if we moved it ...
        const rawTx = await this.getRawTxOfKnownValidTransaction(t.txid, undefined, undefined, trx);
        if (!rawTx)
            return;
        t.rawTx = rawTx;
    }
    /**
     * Make sure database is ready for access:
     *
     * - dateScheme is known
     * - foreign key constraints are enabled
     *
     * @param trx
     */
    async verifyReadyForDatabaseAccess(trx) {
        if (!this._settings) {
            this._settings = await this.readSettings();
        }
        // Always run the PRAGMA for SQLite to ensure foreign key constraints are enabled.
        // This is necessary because PRAGMA foreign_keys is a per-connection setting,
        // and connection pools may create new connections that don't have it set.
        // The performance impact is minimal as SQLite handles this efficiently.
        if (this._settings.dbtype === 'SQLite') {
            await this.toDb(trx).raw('PRAGMA foreign_keys = ON;');
        }
        this._verifiedReadyForDatabaseAccess = true;
        return this._settings.dbtype;
    }
    /**
     * Helper to force uniform behavior across database engines.
     * Use to process the update template for entities being updated.
     */
    validatePartialForUpdate(update, dateFields, booleanFields) {
        if (!this.dbtype)
            throw new WERR_errors_1.WERR_INTERNAL('must call verifyReadyForDatabaseAccess first');
        const v = update;
        if (v.created_at)
            v.created_at = this.validateEntityDate(v.created_at);
        if (v.updated_at)
            v.updated_at = this.validateEntityDate(v.updated_at);
        if (!v.created_at)
            delete v.created_at;
        if (!v.updated_at)
            v.updated_at = this.validateEntityDate(new Date());
        if (dateFields) {
            for (const df of dateFields) {
                if (v[df])
                    v[df] = this.validateOptionalEntityDate(v[df]);
            }
        }
        if (booleanFields) {
            for (const df of booleanFields) {
                if (update[df] !== undefined)
                    update[df] = !!update[df] ? 1 : 0;
            }
        }
        for (const key of Object.keys(v)) {
            const val = v[key];
            if (Array.isArray(val) && (val.length === 0 || typeof val[0] === 'number')) {
                v[key] = Buffer.from(val);
            }
            else if (val === undefined) {
                v[key] = null;
            }
        }
        this.isDirty = true;
        return v;
    }
    /**
     * Helper to force uniform behavior across database engines.
     * Use to process new entities being inserted into the database.
     */
    async validateEntityForInsert(entity, trx, dateFields, booleanFields) {
        await this.verifyReadyForDatabaseAccess(trx);
        const v = { ...entity };
        v.created_at = this.validateOptionalEntityDate(v.created_at, true);
        v.updated_at = this.validateOptionalEntityDate(v.updated_at, true);
        if (!v.created_at)
            delete v.created_at;
        if (!v.updated_at)
            delete v.updated_at;
        if (dateFields) {
            for (const df of dateFields) {
                if (v[df])
                    v[df] = this.validateOptionalEntityDate(v[df]);
            }
        }
        if (booleanFields) {
            for (const df of booleanFields) {
                if (entity[df] !== undefined)
                    entity[df] = !!entity[df] ? 1 : 0;
            }
        }
        for (const key of Object.keys(v)) {
            const val = v[key];
            if (Array.isArray(val) && (val.length === 0 || typeof val[0] === 'number')) {
                v[key] = Buffer.from(val);
            }
            else if (val === undefined) {
                v[key] = null;
            }
        }
        this.isDirty = true;
        return v;
    }
    async getLabelsForTransactionId(transactionId, trx) {
        if (transactionId === undefined)
            return [];
        const labels = await this.toDb(trx)('tx_labels')
            .join('tx_labels_map', 'tx_labels_map.txLabelId', 'tx_labels.txLabelId')
            .where('tx_labels_map.transactionId', transactionId)
            .whereNot('tx_labels_map.isDeleted', true)
            .whereNot('tx_labels.isDeleted', true);
        return this.validateEntities(labels, undefined, ['isDeleted']);
    }
    async getTagsForOutputId(outputId, trx) {
        const tags = await this.toDb(trx)('output_tags')
            .join('output_tags_map', 'output_tags_map.outputTagId', 'output_tags.outputTagId')
            .where('output_tags_map.outputId', outputId)
            .whereNot('output_tags_map.isDeleted', true)
            .whereNot('output_tags.isDeleted', true);
        return this.validateEntities(tags, undefined, ['isDeleted']);
    }
    async purgeData(params, trx) {
        return await (0, purgeData_1.purgeData)(this, params, trx);
    }
    async reviewStatus(args) {
        return await (0, reviewStatus_1.reviewStatus)(this, args);
    }
    /**
     * Counts the outputs for userId in basketId that are spendable: true
     * AND whose transaction status is one of:
     * - completed
     * - unproven
     * - sending (if excludeSending is false)
     */
    async countChangeInputs(userId, basketId, excludeSending) {
        const status = ['completed', 'unproven'];
        if (!excludeSending)
            status.push('sending');
        const q = this.knex('outputs as o')
            .join('transactions as t', 'o.transactionId', 't.transactionId')
            .where({ 'o.userId': userId, 'o.spendable': true, 'o.basketId': basketId })
            .whereIn('t.status', status);
        const count = await this.getCount(q);
        return count;
    }
    async findOutputsByIds(outputIds, trx) {
        const byId = {};
        if (outputIds.length < 1)
            return byId;
        const rows = await this.toDb(trx)('outputs').whereIn('outputId', outputIds).select('*');
        for (const o of rows) {
            await this.validateOutputScript(o, trx);
        }
        const vrows = this.validateEntities(rows, undefined, ['spendable', 'change']);
        for (const row of vrows) {
            if (row.outputId !== undefined)
                byId[row.outputId] = row;
        }
        return byId;
    }
    async findOutputsByOutpoints(userId, outpoints, trx) {
        const byOutpoint = {};
        if (outpoints.length < 1)
            return byOutpoint;
        const outpointSet = new Set(outpoints.map(o => `${o.txid}.${o.vout}`));
        const txids = [...new Set(outpoints.map(o => o.txid))];
        const vouts = [...new Set(outpoints.map(o => o.vout))];
        const rows = await this.toDb(trx)('outputs')
            .where('userId', userId)
            .whereIn('txid', txids)
            .whereIn('vout', vouts)
            .select('*');
        // Only return requested outpoints, vouts of one txid may end up matching another txid that was not requested.
        const filteredRows = rows.filter(r => outpointSet.has(`${r.txid}.${r.vout}`));
        const vrows = this.validateEntities(filteredRows, undefined, ['spendable', 'change']);
        for (const row of vrows) {
            await this.validateOutputScript(row, trx);
            byOutpoint[`${row.txid}.${row.vout}`] = row;
        }
        return byOutpoint;
    }
    async findOrInsertOutputBasketsBulk(userId, names, trx) {
        const byName = {};
        if (names.length < 1)
            return byName;
        const uniqueNames = [...new Set(names)];
        const existing = await this.toDb(trx)('output_baskets')
            .where('userId', userId)
            .whereIn('name', uniqueNames)
            .select('*');
        for (const basket of existing) {
            if (basket.isDeleted)
                await this.updateOutputBasket((0, utilityHelpers_1.verifyId)(basket.basketId), { isDeleted: false }, trx);
            byName[basket.name] = basket;
        }
        for (const name of uniqueNames) {
            if (!byName[name])
                byName[name] = await this.findOrInsertOutputBasket(userId, name, trx);
        }
        return byName;
    }
    async findOrInsertOutputTagsBulk(userId, tags, trx) {
        const byTag = {};
        if (tags.length < 1)
            return byTag;
        const uniqueTags = [...new Set(tags)];
        const existing = await this.toDb(trx)('output_tags')
            .where('userId', userId)
            .whereIn('tag', uniqueTags)
            .select('*');
        for (const outputTag of existing) {
            if (outputTag.isDeleted)
                await this.updateOutputTag((0, utilityHelpers_1.verifyId)(outputTag.outputTagId), { isDeleted: false }, trx);
            byTag[outputTag.tag] = outputTag;
        }
        for (const tag of uniqueTags) {
            if (!byTag[tag])
                byTag[tag] = await this.findOrInsertOutputTag(userId, tag, trx);
        }
        return byTag;
    }
    async sumSpendableSatoshisInBasket(userId, basketId, excludeSending, trx) {
        const status = ['completed', 'unproven'];
        if (!excludeSending)
            status.push('sending');
        const row = await this.toDb(trx)('outputs as o')
            .join('transactions as t', 'o.transactionId', 't.transactionId')
            .where({ 'o.userId': userId, 'o.spendable': true, 'o.basketId': basketId })
            .whereIn('t.status', status)
            .sum({ totalSatoshis: 'o.satoshis' })
            .first();
        return Number((row && row['totalSatoshis']) || 0);
    }
    /**
     *  Finds closest matching available change output to use as input for new transaction.
     *
     * Transactionally allocate the output such that
     */
    async allocateChangeInput(userId, basketId, targetSatoshis, exactSatoshis, excludeSending, transactionId) {
        const status = ['completed', 'unproven'];
        if (!excludeSending)
            status.push('sending');
        const r = await this.knex.transaction(async (trx) => {
            const baseQuery = () => trx('outputs as o')
                .join('transactions as t', 'o.transactionId', 't.transactionId')
                .where('o.userId', userId)
                .where('o.spendable', true)
                .where('o.basketId', basketId)
                .whereIn('t.status', status)
                .select('o.*');
            let output;
            if (exactSatoshis !== undefined) {
                output = await baseQuery().where('o.satoshis', exactSatoshis).orderBy('o.outputId', 'asc').first();
            }
            if (!output) {
                output = await baseQuery()
                    .where('o.satoshis', '>=', targetSatoshis)
                    .orderBy('o.satoshis', 'asc')
                    .orderBy('o.outputId', 'asc')
                    .first();
            }
            if (!output) {
                output = await baseQuery()
                    .where('o.satoshis', '<', targetSatoshis)
                    .orderBy('o.satoshis', 'desc')
                    .orderBy('o.outputId', 'desc')
                    .first();
            }
            if (!output)
                return undefined;
            await this.updateOutput(output.outputId, {
                spendable: false,
                spentBy: transactionId
            }, trx);
            // Keep behavior identical to the pre-optimization path: ensure lockingScript
            // is present even when it was offloaded from outputs into rawTx storage.
            await this.validateOutputScript(output, trx);
            output.spendable = false;
            output.spentBy = transactionId;
            return output;
        });
        return r;
    }
    /**
     * Helper to force uniform behavior across database engines.
     * Use to process all individual records with time stamps retreived from database.
     */
    validateEntity(entity, dateFields, booleanFields) {
        entity.created_at = this.validateDate(entity.created_at);
        entity.updated_at = this.validateDate(entity.updated_at);
        if (dateFields) {
            for (const df of dateFields) {
                if (entity[df])
                    entity[df] = this.validateDate(entity[df]);
            }
        }
        if (booleanFields) {
            for (const df of booleanFields) {
                if (entity[df] !== undefined)
                    entity[df] = !!entity[df];
            }
        }
        for (const key of Object.keys(entity)) {
            const val = entity[key];
            if (val === null) {
                entity[key] = undefined;
            }
            else if (Buffer.isBuffer(val)) {
                entity[key] = Array.from(val);
            }
        }
        return entity;
    }
    /**
     * Helper to force uniform behavior across database engines.
     * Use to process all arrays of records with time stamps retreived from database.
     * @returns input `entities` array with contained values validated.
     */
    validateEntities(entities, dateFields, booleanFields) {
        for (let i = 0; i < entities.length; i++) {
            entities[i] = this.validateEntity(entities[i], dateFields, booleanFields);
        }
        return entities;
    }
    async adminStats(adminIdentityKey) {
        if (this.dbtype !== 'MySQL')
            throw new WERR_errors_1.WERR_NOT_IMPLEMENTED('adminStats, only MySQL is supported');
        const monitorEvent = (0, utilityHelpers_1.verifyOneOrNone)(await this.findMonitorEvents({
            partial: { event: 'MonitorCallHistory' },
            orderDescending: true,
            paged: { limit: 1 }
        }));
        const monitorStats = monitorEvent ? JSON.parse(monitorEvent.details) : undefined;
        const servicesStats = this.getServices().getServicesCallHistory(true);
        await this.insertMonitorEvent({
            event: 'ServicesCallHistory',
            details: JSON.stringify(servicesStats),
            created_at: new Date(),
            updated_at: new Date(),
            id: 0
        });
        const one_day_ago = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
        const one_week_ago = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
        const one_month_ago = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
        const [[{ usersDay, usersMonth, usersWeek, usersTotal, transactionsDay, transactionsMonth, transactionsWeek, transactionsTotal, txCompletedDay, txCompletedMonth, txCompletedWeek, txCompletedTotal, txFailedDay, txFailedMonth, txFailedWeek, txFailedTotal, txAbandonedDay, txAbandonedMonth, txAbandonedWeek, txAbandonedTotal, txUnprocessedDay, txUnprocessedMonth, txUnprocessedWeek, txUnprocessedTotal, txSendingDay, txSendingMonth, txSendingWeek, txSendingTotal, txUnprovenDay, txUnprovenMonth, txUnprovenWeek, txUnprovenTotal, txUnsignedDay, txUnsignedMonth, txUnsignedWeek, txUnsignedTotal, txNosendDay, txNosendMonth, txNosendWeek, txNosendTotal, txNonfinalDay, txNonfinalMonth, txNonfinalWeek, txNonfinalTotal, txUnfailDay, txUnfailMonth, txUnfailWeek, txUnfailTotal, satoshisDefaultDay, satoshisDefaultMonth, satoshisDefaultWeek, satoshisDefaultTotal, satoshisOtherDay, satoshisOtherMonth, satoshisOtherWeek, satoshisOtherTotal, basketsDay, basketsMonth, basketsWeek, basketsTotal, labelsDay, labelsMonth, labelsWeek, labelsTotal, tagsDay, tagsMonth, tagsWeek, tagsTotal }]] = await this.knex.raw(`
select
    (select count(*) from users where created_at > '${one_day_ago}') as usersDay,
    (select count(*) from users where created_at > '${one_week_ago}') as usersWeek,
    (select count(*) from users where created_at > '${one_month_ago}') as usersMonth,
	  (select count(*) from users) as usersTotal,
    (select count(*) from transactions where created_at > '${one_day_ago}') as transactionsDay,
    (select count(*) from transactions where created_at > '${one_week_ago}') as transactionsWeek,
    (select count(*) from transactions where created_at > '${one_month_ago}') as transactionsMonth,
	  (select count(*) from transactions) as transactionsTotal,
    (select count(*) from transactions where status = 'completed' and created_at > '${one_day_ago}') as txCompletedDay,
    (select count(*) from transactions where status = 'completed' and created_at > '${one_week_ago}') as txCompletedWeek,
    (select count(*) from transactions where status = 'completed' and created_at > '${one_month_ago}') as txCompletedMonth,
	  (select count(*) from transactions where status = 'completed') as txCompletedTotal,
    (select count(*) from transactions where status = 'failed' and not txid is null and created_at > '${one_day_ago}') as txFailedDay,
    (select count(*) from transactions where status = 'failed' and not txid is null and created_at > '${one_week_ago}') as txFailedWeek,
    (select count(*) from transactions where status = 'failed' and not txid is null and created_at > '${one_month_ago}') as txFailedMonth,
	  (select count(*) from transactions where status = 'failed' and not txid is null) as txFailedTotal,
    (select count(*) from transactions where status = 'failed' and txid is null and created_at > '${one_day_ago}') as txAbandonedDay,
    (select count(*) from transactions where status = 'failed' and txid is null and created_at > '${one_week_ago}') as txAbandonedWeek,
    (select count(*) from transactions where status = 'failed' and txid is null and created_at > '${one_month_ago}') as txAbandonedMonth,
	  (select count(*) from transactions where status = 'failed' and txid is null) as txAbandonedTotal,
    (select count(*) from transactions where status = 'unprocessed' and created_at > '${one_day_ago}') as txUnprocessedDay,
    (select count(*) from transactions where status = 'unprocessed' and created_at > '${one_week_ago}') as txUnprocessedWeek,
    (select count(*) from transactions where status = 'unprocessed' and created_at > '${one_month_ago}') as txUnprocessedMonth,
	  (select count(*) from transactions where status = 'unprocessed') as txUnprocessedTotal,
    (select count(*) from transactions where status = 'sending' and created_at > '${one_day_ago}') as txSendingDay,
    (select count(*) from transactions where status = 'sending' and created_at > '${one_week_ago}') as txSendingWeek,
    (select count(*) from transactions where status = 'sending' and created_at > '${one_month_ago}') as txSendingMonth,
	  (select count(*) from transactions where status = 'sending') as txSendingTotal,
    (select count(*) from transactions where status = 'unproven' and created_at > '${one_day_ago}') as txUnprovenDay,
    (select count(*) from transactions where status = 'unproven' and created_at > '${one_week_ago}') as txUnprovenWeek,
    (select count(*) from transactions where status = 'unproven' and created_at > '${one_month_ago}') as txUnprovenMonth,
	  (select count(*) from transactions where status = 'unproven') as txUnprovenTotal,
    (select count(*) from transactions where status = 'unsigned' and created_at > '${one_day_ago}') as txUnsignedDay,
    (select count(*) from transactions where status = 'unsigned' and created_at > '${one_week_ago}') as txUnsignedWeek,
    (select count(*) from transactions where status = 'unsigned' and created_at > '${one_month_ago}') as txUnsignedMonth,
	  (select count(*) from transactions where status = 'unsigned') as txUnsignedTotal,
    (select count(*) from transactions where status = 'nosend' and created_at > '${one_day_ago}') as txNosendDay,
    (select count(*) from transactions where status = 'nosend' and created_at > '${one_week_ago}') as txNosendWeek,
    (select count(*) from transactions where status = 'nosend' and created_at > '${one_month_ago}') as txNosendMonth,
	  (select count(*) from transactions where status = 'nosend') as txNosendTotal,
    (select count(*) from transactions where status = 'nonfinal' and created_at > '${one_day_ago}') as txNonfinalDay,
    (select count(*) from transactions where status = 'nonfinal' and created_at > '${one_week_ago}') as txNonfinalWeek,
    (select count(*) from transactions where status = 'nonfinal' and created_at > '${one_month_ago}') as txNonfinalMonth,
	  (select count(*) from transactions where status = 'nonfinal') as txNonfinalTotal,
    (select count(*) from transactions where status = 'unfail' and created_at > '${one_day_ago}') as txUnfailDay,
    (select count(*) from transactions where status = 'unfail' and created_at > '${one_week_ago}') as txUnfailWeek,
    (select count(*) from transactions where status = 'unfail' and created_at > '${one_month_ago}') as txUnfailMonth,
	  (select count(*) from transactions where status = 'unfail') as txUnfailTotal,
    (select sum(satoshis) from outputs where spendable = 1 and \`change\` = 1 and created_at > '${one_day_ago}') as satoshisDefaultDay,
    (select sum(satoshis) from outputs where spendable = 1 and \`change\` = 1 and created_at > '${one_week_ago}') as satoshisDefaultWeek,
    (select sum(satoshis) from outputs where spendable = 1 and \`change\` = 1 and created_at > '${one_month_ago}') as satoshisDefaultMonth,
	  (select sum(satoshis) from outputs where spendable = 1 and \`change\` = 1) as satoshisDefaultTotal,
    (select sum(satoshis) from outputs where spendable = 1 and \`change\` = 0 and not basketId is null and created_at > '${one_day_ago}') as satoshisOtherDay,
    (select sum(satoshis) from outputs where spendable = 1 and \`change\` = 0 and not basketId is null and created_at > '${one_week_ago}') as satoshisOtherWeek,
    (select sum(satoshis) from outputs where spendable = 1 and \`change\` = 0 and not basketId is null and created_at > '${one_month_ago}') as satoshisOtherMonth,
	  (select sum(satoshis) from outputs where spendable = 1 and \`change\` = 0 and not basketId is null) as satoshisOtherTotal,
    (select count(*) from output_baskets where created_at > '${one_day_ago}') as basketsDay,
    (select count(*) from output_baskets where created_at > '${one_week_ago}') as basketsWeek,
    (select count(*) from output_baskets where created_at > '${one_month_ago}') as basketsMonth,
	  (select count(*) from output_baskets) as basketsTotal,
    (select count(*) from tx_labels where created_at > '${one_day_ago}') as labelsDay,
    (select count(*) from tx_labels where created_at > '${one_week_ago}') as labelsWeek,
    (select count(*) from tx_labels where created_at > '${one_month_ago}') as labelsMonth,
	  (select count(*) from tx_labels) as labelsTotal,
    (select count(*) from output_tags where created_at > '${one_day_ago}') as tagsDay,
    (select count(*) from output_tags where created_at > '${one_week_ago}') as tagsWeek,
    (select count(*) from output_tags where created_at > '${one_month_ago}') as tagsMonth,
	  (select count(*) from output_tags) as tagsTotal
      `);
        const r = {
            monitorStats,
            servicesStats,
            requestedBy: adminIdentityKey,
            when: new Date().toISOString(),
            usersDay,
            usersWeek,
            usersMonth,
            usersTotal,
            transactionsDay,
            transactionsWeek,
            transactionsMonth,
            transactionsTotal,
            txCompletedDay,
            txCompletedWeek,
            txCompletedMonth,
            txCompletedTotal,
            txFailedDay,
            txFailedWeek,
            txFailedMonth,
            txFailedTotal,
            txAbandonedDay,
            txAbandonedWeek,
            txAbandonedMonth,
            txAbandonedTotal,
            txUnprocessedDay,
            txUnprocessedWeek,
            txUnprocessedMonth,
            txUnprocessedTotal,
            txSendingDay,
            txSendingWeek,
            txSendingMonth,
            txSendingTotal,
            txUnprovenDay,
            txUnprovenWeek,
            txUnprovenMonth,
            txUnprovenTotal,
            txUnsignedDay,
            txUnsignedWeek,
            txUnsignedMonth,
            txUnsignedTotal,
            txNosendDay,
            txNosendWeek,
            txNosendMonth,
            txNosendTotal,
            txNonfinalDay,
            txNonfinalWeek,
            txNonfinalMonth,
            txNonfinalTotal,
            txUnfailDay,
            txUnfailWeek,
            txUnfailMonth,
            txUnfailTotal,
            satoshisDefaultDay: Number(satoshisDefaultDay),
            satoshisDefaultWeek: Number(satoshisDefaultWeek),
            satoshisDefaultMonth: Number(satoshisDefaultMonth),
            satoshisDefaultTotal: Number(satoshisDefaultTotal),
            satoshisOtherDay: Number(satoshisOtherDay),
            satoshisOtherWeek: Number(satoshisOtherWeek),
            satoshisOtherMonth: Number(satoshisOtherMonth),
            satoshisOtherTotal: Number(satoshisOtherTotal),
            basketsDay,
            basketsWeek,
            basketsMonth,
            basketsTotal,
            labelsDay,
            labelsWeek,
            labelsMonth,
            labelsTotal,
            tagsDay,
            tagsWeek,
            tagsMonth,
            tagsTotal
        };
        return r;
    }
}
exports.StorageKnex = StorageKnex;
//# sourceMappingURL=StorageKnex.js.map