wallet-storage
Version:
BRC100 conforming wallet, wallet storage and wallet signer components
1,035 lines • 41.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.StorageKnex = void 0;
const index_all_1 = require("../index.all");
const index_all_2 = require("./index.all");
const StorageProvider_1 = require("./StorageProvider");
const purgeData_1 = require("./methods/purgeData");
const listActions_1 = require("./methods/listActions");
const listOutputs_1 = require("./methods/listOutputs");
const reviewStatus_1 = require("./methods/reviewStatus");
class StorageKnex extends StorageProvider_1.StorageProvider {
constructor(options) {
super(options);
this._verifiedReadyForDatabaseAccess = false;
if (!options.knex)
throw new index_all_1.sdk.WERR_INVALID_PARAMETER('options.knex', `valid`);
this.knex = options.knex;
}
async readSettings() {
return this.validateEntity((0, index_all_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, index_all_1.verifyOneOrNone)(await this.findProvenTxs({ partial: { txid: txid } }));
if (!r.proven) {
const reqRawTx = (0, index_all_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;
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, index_all_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')`);
if (this.dbtype === 'MySQL')
rs = rs[0];
const r = (0, index_all_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', '>=', 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', '>=', 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 index_all_1.sdk.WERR_UNAUTHORIZED();
return await (0, listActions_1.listActions)(this, auth, vargs);
}
async listOutputs(auth, vargs) {
if (!auth.userId)
throw new index_all_1.sdk.WERR_UNAUTHORIZED();
return await (0, listOutputs_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 index_all_1.sdk.WERR_UNAUTHORIZED();
certificate.userId = auth.userId;
return await this.insertCertificate(certificate);
}
async insertCertificate(certificate, trx) {
const e = await this.validateEntityForInsert(certificate, trx, undefined, [
'isDeleted'
]);
const fields = e.fields;
if (e.fields)
delete e.fields;
if (e.certificateId === 0)
delete e.certificateId;
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) {
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;
}
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 index_all_1.sdk.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 index_all_1.sdk.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.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 index_all_1.sdk.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 index_all_1.sdk.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 = index_all_2.table.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 index_all_1.sdk.WERR_INVALID_PARAMETER('args.partial.rawTx', `undefined. ProvenTxReqs may not be found by rawTx value.`);
if (args.partial.inputBEEF)
throw new index_all_1.sdk.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 && args.txids.length > 0)
q.whereIn('txid', args.txids);
return q;
}
findProvenTxsQuery(args) {
if (args.partial.rawTx)
throw new index_all_1.sdk.WERR_INVALID_PARAMETER('args.partial.rawTx', `undefined. ProvenTxs may not be found by rawTx value.`);
if (args.partial.merklePath)
throw new index_all_1.sdk.WERR_INVALID_PARAMETER('args.partial.merklePath', `undefined. ProvenTxs may not be found by merklePath value.`);
return this.setupQuery('proven_txs', args);
}
findSyncStatesQuery(args) {
return this.setupQuery('sync_states', args);
}
findTransactionsQuery(args, count) {
if (args.partial.rawTx)
throw new index_all_1.sdk.WERR_INVALID_PARAMETER('args.partial.rawTx', `undefined. Transactions may not be found by rawTx value.`);
if (args.partial.inputBEEF)
throw new index_all_1.sdk.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.noRawTx && !count) {
const columns = index_all_2.table.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 index_all_1.sdk.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 index_all_1.sdk.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 index_all_1.sdk.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);
const r = await q;
return this.validateEntities(r, undefined, ['isDeleted']);
}
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 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) {
const config = {
migrationSource: new index_all_2.KnexMigrations(this.chain, storageName, storageIdentityKey, 1024)
};
await this.knex.migrate.latest(config);
const version = await this.knex.migrate.currentVersion(config);
return version;
}
async dropAllData() {
// Only using migrations to migrate down, don't need valid properties for settings table.
const config = {
migrationSource: new index_all_2.KnexMigrations('test', '', '', 1024)
};
const count = Object.keys(config.migrationSource.migrations).length;
for (let i = 0; i < count; i++) {
try {
const r = await this.knex.migrate.down(config);
expect(r).toBeTruthy();
}
catch (eu) {
break;
}
}
}
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;
}
async validateOutputScript(o, trx) {
// without offset and length values return what we have (make no changes)
if (!o.scriptLength || !o.scriptOffset || !o.txid)
return;
// if there is an outputScript and its length is the expected length return what we have.
if (o.lockingScript && o.lockingScript.length === o.scriptLength)
return;
// outputScript is missing or has incorrect length...
const script = await this.getRawTxOfKnownValidTransaction(o.txid, o.scriptOffset, o.scriptLength, trx);
if (!script)
return;
o.lockingScript = script;
}
/**
* 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();
}
if (!this._verifiedReadyForDatabaseAccess) {
// Make sure foreign key constraint checking is turned on in SQLite.
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 index_all_1.sdk.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 extendOutput(o, includeBasket = false, includeTags = false, trx) {
const ox = o;
if (includeBasket && ox.basketId)
ox.basket = await this.findOutputBasketById(o.basketId, trx);
if (includeTags) {
ox.tags = await this.getTagsForOutputId(o.outputId);
}
return o;
}
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);
}
/**
* Finds closest matching available change output to use as input for new transaction.
*
* Transactionally allocate the output such that
*/
async countChangeInputs(userId, basketId, excludeSending) {
const status = ['completed', 'unproven'];
if (!excludeSending)
status.push('sending');
const statusText = status.map(s => `'${s}'`).join(',');
const txStatusCondition = `(SELECT status FROM transactions WHERE outputs.transactionId = transactions.transactionId) in (${statusText})`;
let q = this.knex('outputs')
.where({ userId, spendable: true, basketId })
.whereRaw(txStatusCondition);
const count = await this.getCount(q);
return count;
}
/**
* 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 statusText = status.map(s => `'${s}'`).join(',');
const r = await this.knex.transaction(async (trx) => {
const txStatusCondition = `AND (SELECT status FROM transactions WHERE outputs.transactionId = transactions.transactionId) in (${statusText})`;
let outputId;
const setOutputId = async (rawQuery) => {
let oidr = await trx.raw(rawQuery);
outputId = undefined;
if (!oidr['outputId'] && oidr.length > 0)
oidr = oidr[0];
if (!oidr['outputId'] && oidr.length > 0)
oidr = oidr[0];
if (oidr['outputId'])
outputId = Number(oidr['outputId']);
};
if (exactSatoshis !== undefined) {
// Find outputId of output that with exactSatoshis
await setOutputId(`
SELECT outputId
FROM outputs
WHERE userId = ${userId}
AND spendable = 1
AND basketId = ${basketId}
${txStatusCondition}
AND satoshis = ${exactSatoshis}
LIMIT 1;
`);
}
if (outputId === undefined) {
// Find outputId of output that would at least fund targetSatoshis
await setOutputId(`
SELECT outputId
FROM outputs
WHERE userId = ${userId}
AND spendable = 1
AND basketId = ${basketId}
${txStatusCondition}
AND satoshis - ${targetSatoshis} = (
SELECT MIN(satoshis - ${targetSatoshis})
FROM outputs
WHERE userId = ${userId}
AND spendable = 1
AND basketId = ${basketId}
${txStatusCondition}
AND satoshis - ${targetSatoshis} >= 0
)
LIMIT 1;
`);
}
if (outputId === undefined) {
// Find outputId of output that would add the most fund targetSatoshis
await setOutputId(`
SELECT outputId
FROM outputs
WHERE userId = ${userId}
AND spendable = 1
AND basketId = ${basketId}
${txStatusCondition}
AND satoshis - ${targetSatoshis} = (
SELECT MAX(satoshis - ${targetSatoshis})
FROM outputs
WHERE userId = ${userId}
AND spendable = 1
AND basketId = ${basketId}
${txStatusCondition}
AND satoshis - ${targetSatoshis} < 0
)
LIMIT 1;
`);
}
if (outputId === undefined)
return undefined;
await this.updateOutput(outputId, {
spendable: false,
spentBy: transactionId
}, trx);
const r = (0, index_all_1.verifyTruthy)(await this.findOutputById(outputId, trx));
return r;
});
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;
}
}
exports.StorageKnex = StorageKnex;
//# sourceMappingURL=StorageKnex.js.map