UNPKG

@bsv/wallet-toolbox-client

Version:
1,193 lines (1,192 loc) 96.9 kB
"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));