UNPKG

@bsv/wallet-toolbox-client

Version:
410 lines 21.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.KnexMigrations = void 0; exports.determineDBType = determineDBType; const StorageKnex_1 = require("../StorageKnex"); const WalletError_1 = require("../../sdk/WalletError"); const WERR_errors_1 = require("../../sdk/WERR_errors"); class KnexMigrations { /** * @param chain * @param storageName human readable name for this storage instance * @param maxOutputScriptLength limit for scripts kept in outputs table, longer scripts will be pulled from rawTx */ constructor(chain, storageName, storageIdentityKey, maxOutputScriptLength) { this.chain = chain; this.storageName = storageName; this.storageIdentityKey = storageIdentityKey; this.maxOutputScriptLength = maxOutputScriptLength; this.migrations = {}; this.migrations = this.setupMigrations(chain, storageName, storageIdentityKey, maxOutputScriptLength); } async getMigrations() { return Object.keys(this.migrations).sort(); } getMigrationName(migration) { return migration; } async getMigration(migration) { return this.migrations[migration]; } async getLatestMigration() { const ms = await this.getMigrations(); return ms[ms.length - 1]; } static async latestMigration() { const km = new KnexMigrations('test', 'dummy', '1'.repeat(64), 100); return await km.getLatestMigration(); } setupMigrations(chain, storageName, storageIdentityKey, maxOutputScriptLength) { const migrations = {}; const addTimeStamps = (knex, table, dbtype) => { if (dbtype === 'MySQL') { table.timestamp('created_at', { precision: 3 }).defaultTo(knex.fn.now(3)).notNullable(); table.timestamp('updated_at', { precision: 3 }).defaultTo(knex.fn.now(3)).notNullable(); } else { table.timestamp('created_at', { precision: 3 }).defaultTo(knex.fn.now()).notNullable(); table.timestamp('updated_at', { precision: 3 }).defaultTo(knex.fn.now()).notNullable(); } }; migrations['2025-05-13-001 add monitor events event index'] = { async up(knex) { await knex.schema.alterTable('monitor_events', table => { table.index('event'); }); }, async down(knex) { await knex.schema.alterTable('monitor_events', table => { table.dropIndex('event'); }); } }; migrations['2025-03-03-001 descriptions to 2000'] = { async up(knex) { await knex.schema.alterTable('transactions', table => { table.string('description', 2048).alter(); }); await knex.schema.alterTable('outputs', table => { table.string('outputDescription', 2048).alter(); table.string('spendingDescription', 2048).alter(); }); }, async down(knex) { } }; migrations['2025-03-01-001 reset req history'] = { async up(knex) { const storage = new StorageKnex_1.StorageKnex({ ...StorageKnex_1.StorageKnex.defaultOptions(), chain: chain, knex }); const settings = await storage.makeAvailable(); await knex.raw(`update proven_tx_reqs set history = '{}'`); }, async down(knex) { // No way back... } }; migrations['2025-02-28-001 derivations to 200'] = { async up(knex) { await knex.schema.alterTable('outputs', table => { table.string('derivationPrefix', 200).alter(); table.string('derivationSuffix', 200).alter(); }); }, async down(knex) { await knex.schema.alterTable('outputs', table => { table.string('derivationPrefix', 32).alter(); table.string('derivationSuffix', 32).alter(); }); } }; migrations['2025-02-22-001 nonNULL activeStorage'] = { async up(knex) { const storage = new StorageKnex_1.StorageKnex({ ...StorageKnex_1.StorageKnex.defaultOptions(), chain: chain, knex }); const settings = await storage.makeAvailable(); await knex.raw(`update users set activeStorage = '${settings.storageIdentityKey}' where activeStorage is NULL`); await knex.schema.alterTable('users', table => { table.string('activeStorage').notNullable().alter(); }); }, async down(knex) { await knex.schema.alterTable('users', table => { table.string('activeStorage').nullable().alter(); }); } }; migrations['2025-01-21-001 add activeStorage to users'] = { async up(knex) { await knex.schema.alterTable('users', table => { table.string('activeStorage', 130).nullable().defaultTo(null); }); }, async down(knex) { await knex.schema.alterTable('users', table => { table.dropColumn('activeStorage'); }); } }; migrations['2024-12-26-001 initial migration'] = { async up(knex) { const dbtype = await determineDBType(knex); await knex.schema.createTable('proven_txs', table => { addTimeStamps(knex, table, dbtype); table.increments('provenTxId').notNullable(); table.string('txid', 64).notNullable().unique(); table.integer('height').unsigned().notNullable(); table.integer('index').unsigned().notNullable(); table.binary('merklePath').notNullable(); table.binary('rawTx').notNullable(); table.string('blockHash', 64).notNullable(); table.string('merkleRoot', 64).notNullable(); }); await knex.schema.createTable('proven_tx_reqs', table => { addTimeStamps(knex, table, dbtype); table.increments('provenTxReqId'); table.integer('provenTxId').unsigned().references('provenTxId').inTable('proven_txs'); table.string('status', 16).notNullable().defaultTo('unknown'); table.integer('attempts').unsigned().defaultTo(0).notNullable(); table.boolean('notified').notNullable().defaultTo(false); table.string('txid', 64).notNullable().unique(); table.string('batch', 64).nullable(); table.text('history', 'longtext').notNullable().defaultTo('{}'); table.text('notify', 'longtext').notNullable().defaultTo('{}'); table.binary('rawTx').notNullable(); table.binary('inputBEEF'); table.index('status'); table.index('batch'); }); await knex.schema.createTable('users', table => { addTimeStamps(knex, table, dbtype); table.increments('userId'); table.string('identityKey', 130).notNullable().unique(); }); await knex.schema.createTable('certificates', table => { addTimeStamps(knex, table, dbtype); table.increments('certificateId'); table.integer('userId').unsigned().references('userId').inTable('users').notNullable(); table.string('serialNumber', 100).notNullable(); table.string('type', 100).notNullable(); table.string('certifier', 100).notNullable(); table.string('subject', 100).notNullable(); table.string('verifier', 100).nullable(); table.string('revocationOutpoint', 100).notNullable(); table.string('signature', 255).notNullable(); table.boolean('isDeleted').notNullable().defaultTo(false); table.unique(['userId', 'type', 'certifier', 'serialNumber']); }); await knex.schema.createTable('certificate_fields', table => { addTimeStamps(knex, table, dbtype); table.integer('userId').unsigned().references('userId').inTable('users').notNullable(); table.integer('certificateId').unsigned().references('certificateId').inTable('certificates').notNullable(); table.string('fieldName', 100).notNullable(); table.string('fieldValue').notNullable(); table.string('masterKey', 255).defaultTo('').notNullable(); table.unique(['fieldName', 'certificateId']); }); await knex.schema.createTable('output_baskets', table => { addTimeStamps(knex, table, dbtype); table.increments('basketId'); table.integer('userId').unsigned().references('userId').inTable('users').notNullable(); table.string('name', 300).notNullable(); table.integer('numberOfDesiredUTXOs', 6).defaultTo(6).notNullable(); table.integer('minimumDesiredUTXOValue', 15).defaultTo(10000).notNullable(); table.boolean('isDeleted').notNullable().defaultTo(false); table.unique(['name', 'userId']); }); await knex.schema.createTable('transactions', table => { addTimeStamps(knex, table, dbtype); table.increments('transactionId'); table.integer('userId').unsigned().references('userId').inTable('users').notNullable(); table.integer('provenTxId').unsigned().references('provenTxId').inTable('proven_txs'); table.string('status', 64).notNullable(); table.string('reference', 64).notNullable().unique(); table.boolean('isOutgoing').notNullable(); table.bigint('satoshis').defaultTo(0).notNullable(); table.integer('version').unsigned().nullable(); table.integer('lockTime').unsigned().nullable(); table.string('description', 500).notNullable(); table.string('txid', 64); table.binary('inputBEEF'); table.binary('rawTx'); table.index('status'); }); await knex.schema.createTable('commissions', table => { addTimeStamps(knex, table, dbtype); table.increments('commissionId'); table.integer('userId').unsigned().references('userId').inTable('users').notNullable(); table .integer('transactionId') .unsigned() .references('transactionId') .inTable('transactions') .notNullable() .unique(); table.integer('satoshis', 15).notNullable(); table.string('keyOffset', 130).notNullable(); table.boolean('isRedeemed').defaultTo(false).notNullable(); table.binary('lockingScript').notNullable(); table.index('transactionId'); }); await knex.schema.createTable('outputs', table => { addTimeStamps(knex, table, dbtype); table.increments('outputId'); table.integer('userId').unsigned().references('userId').inTable('users').notNullable(); table.integer('transactionId').unsigned().references('transactionId').inTable('transactions').notNullable(); table.integer('basketId').unsigned().references('basketId').inTable('output_baskets'); table.boolean('spendable').defaultTo(false).notNullable(); table.boolean('change').defaultTo(false).notNullable(); table.integer('vout', 10).notNullable(); table.bigint('satoshis').notNullable(); table.string('providedBy', 130).notNullable(); table.string('purpose', 20).notNullable(); table.string('type', 50).notNullable(); table.string('outputDescription', 300); // allow extra room for encryption and imports table.string('txid', 64); table.string('senderIdentityKey', 130); table.string('derivationPrefix', 32); table.string('derivationSuffix', 32); table.string('customInstructions', 2500); table.integer('spentBy').unsigned().references('transactionId').inTable('transactions'); table.integer('sequenceNumber').unsigned().nullable(); table.string('spendingDescription'); table.bigint('scriptLength').unsigned().nullable(); table.bigint('scriptOffset').unsigned().nullable(); table.binary('lockingScript'); table.unique(['transactionId', 'vout', 'userId']); }); await knex.schema.createTable('output_tags', table => { addTimeStamps(knex, table, dbtype); table.increments('outputTagId'); table.integer('userId').unsigned().references('userId').inTable('users').notNullable(); table.string('tag', 150).notNullable(); table.boolean('isDeleted').notNullable().defaultTo(false); table.unique(['tag', 'userId']); }); await knex.schema.createTable('output_tags_map', table => { addTimeStamps(knex, table, dbtype); table.integer('outputTagId').unsigned().references('outputTagId').inTable('output_tags').notNullable(); table.integer('outputId').unsigned().references('outputId').inTable('outputs').notNullable(); table.boolean('isDeleted').notNullable().defaultTo(false); table.unique(['outputTagId', 'outputId']); table.index('outputId'); }); await knex.schema.createTable('tx_labels', table => { addTimeStamps(knex, table, dbtype); table.increments('txLabelId'); table.integer('userId').unsigned().references('userId').inTable('users').notNullable(); table.string('label', 300).notNullable(); table.boolean('isDeleted').notNullable().defaultTo(false); table.unique(['label', 'userId']); }); await knex.schema.createTable('tx_labels_map', table => { addTimeStamps(knex, table, dbtype); table.integer('txLabelId').unsigned().references('txLabelId').inTable('tx_labels').notNullable(); table.integer('transactionId').unsigned().references('transactionId').inTable('transactions').notNullable(); table.boolean('isDeleted').notNullable().defaultTo(false); table.unique(['txLabelId', 'transactionId']); table.index('transactionId'); }); await knex.schema.createTable('monitor_events', table => { addTimeStamps(knex, table, dbtype); table.increments('id'); table.string('event', 64).notNullable(); table.text('details', 'longtext').nullable(); }); await knex.schema.createTable('settings', table => { addTimeStamps(knex, table, dbtype); table.string('storageIdentityKey', 130).notNullable(); table.string('storageName', 128).notNullable(); table.string('chain', 10).notNullable(); table.string('dbtype', 10).notNullable(); table.integer('maxOutputScript', 15).notNullable(); }); await knex.schema.createTable('sync_states', table => { addTimeStamps(knex, table, dbtype); table.increments('syncStateId'); table.integer('userId').unsigned().notNullable().references('userId').inTable('users'); table.string('storageIdentityKey', 130).notNullable().defaultTo(''); table.string('storageName').notNullable(); table.string('status').notNullable().defaultTo('unknown'); table.boolean('init').notNullable().defaultTo(false); table.string('refNum', 100).notNullable().unique(); table.text('syncMap', 'longtext').notNullable(); table.dateTime('when'); table.bigint('satoshis'); table.text('errorLocal', 'longtext'); table.text('errorOther', 'longtext'); table.index('status'); table.index('refNum'); }); if (dbtype === 'MySQL') { await knex.raw('ALTER TABLE proven_tx_reqs MODIFY COLUMN rawTx LONGBLOB'); await knex.raw('ALTER TABLE proven_tx_reqs MODIFY COLUMN inputBEEF LONGBLOB'); await knex.raw('ALTER TABLE proven_txs MODIFY COLUMN rawTx LONGBLOB'); await knex.raw('ALTER TABLE transactions MODIFY COLUMN rawTx LONGBLOB'); await knex.raw('ALTER TABLE transactions MODIFY COLUMN inputBEEF LONGBLOB'); await knex.raw('ALTER TABLE outputs MODIFY COLUMN lockingScript LONGBLOB'); } else { await knex.schema.alterTable('proven_tx_reqs', table => { table.binary('rawTx', 10000000).alter(); table.binary('beef', 10000000).alter(); }); await knex.schema.alterTable('outputs', table => { table.binary('lockingScript', 10000000).alter(); }); await knex.schema.alterTable('proven_txs', table => { table.binary('rawTx', 10000000).alter(); }); await knex.schema.alterTable('transactions', table => { table.binary('rawTx', 10000000).alter(); table.binary('beef', 10000000).alter(); }); } await knex('settings').insert({ storageIdentityKey, storageName, chain, dbtype, maxOutputScript: maxOutputScriptLength }); }, async down(knex) { await knex.schema.dropTable('sync_states'); await knex.schema.dropTable('settings'); await knex.schema.dropTable('monitor_events'); await knex.schema.dropTable('certificate_fields'); await knex.schema.dropTable('certificates'); await knex.schema.dropTable('commissions'); await knex.schema.dropTable('output_tags_map'); await knex.schema.dropTable('output_tags'); await knex.schema.dropTable('outputs'); await knex.schema.dropTable('output_baskets'); await knex.schema.dropTable('tx_labels_map'); await knex.schema.dropTable('tx_labels'); await knex.schema.dropTable('transactions'); await knex.schema.dropTable('users'); await knex.schema.dropTable('proven_tx_reqs'); await knex.schema.dropTable('proven_txs'); } }; return migrations; } } exports.KnexMigrations = KnexMigrations; /** * @param knex * @returns {DBType} connected database engine variant */ async function determineDBType(knex) { try { const q = `SELECT CASE WHEN (SELECT VERSION() LIKE '%MariaDB%') = 1 THEN 'Unknown' WHEN (SELECT VERSION()) IS NOT NULL THEN 'MySQL' ELSE 'Unknown' END AS database_type;`; let r = await knex.raw(q); if (!r[0]['database_type']) r = r[0]; if (r['rows']) r = r.rows; const dbtype = r[0].database_type; if (dbtype === 'Unknown') throw new WERR_errors_1.WERR_NOT_IMPLEMENTED(`Attempting to create database on unsuported engine.`); return dbtype; } catch (eu) { const e = WalletError_1.WalletError.fromUnknown(eu); if (e.code === 'SQLITE_ERROR') return 'SQLite'; throw new WERR_errors_1.WERR_NOT_IMPLEMENTED(`Attempting to create database on unsuported engine.`); } } //# sourceMappingURL=KnexMigrations.js.map