@bsv/wallet-toolbox-client
Version:
Client only Wallet Storage
1,193 lines (1,192 loc) • 96.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.StorageIdb = void 0;
const idb_1 = require("idb");
const utilityHelpers_1 = require("../utility/utilityHelpers");
const StorageProvider_1 = require("./StorageProvider");
const listActionsIdb_1 = require("./methods/listActionsIdb");
const listOutputsIdb_1 = require("./methods/listOutputsIdb");
const reviewStatusIdb_1 = require("./methods/reviewStatusIdb");
const purgeDataIdb_1 = require("./methods/purgeDataIdb");
const WERR_errors_1 = require("../sdk/WERR_errors");
/**
* This class implements the `StorageProvider` interface using IndexedDB,
* via the promises wrapper package `idb`.
*/
class StorageIdb extends StorageProvider_1.StorageProvider {
constructor(options) {
super(options);
this.allStores = [
'certificates',
'certificate_fields',
'commissions',
'monitor_events',
'outputs',
'output_baskets',
'output_tags',
'output_tags_map',
'proven_txs',
'proven_tx_reqs',
'sync_states',
'transactions',
'tx_labels',
'tx_labels_map',
'users'
];
this.dbName = `wallet-toolbox-${this.chain}net`;
}
/**
* This method must be called at least once before any other method accesses the database,
* and each time the schema may have updated.
*
* If the database has already been created in this context, `storageName` and `storageIdentityKey`
* are ignored.
*
* @param storageName
* @param storageIdentityKey
* @returns
*/
async migrate(storageName, storageIdentityKey) {
const db = await this.verifyDB(storageName, storageIdentityKey);
return db.version.toString();
}
/**
* Following initial database initialization, this method verfies that db is ready for use.
*
* @throws `WERR_INVALID_OPERATION` if the database has not been initialized by a call to `migrate`.
*
* @param storageName
* @param storageIdentityKey
*
* @returns
*/
async verifyDB(storageName, storageIdentityKey) {
if (this.db)
return this.db;
this.db = await this.initDB(storageName, storageIdentityKey);
this._settings = (await this.db.getAll('settings'))[0];
this.whenLastAccess = new Date();
return this.db;
}
/**
* Convert the standard optional `TrxToken` parameter into either a direct knex database instance,
* or a Knex.Transaction as appropriate.
*/
toDbTrx(stores, mode, trx) {
if (trx) {
const t = trx;
return t;
}
else {
if (!this.db)
throw new Error('not initialized');
const db = this.db;
const trx = db.transaction(stores || this.allStores, mode || 'readwrite');
this.whenLastAccess = new Date();
return trx;
}
}
/**
* Called by `makeAvailable` to return storage `TableSettings`.
* Since this is the first async method that must be called by all clients,
* it is where async initialization occurs.
*
* After initialization, cached settings are returned.
*
* @param trx
*/
async readSettings(trx) {
await this.verifyDB();
return this._settings;
}
async initDB(storageName, storageIdentityKey) {
const chain = this.chain;
const maxOutputScript = 1024;
const db = await (0, idb_1.openDB)(this.dbName, 1, {
upgrade(db, oldVersion, newVersion, transaction) {
if (!db.objectStoreNames.contains('proven_txs')) {
// proven_txs object store
const provenTxsStore = db.createObjectStore('proven_txs', {
keyPath: 'provenTxId',
autoIncrement: true
});
provenTxsStore.createIndex('txid', 'txid', { unique: true });
}
if (!db.objectStoreNames.contains('proven_tx_reqs')) {
// proven_tx_reqs object store
const provenTxReqsStore = db.createObjectStore('proven_tx_reqs', {
keyPath: 'provenTxReqId',
autoIncrement: true
});
provenTxReqsStore.createIndex('provenTxId', 'provenTxId');
provenTxReqsStore.createIndex('txid', 'txid', { unique: true });
provenTxReqsStore.createIndex('status', 'status');
provenTxReqsStore.createIndex('batch', 'batch');
}
if (!db.objectStoreNames.contains('users')) {
const users = db.createObjectStore('users', {
keyPath: 'userId',
autoIncrement: true
});
users.createIndex('identityKey', 'identityKey', { unique: true });
}
if (!db.objectStoreNames.contains('certificates')) {
// certificates object store
const certificatesStore = db.createObjectStore('certificates', {
keyPath: 'certificateId',
autoIncrement: true
});
certificatesStore.createIndex('userId', 'userId');
certificatesStore.createIndex('userId_type_certifier_serialNumber', ['userId', 'type', 'certifier', 'serialNumber'], { unique: true });
}
if (!db.objectStoreNames.contains('certificate_fields')) {
// certificate_fields object store
const certificateFieldsStore = db.createObjectStore('certificate_fields', {
keyPath: ['certificateId', 'fieldName'] // Composite key
});
certificateFieldsStore.createIndex('userId', 'userId');
certificateFieldsStore.createIndex('certificateId', 'certificateId');
}
if (!db.objectStoreNames.contains('output_baskets')) {
// output_baskets object store
const outputBasketsStore = db.createObjectStore('output_baskets', {
keyPath: 'basketId',
autoIncrement: true
});
outputBasketsStore.createIndex('userId', 'userId');
outputBasketsStore.createIndex('name_userId', ['name', 'userId'], { unique: true });
}
if (!db.objectStoreNames.contains('transactions')) {
// transactions object store
const transactionsStore = db.createObjectStore('transactions', {
keyPath: 'transactionId',
autoIncrement: true
});
transactionsStore.createIndex('userId', 'userId');
(transactionsStore.createIndex('status', 'status'),
transactionsStore.createIndex('status_userId', ['status', 'userId']));
transactionsStore.createIndex('provenTxId', 'provenTxId');
transactionsStore.createIndex('reference', 'reference', { unique: true });
}
if (!db.objectStoreNames.contains('commissions')) {
// commissions object store
const commissionsStore = db.createObjectStore('commissions', {
keyPath: 'commissionId',
autoIncrement: true
});
commissionsStore.createIndex('userId', 'userId');
commissionsStore.createIndex('transactionId', 'transactionId', { unique: true });
}
if (!db.objectStoreNames.contains('outputs')) {
// outputs object store
const outputsStore = db.createObjectStore('outputs', {
keyPath: 'outputId',
autoIncrement: true
});
outputsStore.createIndex('userId', 'userId');
outputsStore.createIndex('transactionId', 'transactionId');
outputsStore.createIndex('basketId', 'basketId');
outputsStore.createIndex('spentBy', 'spentBy');
outputsStore.createIndex('transactionId_vout_userId', ['transactionId', 'vout', 'userId'], { unique: true });
}
if (!db.objectStoreNames.contains('output_tags')) {
// output_tags object store
const outputTagsStore = db.createObjectStore('output_tags', {
keyPath: 'outputTagId',
autoIncrement: true
});
outputTagsStore.createIndex('userId', 'userId');
outputTagsStore.createIndex('tag_userId', ['tag', 'userId'], { unique: true });
}
if (!db.objectStoreNames.contains('output_tags_map')) {
// output_tags_map object store
const outputTagsMapStore = db.createObjectStore('output_tags_map', {
keyPath: ['outputTagId', 'outputId']
});
outputTagsMapStore.createIndex('outputTagId', 'outputTagId');
outputTagsMapStore.createIndex('outputId', 'outputId');
}
if (!db.objectStoreNames.contains('tx_labels')) {
// tx_labels object store
const txLabelsStore = db.createObjectStore('tx_labels', {
keyPath: 'txLabelId',
autoIncrement: true
});
txLabelsStore.createIndex('userId', 'userId');
txLabelsStore.createIndex('label_userId', ['label', 'userId'], { unique: true });
}
if (!db.objectStoreNames.contains('tx_labels_map')) {
// tx_labels_map object store
const txLabelsMapStore = db.createObjectStore('tx_labels_map', {
keyPath: ['txLabelId', 'transactionId']
});
txLabelsMapStore.createIndex('txLabelId', 'txLabelId');
txLabelsMapStore.createIndex('transactionId', 'transactionId');
}
if (!db.objectStoreNames.contains('monitor_events')) {
// monitor_events object store
const monitorEventsStore = db.createObjectStore('monitor_events', {
keyPath: 'id',
autoIncrement: true
});
}
if (!db.objectStoreNames.contains('sync_states')) {
// sync_states object store
const syncStatesStore = db.createObjectStore('sync_states', {
keyPath: 'syncStateId',
autoIncrement: true
});
syncStatesStore.createIndex('userId', 'userId');
syncStatesStore.createIndex('refNum', 'refNum', { unique: true });
syncStatesStore.createIndex('status', 'status');
}
if (!db.objectStoreNames.contains('settings')) {
if (!storageName || !storageIdentityKey) {
throw new WERR_errors_1.WERR_INVALID_OPERATION('migrate must be called before first access');
}
const settings = db.createObjectStore('settings', {
keyPath: 'storageIdentityKey'
});
const s = {
created_at: new Date(),
updated_at: new Date(),
storageIdentityKey,
storageName,
chain,
dbtype: 'IndexedDB',
maxOutputScript
};
settings.put(s);
}
}
});
return db;
}
//
// StorageProvider abstract methods
//
async reviewStatus(args) {
return await (0, reviewStatusIdb_1.reviewStatusIdb)(this, args);
}
async purgeData(params, trx) {
return await (0, purgeDataIdb_1.purgeDataIdb)(this, params, trx);
}
/**
* Proceeds in three stages:
* 1. Find an output that exactly funds the transaction (if exactSatoshis is not undefined).
* 2. Find an output that overfunds by the least amount (targetSatoshis).
* 3. Find an output that comes as close to funding as possible (targetSatoshis).
* 4. Return undefined if no output is found.
*
* Outputs must belong to userId and basketId and have spendable true.
* Their corresponding transaction must have status of 'completed', 'unproven', or 'sending' (if excludeSending is false).
*
* @param userId
* @param basketId
* @param targetSatoshis
* @param exactSatoshis
* @param excludeSending
* @param transactionId
* @returns next funding output to add to transaction or undefined if there are none.
*/
async allocateChangeInput(userId, basketId, targetSatoshis, exactSatoshis, excludeSending, transactionId) {
const dbTrx = this.toDbTrx(['outputs', 'transactions'], 'readwrite');
try {
const txStatus = ['completed', 'unproven'];
if (!excludeSending)
txStatus.push('sending');
const args = {
partial: { userId, basketId, spendable: true },
txStatus,
trx: dbTrx
};
const outputs = await this.findOutputs(args);
let output;
let scores = [];
for (const o of outputs) {
if (exactSatoshis && o.satoshis === exactSatoshis) {
output = o;
break;
}
const score = o.satoshis - targetSatoshis;
scores.push({ output: o, score });
}
if (!output) {
// sort scores increasing by score property
scores = scores.sort((a, b) => a.score - b.score);
// find the first score that is greater than or equal to 0
const o = scores.find(s => s.score >= 0);
if (o) {
// stage 2 satisfied (minimally funded)
output = o.output;
}
else if (scores.length > 0) {
// stage 3 satisfied (minimally under-funded)
output = scores.slice(-1)[0].output;
}
else {
// no available funding outputs
output = undefined;
}
}
if (output) {
// mark output as spent by transactionId
await this.updateOutput(output.outputId, { spendable: false, spentBy: transactionId }, dbTrx);
}
return output;
}
finally {
await dbTrx.done;
}
}
async getProvenOrRawTx(txid, trx) {
const r = {
proven: undefined,
rawTx: undefined,
inputBEEF: undefined
};
r.proven = (0, utilityHelpers_1.verifyOneOrNone)(await this.findProvenTxs({ partial: { txid: txid }, trx }));
if (!r.proven) {
const req = (0, utilityHelpers_1.verifyOneOrNone)(await this.findProvenTxReqs({ partial: { txid: txid }, trx }));
if (req && ['unsent', 'unmined', 'unconfirmed', 'sending', 'nosend', 'completed'].includes(req.status)) {
r.rawTx = req.rawTx;
r.inputBEEF = req.inputBEEF;
}
}
return r;
}
async getRawTxOfKnownValidTransaction(txid, offset, length, trx) {
if (!txid)
return undefined;
if (!this.isAvailable())
await this.makeAvailable();
let rawTx = undefined;
const r = await this.getProvenOrRawTx(txid, trx);
if (r.proven)
rawTx = r.proven.rawTx;
else
rawTx = r.rawTx;
if (rawTx && offset !== undefined && length !== undefined && Number.isInteger(offset) && Number.isInteger(length)) {
rawTx = rawTx.slice(offset, offset + length);
}
return rawTx;
}
async getLabelsForTransactionId(transactionId, trx) {
const maps = await this.findTxLabelMaps({ partial: { transactionId, isDeleted: false }, trx });
const labelIds = maps.map(m => m.txLabelId);
const labels = [];
for (const txLabelId of labelIds) {
const label = (0, utilityHelpers_1.verifyOne)(await this.findTxLabels({ partial: { txLabelId, isDeleted: false }, trx }));
labels.push(label);
}
return labels;
}
async getTagsForOutputId(outputId, trx) {
const maps = await this.findOutputTagMaps({ partial: { outputId, isDeleted: false }, trx });
const tagIds = maps.map(m => m.outputTagId);
const tags = [];
for (const outputTagId of tagIds) {
const tag = (0, utilityHelpers_1.verifyOne)(await this.findOutputTags({ partial: { outputTagId, isDeleted: false }, trx }));
tags.push(tag);
}
return tags;
}
async listActions(auth, vargs) {
if (!auth.userId)
throw new WERR_errors_1.WERR_UNAUTHORIZED();
return await (0, listActionsIdb_1.listActionsIdb)(this, auth, vargs);
}
async listOutputs(auth, vargs) {
if (!auth.userId)
throw new WERR_errors_1.WERR_UNAUTHORIZED();
return await (0, listOutputsIdb_1.listOutputsIdb)(this, auth, vargs);
}
async countChangeInputs(userId, basketId, excludeSending) {
const txStatus = ['completed', 'unproven'];
if (!excludeSending)
txStatus.push('sending');
const args = { partial: { userId, basketId }, txStatus };
let count = 0;
await this.filterOutputs(args, r => {
count++;
});
return count;
}
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 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);
}
//
// StorageReaderWriter abstract methods
//
async dropAllData() {
await (0, idb_1.deleteDB)(this.dbName);
}
async filterOutputTagMaps(args, filtered, userId) {
var _a, _b, _c, _d;
const offset = ((_a = args.paged) === null || _a === void 0 ? void 0 : _a.offset) || 0;
let skipped = 0;
let count = 0;
const dbTrx = this.toDbTrx(['output_tags_map'], 'readonly', args.trx);
let cursor;
if (((_b = args.partial) === null || _b === void 0 ? void 0 : _b.outputTagId) !== undefined) {
cursor = await dbTrx.objectStore('output_tags_map').index('outputTagId').openCursor(args.partial.outputTagId);
}
else if (((_c = args.partial) === null || _c === void 0 ? void 0 : _c.outputId) !== undefined) {
cursor = await dbTrx.objectStore('output_tags_map').index('outputId').openCursor(args.partial.outputId);
}
else {
cursor = await dbTrx.objectStore('output_tags_map').openCursor();
}
let firstTime = true;
while (cursor) {
if (!firstTime)
cursor = await cursor.continue();
if (!cursor)
break;
firstTime = false;
const r = cursor.value;
if (args.since && args.since > r.updated_at)
continue;
if (args.tagIds && !args.tagIds.includes(r.outputTagId))
continue;
if (args.partial) {
if (args.partial.outputTagId && r.outputTagId !== args.partial.outputTagId)
continue;
if (args.partial.outputId && r.outputId !== args.partial.outputId)
continue;
if (args.partial.created_at && r.created_at.getTime() !== args.partial.created_at.getTime())
continue;
if (args.partial.updated_at && r.updated_at.getTime() !== args.partial.updated_at.getTime())
continue;
if (args.partial.isDeleted !== undefined && r.isDeleted !== args.partial.isDeleted)
continue;
}
if (userId !== undefined && r.txid) {
const count = await this.countOutputTags({ partial: { userId, outputTagId: r.outputTagId }, trx: args.trx });
if (count === 0)
continue;
}
if (skipped < offset) {
skipped++;
continue;
}
filtered(r);
count++;
if (((_d = args.paged) === null || _d === void 0 ? void 0 : _d.limit) && count >= args.paged.limit)
break;
}
if (!args.trx)
await dbTrx.done;
}
async findOutputTagMaps(args) {
const results = [];
await this.filterOutputTagMaps(args, r => {
results.push(this.validateEntity(r));
});
return results;
}
async filterProvenTxReqs(args, filtered, userId) {
var _a, _b, _c, _d, _e, _f, _g;
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 offset = ((_a = args.paged) === null || _a === void 0 ? void 0 : _a.offset) || 0;
let skipped = 0;
let count = 0;
const dbTrx = this.toDbTrx(['proven_tx_reqs'], 'readonly', args.trx);
let cursor;
if ((_b = args.partial) === null || _b === void 0 ? void 0 : _b.provenTxReqId) {
cursor = await dbTrx.objectStore('proven_tx_reqs').openCursor(args.partial.provenTxReqId);
}
else if (((_c = args.partial) === null || _c === void 0 ? void 0 : _c.provenTxId) !== undefined) {
cursor = await dbTrx.objectStore('proven_tx_reqs').index('provenTxId').openCursor(args.partial.provenTxId);
}
else if (((_d = args.partial) === null || _d === void 0 ? void 0 : _d.txid) !== undefined) {
cursor = await dbTrx.objectStore('proven_tx_reqs').index('txid').openCursor(args.partial.txid);
}
else if (((_e = args.partial) === null || _e === void 0 ? void 0 : _e.status) !== undefined) {
cursor = await dbTrx.objectStore('proven_tx_reqs').index('status').openCursor(args.partial.status);
}
else if (((_f = args.partial) === null || _f === void 0 ? void 0 : _f.batch) !== undefined) {
cursor = await dbTrx.objectStore('proven_tx_reqs').index('batch').openCursor(args.partial.batch);
}
else {
cursor = await dbTrx.objectStore('proven_tx_reqs').openCursor();
}
let firstTime = true;
while (cursor) {
if (!firstTime)
cursor = await cursor.continue();
if (!cursor)
break;
firstTime = false;
const r = cursor.value;
if (args.since && args.since > r.updated_at)
continue;
if (args.partial) {
if (args.partial.provenTxReqId && r.provenTxReqId !== args.partial.provenTxReqId)
continue;
if (args.partial.provenTxId && r.provenTxId !== args.partial.provenTxId)
continue;
if (args.partial.created_at && r.created_at.getTime() !== args.partial.created_at.getTime())
continue;
if (args.partial.updated_at && r.updated_at.getTime() !== args.partial.updated_at.getTime())
continue;
if (args.partial.status && r.status !== args.partial.status)
continue;
if (args.partial.attempts !== undefined && r.attempts !== args.partial.attempts)
continue;
if (args.partial.notified !== undefined && r.notified !== args.partial.notified)
continue;
if (args.partial.txid && r.txid !== args.partial.txid)
continue;
if (args.partial.batch && r.batch !== args.partial.batch)
continue;
if (args.partial.history && r.history !== args.partial.history)
continue;
if (args.partial.notify && r.notify !== args.partial.notify)
continue;
}
if (userId !== undefined && r.txid) {
const count = await this.countTransactions({ partial: { userId, txid: r.txid }, trx: args.trx });
if (count === 0)
continue;
}
if (skipped < offset) {
skipped++;
continue;
}
filtered(r);
count++;
if (((_g = args.paged) === null || _g === void 0 ? void 0 : _g.limit) && count >= args.paged.limit)
break;
}
if (!args.trx)
await dbTrx.done;
}
async findProvenTxReqs(args) {
const results = [];
await this.filterProvenTxReqs(args, r => {
results.push(this.validateEntity(r));
});
return results;
}
async filterProvenTxs(args, filtered, userId) {
var _a, _b, _c, _d;
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.`);
const offset = ((_a = args.paged) === null || _a === void 0 ? void 0 : _a.offset) || 0;
let skipped = 0;
let count = 0;
const dbTrx = this.toDbTrx(['proven_txs'], 'readonly', args.trx);
let cursor;
if ((_b = args.partial) === null || _b === void 0 ? void 0 : _b.provenTxId) {
cursor = await dbTrx.objectStore('proven_txs').openCursor(args.partial.provenTxId);
}
else if (((_c = args.partial) === null || _c === void 0 ? void 0 : _c.txid) !== undefined) {
cursor = await dbTrx.objectStore('proven_txs').index('txid').openCursor(args.partial.txid);
}
else {
cursor = await dbTrx.objectStore('proven_txs').openCursor();
}
let firstTime = true;
while (cursor) {
if (!firstTime)
cursor = await cursor.continue();
if (!cursor)
break;
firstTime = false;
const r = cursor.value;
if (args.since && args.since > r.updated_at)
continue;
if (args.partial) {
if (args.partial.provenTxId && r.provenTxId !== args.partial.provenTxId)
continue;
if (args.partial.created_at && r.created_at.getTime() !== args.partial.created_at.getTime())
continue;
if (args.partial.updated_at && r.updated_at.getTime() !== args.partial.updated_at.getTime())
continue;
if (args.partial.txid && r.txid !== args.partial.txid)
continue;
if (args.partial.height !== undefined && r.height !== args.partial.height)
continue;
if (args.partial.index !== undefined && r.index !== args.partial.index)
continue;
if (args.partial.blockHash && r.blockHash !== args.partial.blockHash)
continue;
if (args.partial.merkleRoot && r.merkleRoot !== args.partial.merkleRoot)
continue;
}
if (userId !== undefined) {
const count = await this.countTransactions({ partial: { userId, provenTxId: r.provenTxId }, trx: args.trx });
if (count === 0)
continue;
}
if (skipped < offset) {
skipped++;
continue;
}
filtered(r);
count++;
if (((_d = args.paged) === null || _d === void 0 ? void 0 : _d.limit) && count >= args.paged.limit)
break;
}
if (!args.trx)
await dbTrx.done;
}
async findProvenTxs(args) {
const results = [];
await this.filterProvenTxs(args, r => {
results.push(this.validateEntity(r));
});
return results;
}
async filterTxLabelMaps(args, filtered, userId) {
var _a, _b, _c, _d;
const offset = ((_a = args.paged) === null || _a === void 0 ? void 0 : _a.offset) || 0;
let skipped = 0;
let count = 0;
const dbTrx = this.toDbTrx(['tx_labels_map'], 'readonly', args.trx);
let cursor;
if (((_b = args.partial) === null || _b === void 0 ? void 0 : _b.transactionId) !== undefined) {
cursor = await dbTrx.objectStore('tx_labels_map').index('transactionId').openCursor(args.partial.transactionId);
}
else if (((_c = args.partial) === null || _c === void 0 ? void 0 : _c.txLabelId) !== undefined) {
cursor = await dbTrx.objectStore('tx_labels_map').index('txLabelId').openCursor(args.partial.txLabelId);
}
else {
cursor = await dbTrx.objectStore('tx_labels_map').openCursor();
}
let firstTime = true;
while (cursor) {
if (!firstTime)
cursor = await cursor.continue();
if (!cursor)
break;
firstTime = false;
const r = cursor.value;
if (args.since && args.since > r.updated_at)
continue;
if (args.partial) {
if (args.partial.txLabelId && r.txLabelId !== args.partial.txLabelId)
continue;
if (args.partial.transactionId && r.transactionId !== args.partial.transactionId)
continue;
if (args.partial.created_at && r.created_at.getTime() !== args.partial.created_at.getTime())
continue;
if (args.partial.updated_at && r.updated_at.getTime() !== args.partial.updated_at.getTime())
continue;
if (args.partial.isDeleted !== undefined && r.isDeleted !== args.partial.isDeleted)
continue;
}
if (userId !== undefined) {
const count = await this.countTxLabels({ partial: { userId, txLabelId: r.txLabelId }, trx: args.trx });
if (count === 0)
continue;
}
if (skipped < offset) {
skipped++;
continue;
}
filtered(r);
count++;
if (((_d = args.paged) === null || _d === void 0 ? void 0 : _d.limit) && count >= args.paged.limit)
break;
}
if (!args.trx)
await dbTrx.done;
}
async findTxLabelMaps(args) {
const results = [];
await this.filterTxLabelMaps(args, r => {
results.push(this.validateEntity(r));
});
return results;
}
async countOutputTagMaps(args) {
let count = 0;
await this.filterOutputTagMaps(args, () => {
count++;
});
return count;
}
async countProvenTxReqs(args) {
let count = 0;
await this.filterProvenTxReqs(args, () => {
count++;
});
return count;
}
async countProvenTxs(args) {
let count = 0;
await this.filterProvenTxs(args, () => {
count++;
});
return count;
}
async countTxLabelMaps(args) {
let count = 0;
await this.filterTxLabelMaps(args, () => {
count++;
});
return count;
}
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 dbTrx = this.toDbTrx(['certificates', 'certificate_fields'], 'readwrite', trx);
const store = dbTrx.objectStore('certificates');
try {
const id = Number(await store.add(e));
certificate.certificateId = id;
if (fields) {
for (const field of fields) {
field.certificateId = certificate.certificateId;
field.userId = certificate.userId;
await this.insertCertificateField(field, dbTrx);
}
}
}
finally {
if (!trx)
await dbTrx.done;
}
return certificate.certificateId;
}
async insertCertificateField(certificateField, trx) {
const e = await this.validateEntityForInsert(certificateField, trx);
const dbTrx = this.toDbTrx(['certificate_fields'], 'readwrite', trx);
const store = dbTrx.objectStore('certificate_fields');
try {
await store.add(e);
}
finally {
if (!trx)
await dbTrx.done;
}
}
async insertCommission(commission, trx) {
const e = await this.validateEntityForInsert(commission, trx);
if (e.commissionId === 0)
delete e.commissionId;
const dbTrx = this.toDbTrx(['commissions'], 'readwrite', trx);
const store = dbTrx.objectStore('commissions');
try {
const id = Number(await store.add(e));
commission.commissionId = id;
}
finally {
if (!trx)
await dbTrx.done;
}
return commission.commissionId;
}
async insertMonitorEvent(event, trx) {
const e = await this.validateEntityForInsert(event, trx);
if (e.id === 0)
delete e.id;
const dbTrx = this.toDbTrx(['monitor_events'], 'readwrite', trx);
const store = dbTrx.objectStore('monitor_events');
try {
const id = Number(await store.add(e));
event.id = id;
}
finally {
if (!trx)
await dbTrx.done;
}
return event.id;
}
async insertOutput(output, trx) {
const e = await this.validateEntityForInsert(output, trx);
if (e.outputId === 0)
delete e.outputId;
const dbTrx = this.toDbTrx(['outputs'], 'readwrite', trx);
const store = dbTrx.objectStore('outputs');
try {
const id = Number(await store.add(e));
output.outputId = id;
}
finally {
if (!trx)
await dbTrx.done;
}
return output.outputId;
}
async insertOutputBasket(basket, trx) {
const e = await this.validateEntityForInsert(basket, trx, undefined, ['isDeleted']);
if (e.basketId === 0)
delete e.basketId;
const dbTrx = this.toDbTrx(['output_baskets'], 'readwrite', trx);
const store = dbTrx.objectStore('output_baskets');
try {
const id = Number(await store.add(e));
basket.basketId = id;
}
finally {
if (!trx)
await dbTrx.done;
}
return basket.basketId;
}
async insertOutputTag(tag, trx) {
const e = await this.validateEntityForInsert(tag, trx, undefined, ['isDeleted']);
if (e.outputTagId === 0)
delete e.outputTagId;
const dbTrx = this.toDbTrx(['output_tags'], 'readwrite', trx);
const store = dbTrx.objectStore('output_tags');
try {
const id = Number(await store.add(e));
tag.outputTagId = id;
}
finally {
if (!trx)
await dbTrx.done;
}
return tag.outputTagId;
}
async insertOutputTagMap(tagMap, trx) {
const e = await this.validateEntityForInsert(tagMap, trx, undefined, ['isDeleted']);
const dbTrx = this.toDbTrx(['output_tags_map'], 'readwrite', trx);
const store = dbTrx.objectStore('output_tags_map');
try {
await store.add(e);
}
finally {
if (!trx)
await dbTrx.done;
}
}
async insertProvenTx(tx, trx) {
const e = await this.validateEntityForInsert(tx, trx);
if (e.provenTxId === 0)
delete e.provenTxId;
const dbTrx = this.toDbTrx(['proven_txs'], 'readwrite', trx);
const store = dbTrx.objectStore('proven_txs');
try {
const id = Number(await store.add(e));
tx.provenTxId = id;
}
finally {
if (!trx)
await dbTrx.done;
}
return tx.provenTxId;
}
async insertProvenTxReq(tx, trx) {
const e = await this.validateEntityForInsert(tx, trx);
if (e.provenTxReqId === 0)
delete e.provenTxReqId;
const dbTrx = this.toDbTrx(['proven_tx_reqs'], 'readwrite', trx);
const store = dbTrx.objectStore('proven_tx_reqs');
try {
const id = Number(await store.add(e));
tx.provenTxReqId = id;
}
finally {
if (!trx)
await dbTrx.done;
}
return tx.provenTxReqId;
}
async insertSyncState(syncState, trx) {
const e = await this.validateEntityForInsert(syncState, trx, ['when'], ['init']);
if (e.syncStateId === 0)
delete e.syncStateId;
const dbTrx = this.toDbTrx(['sync_states'], 'readwrite', trx);
const store = dbTrx.objectStore('sync_states');
try {
const id = Number(await store.add(e));
syncState.syncStateId = id;
}
finally {
if (!trx)
await dbTrx.done;
}
return syncState.syncStateId;
}
async insertTransaction(tx, trx) {
const e = await this.validateEntityForInsert(tx, trx);
if (e.transactionId === 0)
delete e.transactionId;
const dbTrx = this.toDbTrx(['transactions'], 'readwrite', trx);
const store = dbTrx.objectStore('transactions');
try {
const id = Number(await store.add(e));
tx.transactionId = id;
}
finally {
if (!trx)
await dbTrx.done;
}
return tx.transactionId;
}
async insertTxLabel(label, trx) {
const e = await this.validateEntityForInsert(label, trx, undefined, ['isDeleted']);
if (e.txLabelId === 0)
delete e.txLabelId;
const dbTrx = this.toDbTrx(['tx_labels'], 'readwrite', trx);
const store = dbTrx.objectStore('tx_labels');
try {
const id = Number(await store.add(e));
label.txLabelId = id;
}
finally {
if (!trx)
await dbTrx.done;
}
return label.txLabelId;
}
async insertTxLabelMap(labelMap, trx) {
const e = await this.validateEntityForInsert(labelMap, trx, undefined, ['isDeleted']);
const dbTrx = this.toDbTrx(['tx_labels_map'], 'readwrite', trx);
const store = dbTrx.objectStore('tx_labels_map');
try {
await store.add(e);
}
finally {
if (!trx)
await dbTrx.done;
}
}
async insertUser(user, trx) {
const e = await this.validateEntityForInsert(user, trx);
if (e.userId === 0)
delete e.userId;
const dbTrx = this.toDbTrx(['users'], 'readwrite', trx);
const store = dbTrx.objectStore('users');
try {
const id = Number(await store.add(e));
user.userId = id;
}
finally {
if (!trx)
await dbTrx.done;
}
return user.userId;
}
async updateIdb(id, update, keyProp, storeName, trx) {
if (update[keyProp] !== undefined && (Array.isArray(id) || update[keyProp] !== id)) {
throw new WERR_errors_1.WERR_INVALID_PARAMETER(`update.${keyProp}`, `undefined`);
}
const u = this.validatePartialForUpdate(update);
const dbTrx = this.toDbTrx([storeName], 'readwrite', trx);
const store = dbTrx.objectStore(storeName);
const ids = Array.isArray(id) ? id : [id];
try {
for (const i of ids) {
const e = await store.get(i);
if (!e)
throw new WERR_errors_1.WERR_INVALID_PARAMETER('id', `an existing record to update ${keyProp} ${i} not found`);
const v = {
...e,
...u
};
const uid = await store.put(v);
if (uid !== i)
throw new WERR_errors_1.WERR_INTERNAL(`updated id ${uid} does not match original ${id}`);
}
}
finally {
if (!trx)
await dbTrx.done;
}
return 1;
}
async updateIdbKey(key, update, keyProps, storeName, trx) {
if (key.length !== keyProps.length)
throw new WERR_errors_1.WERR_INTERNAL(`key.length ${key.length} !== keyProps.length ${keyProps.length}`);
for (let i = 0; i < key.length; i++) {
if (update[keyProps[i]] !== undefined && update[keyProps[i]] !== key[i]) {
throw new WERR_errors_1.WERR_INVALID_PARAMETER(`update.${keyProps[i]}`, `undefined`);
}
}
const u = this.validatePartialForUpdate(update);
const dbTrx = this.toDbTrx([storeName], 'readwrite', trx);
const store = dbTrx.objectStore(storeName);
try {
const e = await store.get(key);
if (!e)
throw new WERR_errors_1.WERR_INVALID_PARAMETER('key', `an existing record to update ${keyProps.join(',')} ${key.join(',')} not found`);
const v = {
...e,
...u
};
const uid = await store.put(v);
for (let i = 0; i < key.length; i++) {
if (uid[i] !== key[i])
throw new WERR_errors_1.WERR_INTERNAL(`updated key ${uid[i]} does not match original ${key[i]}`);
}
}
finally {
if (!trx)
await dbTrx.done;
}
return 1;
}
async updateCertificate(id, update, trx) {
return this.updateIdb(id, update, 'certificateId', 'certificates', trx);
}
async updateCertificateField(certificateId, fieldName, update, trx) {
return this.updateIdbKey([certificateId, fieldName], update, ['certificateId', 'fieldName'], 'certificate_fields', trx);
}
async updateCommission(id, update, trx) {
return this.updateIdb(id, update, 'commissionId', 'commissions', trx);
}
async updateMonitorEvent(id, update, trx) {
return this.updateIdb(id, update, 'id', 'monitor_events', trx);
}
async updateOutput(id, update, trx) {
return this.updateIdb(id, update, 'outputId', 'outputs', trx);
}
async updateOutputBasket(id, update, trx) {
return this.updateIdb(id, update, 'basketId', 'output_baskets', trx);
}
async updateOutputTag(id, update, trx) {
return this.updateIdb(id, update, 'outputTagId', 'output_tags', trx);
}
async updateProvenTx(id, update, trx) {
return this.updateIdb(id, update, 'provenTxId', 'proven_txs', trx);
}
async updateProvenTxReq(id, update, trx) {
return this.updateIdb(id, update, 'provenTxReqId', 'proven_tx_reqs', trx);
}
async updateSyncState(id, update, trx) {
return this.updateIdb(id, update, 'syncStateId', 'sync_states', trx);
}
async updateTransaction(id, update, trx) {
return this.updateIdb(id, update, 'transactionId', 'transactions', trx);
}
async updateTxLabel(id, update, trx) {
return this.updateIdb(id, update, 'txLabelId', 'tx_labels', trx);
}
async updateUser(id, update, trx) {
return this.updateIdb(id, update, 'userId', 'users', trx);
}
async updateOutputTagMap(outputId, tagId, update, trx) {
return this.updateIdbKey([tagId, outputId], update, ['outputTagId', 'outputId'], 'output_tags_map', trx);
}
async updateTxLabelMap(transactionId, txLabelId, update, trx) {
return this.updateIdbKey([txLabelId, transactionId], update, ['txLabelId', 'transactionId'], 'tx_labels_map', trx);
}
//
// StorageReader abstract methods
//
async destroy() {
if (this.db) {
this.db.close();
}
this.db = undefined;
this._settings = undefined;
}
/**
* @param scope
* @param trx
* @returns
*/
async transaction(scope, trx) {
if (trx)
return await scope(trx);
const stores = this.allStores;
const db = await this.verifyDB();
const tx = db.transaction(stores, 'readwrite');
try {
const r = await scope(tx);
await tx.done;
return r;
}
catch (err) {
tx.abort();
await tx.done;
throw err;
}
}
async filterCertificateFields(args, filtered) {
var _a, _b, _c, _d;
const offset = ((_a = args.paged) === null || _a === void 0 ? void 0 : _a.offset) || 0;
let skipped = 0;
let count = 0;
const dbTrx = this.toDbTrx(['certificate_fields'], 'readonly', args.trx);
let cursor;
if (((_b = args.partial) === null || _b === void 0 ? void 0 : _b.certificateId) !== undefined) {
cursor = await dbTrx
.objectStore('certificate_fields')
.index('certificateId')
.openCursor(args.partial.certificateId);
}
else if (((_c = args.partial) === null || _c === void 0 ? void 0 : _c.userId) !== undefined) {
cursor = await dbTrx.objectStore('certificate_fields').index('userId').openCursor(args.partial.userId);
}
else {
cursor = await dbTrx.objectStore('certificate_fields').openCursor();
}
let firstTime = true;
while (cursor) {
if (!firstTime)
cursor = await cursor.continue();
if (!cursor)
break;
firstTime = false;
const r = cursor.value;
if (args.since && args.since > r.updated_at)
continue;
if (args.partial) {
if (args.partial.userId && r.userId !== args.partial.userId)
continue;
if (args.partial.certificateId && r.certificateId !== args.partial.certificateId)
continue;
if (args.partial.created_at && r.created_at.getTime() !== args.partial.created_at.getTime())
continue;
if (args.partial.updated_at && r.updated_at.getTime() !== args.partial.updated_at.getTime())
continue;
if (args.partial.fieldName && r.fieldName !== args.partial.fieldName)
continue;
if (args.partial.fieldValue && r.fieldValue !== args.partial.fieldValue)
continue;
if (args.partial.masterKey && r.masterKey !== args.partial.masterKey)
continue;
}
if (skipped < offset) {
skipped++;
continue;
}
filtered(r);
count++;
if (((_d = args.paged) === null || _d === void 0 ? void 0 : _d.limit) && count >= args.paged.limit)
break;
}
if (!args.trx)
await dbTrx.done;
}
async findCertificateFields(args) {
const result = [];
await this.filterCertificateFields(args, r => {
result.push(this.validateEntity(r));