@bopen-io/wallet-toolbox 2.14.5 → 2.15.0-bopen.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +47 -2
  2. package/README.md +73 -7
  3. package/docs/client.md +3020 -2729
  4. package/docs/monitor.md +164 -153
  5. package/docs/services.md +856 -710
  6. package/docs/setup.md +46 -1
  7. package/docs/storage.md +1654 -1636
  8. package/docs/wallet.md +2090 -1842
  9. package/out/src/Setup.d.ts +10 -0
  10. package/out/src/Setup.d.ts.map +1 -1
  11. package/out/src/Setup.js +24 -0
  12. package/out/src/Setup.js.map +1 -1
  13. package/out/src/monitor/MonitorDaemon.d.ts +2 -0
  14. package/out/src/monitor/MonitorDaemon.d.ts.map +1 -1
  15. package/out/src/monitor/MonitorDaemon.js +9 -1
  16. package/out/src/monitor/MonitorDaemon.js.map +1 -1
  17. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageKnex.d.ts +5 -0
  18. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageKnex.d.ts.map +1 -1
  19. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageKnex.js +11 -3
  20. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageKnex.js.map +1 -1
  21. package/out/src/storage/StorageKnex.d.ts +3 -0
  22. package/out/src/storage/StorageKnex.d.ts.map +1 -1
  23. package/out/src/storage/StorageKnex.js +115 -207
  24. package/out/src/storage/StorageKnex.js.map +1 -1
  25. package/out/src/storage/adminServer/adminServer.d.ts.map +1 -1
  26. package/out/src/storage/adminServer/adminServer.js +3 -41
  27. package/out/src/storage/adminServer/adminServer.js.map +1 -1
  28. package/out/src/storage/adminServer/reqReviewQuery.d.ts +41 -0
  29. package/out/src/storage/adminServer/reqReviewQuery.d.ts.map +1 -0
  30. package/out/src/storage/adminServer/reqReviewQuery.js +67 -0
  31. package/out/src/storage/adminServer/reqReviewQuery.js.map +1 -0
  32. package/out/src/storage/index.all.d.ts +1 -0
  33. package/out/src/storage/index.all.d.ts.map +1 -1
  34. package/out/src/storage/index.all.js +1 -0
  35. package/out/src/storage/index.all.js.map +1 -1
  36. package/out/src/storage/knexPostgres.d.ts +16 -0
  37. package/out/src/storage/knexPostgres.d.ts.map +1 -0
  38. package/out/src/storage/knexPostgres.js +34 -0
  39. package/out/src/storage/knexPostgres.js.map +1 -0
  40. package/out/src/storage/methods/listActionsKnex.d.ts.map +1 -1
  41. package/out/src/storage/methods/listActionsKnex.js +0 -1
  42. package/out/src/storage/methods/listActionsKnex.js.map +1 -1
  43. package/out/src/storage/methods/listOutputsKnex.d.ts.map +1 -1
  44. package/out/src/storage/methods/listOutputsKnex.js +0 -1
  45. package/out/src/storage/methods/listOutputsKnex.js.map +1 -1
  46. package/out/src/storage/methods/purgeData.d.ts.map +1 -1
  47. package/out/src/storage/methods/purgeData.js +0 -3
  48. package/out/src/storage/methods/purgeData.js.map +1 -1
  49. package/out/src/storage/methods/reviewStatus.d.ts.map +1 -1
  50. package/out/src/storage/methods/reviewStatus.js +0 -3
  51. package/out/src/storage/methods/reviewStatus.js.map +1 -1
  52. package/out/src/storage/portable/index.js +38 -12
  53. package/out/src/storage/portable/index.js.map +1 -1
  54. package/out/src/storage/remoting/StorageServer.d.ts +17 -4
  55. package/out/src/storage/remoting/StorageServer.d.ts.map +1 -1
  56. package/out/src/storage/remoting/StorageServer.js +22 -13
  57. package/out/src/storage/remoting/StorageServer.js.map +1 -1
  58. package/out/src/storage/schema/KnexMigrations.d.ts +7 -2
  59. package/out/src/storage/schema/KnexMigrations.d.ts.map +1 -1
  60. package/out/src/storage/schema/KnexMigrations.js +162 -60
  61. package/out/src/storage/schema/KnexMigrations.js.map +1 -1
  62. package/package.json +5 -4
@@ -12,6 +12,7 @@ const WERR_errors_1 = require("../sdk/WERR_errors");
12
12
  const utilityHelpers_1 = require("../utility/utilityHelpers");
13
13
  const managedChange_1 = require("./methods/managedChange");
14
14
  const preparedBeef_1 = require("./methods/preparedBeef");
15
+ const knexPostgres_1 = require("./knexPostgres");
15
16
  // Keep bulk statements below conservative SQLite/MySQL bind-parameter
16
17
  // ceilings. The surrounding transaction still makes a complete pack atomic.
17
18
  const ACTION_BATCH_BLOB_SQL_CHUNK = 500;
@@ -47,7 +48,7 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
47
48
  if (options.knex == null)
48
49
  throw new WERR_errors_1.WERR_INVALID_PARAMETER('options.knex', 'valid');
49
50
  this.knex = options.knex;
50
- parsePostgresInt8AsNumber(this.knex);
51
+ (0, knexPostgres_1.usePostgresInt8Numbers)(this.knex);
51
52
  this.preparedBeefPolicy = (0, preparedBeef_1.validatePreparedBeefPolicy)(options.preparedBeef);
52
53
  this.preparedBeefCoordinator = new preparedBeef_1.PreparedBeefCoordinator(this);
53
54
  if (this.telemetry.enabled) {
@@ -260,7 +261,6 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
260
261
  .whereNull('pb.preparedBeefId')
261
262
  .select('o.userId', 'o.txid as rootTxid')
262
263
  .groupBy('o.userId', 'o.txid')
263
- // ?? quotes identifiers: Postgres lowercases unquoted names, so camelCase columns must be quoted.
264
264
  .orderByRaw('MIN(??)', ['o.outputId'])
265
265
  .limit(limit));
266
266
  return rows;
@@ -387,7 +387,6 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
387
387
  void this.whereExists(k
388
388
  .select('*')
389
389
  .from('transactions')
390
- // ?? quotes identifiers: Postgres lowercases unquoted names, so camelCase columns must be quoted.
391
390
  .whereRaw('?? = ?? and ?? = ?', [
392
391
  'proven_txs.provenTxId',
393
392
  'transactions.provenTxId',
@@ -415,7 +414,6 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
415
414
  void this.whereExists(k
416
415
  .select('*')
417
416
  .from('transactions')
418
- // ?? quotes identifiers: Postgres lowercases unquoted names, so camelCase columns must be quoted.
419
417
  .whereRaw('proven_tx_reqs.txid = transactions.txid and ?? = ?', ['transactions.userId', args.userId]));
420
418
  });
421
419
  if (args.paged != null) {
@@ -437,7 +435,6 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
437
435
  let q = k('tx_labels_map').whereExists(k
438
436
  .select('*')
439
437
  .from('tx_labels')
440
- // ?? quotes identifiers: Postgres lowercases unquoted names, so camelCase columns must be quoted.
441
438
  .whereRaw('?? = ?? and ?? = ?', [
442
439
  'tx_labels.txLabelId',
443
440
  'tx_labels_map.txLabelId',
@@ -463,7 +460,6 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
463
460
  let q = k('output_tags_map').whereExists(k
464
461
  .select('*')
465
462
  .from('output_tags')
466
- // ?? quotes identifiers: Postgres lowercases unquoted names, so camelCase columns must be quoted.
467
463
  .whereRaw('?? = ?? and ?? = ?', [
468
464
  'output_tags.outputTagId',
469
465
  'output_tags_map.outputTagId',
@@ -536,16 +532,20 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
536
532
  /**
537
533
  * Insert one row and return its auto-increment primary key. MySQL and SQLite
538
534
  * report the id as the insert result; Postgres requires a RETURNING clause.
535
+ *
536
+ * `recoverable` marks inserts that the findOrInsert helpers retry as a find
537
+ * after a duplicate-key error, in the same transaction; see `inSavepoint`.
539
538
  */
540
- async insertReturningId(table, idColumn, row, trx) {
539
+ async insertReturningId(table, idColumn, row, trx, recoverable = false) {
541
540
  if (this.dbtype !== 'Postgres') {
542
541
  const [id] = await this.toDb(trx)(table).insert(row);
543
542
  return id;
544
543
  }
545
- return await this.inSavepoint(trx, async (db) => {
544
+ const insert = async (db) => {
546
545
  const [r] = await db(table).insert(row).returning(idColumn);
547
546
  return r[idColumn];
548
- });
547
+ };
548
+ return recoverable ? await this.inSavepoint(trx, insert) : await insert(this.toDb(trx));
549
549
  }
550
550
  /**
551
551
  * The findOrInsert helpers catch a failed insert and find the row again in
@@ -562,7 +562,7 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
562
562
  const e = await this.validateEntityForInsert(tx, trx);
563
563
  if (e.provenTxId === 0)
564
564
  delete e.provenTxId;
565
- const id = await this.insertReturningId('proven_txs', 'provenTxId', e, trx);
565
+ const id = await this.insertReturningId('proven_txs', 'provenTxId', e, trx, true);
566
566
  tx.provenTxId = id;
567
567
  return tx.provenTxId;
568
568
  }
@@ -690,7 +690,7 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
690
690
  const e = await this.validateEntityForInsert(tx, trx);
691
691
  if (e.provenTxReqId === 0)
692
692
  delete e.provenTxReqId;
693
- const id = await this.insertReturningId('proven_tx_reqs', 'provenTxReqId', e, trx);
693
+ const id = await this.insertReturningId('proven_tx_reqs', 'provenTxReqId', e, trx, true);
694
694
  tx.provenTxReqId = id;
695
695
  return tx.provenTxReqId;
696
696
  }
@@ -737,7 +737,7 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
737
737
  const e = await this.validateEntityForInsert(basket, trx, undefined, ['isDeleted']);
738
738
  if (e.basketId === 0)
739
739
  delete e.basketId;
740
- const id = await this.insertReturningId('output_baskets', 'basketId', e, trx);
740
+ const id = await this.insertReturningId('output_baskets', 'basketId', e, trx, true);
741
741
  basket.basketId = id;
742
742
  return basket.basketId;
743
743
  }
@@ -745,7 +745,7 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
745
745
  const e = await this.validateEntityForInsert(tx, trx);
746
746
  if (e.transactionId === 0)
747
747
  delete e.transactionId;
748
- const id = await this.insertReturningId('transactions', 'transactionId', e, trx);
748
+ const id = await this.insertReturningId('transactions', 'transactionId', e, trx, true);
749
749
  tx.transactionId = id;
750
750
  return tx.transactionId;
751
751
  }
@@ -782,7 +782,7 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
782
782
  const e = await this.validateEntityForInsert(tag, trx, undefined, ['isDeleted']);
783
783
  if (e.outputTagId === 0)
784
784
  delete e.outputTagId;
785
- const id = await this.insertReturningId('output_tags', 'outputTagId', e, trx);
785
+ const id = await this.insertReturningId('output_tags', 'outputTagId', e, trx, true);
786
786
  tag.outputTagId = id;
787
787
  return tag.outputTagId;
788
788
  }
@@ -794,7 +794,7 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
794
794
  const e = await this.validateEntityForInsert(label, trx, undefined, ['isDeleted']);
795
795
  if (e.txLabelId === 0)
796
796
  delete e.txLabelId;
797
- const id = await this.insertReturningId('tx_labels', 'txLabelId', e, trx);
797
+ const id = await this.insertReturningId('tx_labels', 'txLabelId', e, trx, true);
798
798
  label.txLabelId = id;
799
799
  return label.txLabelId;
800
800
  }
@@ -1364,13 +1364,27 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
1364
1364
  if (isSQLite) {
1365
1365
  await this.knex.raw('PRAGMA foreign_keys = OFF;');
1366
1366
  }
1367
+ const isPostgres = this.knex.client.dialect === 'postgresql';
1367
1368
  try {
1368
1369
  const config = {
1369
- migrationSource: new KnexMigrations_1.KnexMigrations(this.chain, storageName, storageIdentityKey, 1024),
1370
+ migrationSource: new KnexMigrations_1.KnexMigrations(this.chain, storageName, storageIdentityKey, 1024, isPostgres ? 'Postgres' : undefined),
1370
1371
  // Keep DDL and its migration journal entry in the same transaction.
1371
1372
  disableTransactions: false
1372
1373
  };
1373
- await this.knex.migrate.latest(config);
1374
+ if (isPostgres) {
1375
+ // Postgres index migrations run outside a transaction (CREATE INDEX
1376
+ // CONCURRENTLY). When any pending migration does, knex's latest()
1377
+ // journals every migration of the batch outside its transaction, so
1378
+ // run one migration per up() to keep the others atomic with their journal row.
1379
+ for (;;) {
1380
+ const [, applied] = (await this.knex.migrate.up(config));
1381
+ if (applied.length === 0)
1382
+ break;
1383
+ }
1384
+ }
1385
+ else {
1386
+ await this.knex.migrate.latest(config);
1387
+ }
1374
1388
  return await this.knex.migrate.currentVersion(config);
1375
1389
  }
1376
1390
  finally {
@@ -1605,7 +1619,6 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
1605
1619
  void this.select(1)
1606
1620
  .from('action_batch_outputs as abo')
1607
1621
  .join('action_batches as ab', 'abo.actionBatchId', 'ab.actionBatchId')
1608
- // ?? quotes identifiers: Postgres lowercases unquoted names, so camelCase columns must be quoted.
1609
1622
  .whereRaw('?? = ??', ['abo.outputId', 'o.outputId'])
1610
1623
  .whereIn('ab.status', ['active', 'prepared'])
1611
1624
  .where('ab.expiresAt', '>', now)
@@ -1640,7 +1653,6 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
1640
1653
  void this.select(1)
1641
1654
  .from('action_batch_outputs as abo')
1642
1655
  .join('action_batches as ab', 'abo.actionBatchId', 'ab.actionBatchId')
1643
- // ?? quotes identifiers: Postgres lowercases unquoted names, so camelCase columns must be quoted.
1644
1656
  .whereRaw('?? = ??', ['abo.outputId', 'o.outputId'])
1645
1657
  .whereIn('ab.status', ['active', 'prepared'])
1646
1658
  .where('ab.expiresAt', '>', now)
@@ -1675,7 +1687,6 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
1675
1687
  void this.select(1)
1676
1688
  .from('action_batch_outputs as abo')
1677
1689
  .join('action_batches as ab', 'abo.actionBatchId', 'ab.actionBatchId')
1678
- // ?? quotes identifiers: Postgres lowercases unquoted names, so camelCase columns must be quoted.
1679
1690
  .whereRaw('?? = ??', ['abo.outputId', 'o.outputId'])
1680
1691
  .whereIn('ab.status', ['active', 'prepared'])
1681
1692
  .where('ab.expiresAt', '>', now)
@@ -1865,7 +1876,6 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
1865
1876
  const status = ['completed', 'unproven'];
1866
1877
  if (!excludeSending)
1867
1878
  status.push('sending');
1868
- let reservedConcurrently = false;
1869
1879
  const r = await this.knex.transaction(async (trx) => {
1870
1880
  const baseQuery = () => trx('outputs as o')
1871
1881
  .join('transactions as t', 'o.transactionId', 't.transactionId')
@@ -1884,36 +1894,42 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
1884
1894
  .whereNot('o.derivationSuffix', '')
1885
1895
  .whereNull('o.spentBy')
1886
1896
  .whereNotExists(function () {
1887
- // ?? quotes identifiers: Postgres lowercases unquoted names, so camelCase columns must be quoted.
1888
1897
  void this.select(1).from('action_batch_outputs as abo').whereRaw('?? = ??', ['abo.outputId', 'o.outputId']);
1889
1898
  })
1890
1899
  .whereIn('t.status', status)
1891
1900
  .select('o.*')
1892
1901
  .forUpdate();
1893
- let output;
1894
- if (exactSatoshis !== undefined) {
1895
- output = await baseQuery().where('o.satoshis', exactSatoshis).orderBy('o.outputId', 'asc').first();
1896
- }
1897
- output ??= await baseQuery()
1898
- .where('o.satoshis', '>=', targetSatoshis)
1899
- .orderBy('o.satoshis', 'asc')
1900
- .orderBy('o.outputId', 'asc')
1901
- .first();
1902
- output ??= await baseQuery()
1903
- .where('o.satoshis', '<', targetSatoshis)
1904
- .orderBy('o.satoshis', 'desc')
1905
- .orderBy('o.outputId', 'desc')
1906
- .first();
1907
- if (output == null)
1908
- return undefined;
1902
+ const excluded = [];
1903
+ const candidates = () => excluded.length > 0 ? baseQuery().whereNotIn('o.outputId', excluded) : baseQuery();
1904
+ const selectOutput = async () => {
1905
+ let output;
1906
+ if (exactSatoshis !== undefined) {
1907
+ output = await candidates().where('o.satoshis', exactSatoshis).orderBy('o.outputId', 'asc').first();
1908
+ }
1909
+ output ??= await candidates()
1910
+ .where('o.satoshis', '>=', targetSatoshis)
1911
+ .orderBy('o.satoshis', 'asc')
1912
+ .orderBy('o.outputId', 'asc')
1913
+ .first();
1914
+ output ??= await candidates()
1915
+ .where('o.satoshis', '<', targetSatoshis)
1916
+ .orderBy('o.satoshis', 'desc')
1917
+ .orderBy('o.outputId', 'desc')
1918
+ .first();
1919
+ return output;
1920
+ };
1921
+ let output = await selectOutput();
1909
1922
  // Under Postgres READ COMMITTED the NOT EXISTS above uses the statement's
1910
1923
  // snapshot, so an action batch reservation committed while the statement
1911
- // waited for the row lock is not visible to it. Retry in a new transaction.
1912
- if (this.dbtype === 'Postgres' &&
1924
+ // waited for the row lock is not visible to it. Re-check with a new statement.
1925
+ while (output != null &&
1926
+ this.dbtype === 'Postgres' &&
1913
1927
  (await this.findReservedActionBatchOutputIds([output.outputId], trx)).length > 0) {
1914
- reservedConcurrently = true;
1915
- return undefined;
1928
+ excluded.push(output.outputId);
1929
+ output = await selectOutput();
1916
1930
  }
1931
+ if (output == null)
1932
+ return undefined;
1917
1933
  await this.updateOutput(output.outputId, {
1918
1934
  spendable: false,
1919
1935
  spentBy: transactionId
@@ -1925,8 +1941,6 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
1925
1941
  output.spentBy = transactionId;
1926
1942
  return output;
1927
1943
  });
1928
- if (reservedConcurrently)
1929
- return await this.allocateChangeInput(userId, basketId, targetSatoshis, exactSatoshis, excludeSending, transactionId);
1930
1944
  return r;
1931
1945
  }
1932
1946
  async markChangeInputsSpent(outputIds, transactionId, trx) {
@@ -1992,8 +2006,8 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
1992
2006
  return entities;
1993
2007
  }
1994
2008
  async adminStats(adminIdentityKey) {
1995
- if (this.dbtype !== 'MySQL')
1996
- throw new WERR_errors_1.WERR_NOT_IMPLEMENTED('adminStats, only MySQL is supported');
2009
+ if (this.dbtype !== 'MySQL' && this.dbtype !== 'Postgres')
2010
+ throw new WERR_errors_1.WERR_NOT_IMPLEMENTED('adminStats, only MySQL and Postgres are supported');
1997
2011
  const monitorEvent = (0, utilityHelpers_1.verifyOneOrNone)(await this.findMonitorEvents({
1998
2012
  partial: { event: 'MonitorCallHistory' },
1999
2013
  orderDescending: true,
@@ -2001,153 +2015,66 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
2001
2015
  }));
2002
2016
  const monitorStats = monitorEvent != null ? JSON.parse(monitorEvent.details) : undefined;
2003
2017
  const servicesStats = this.getServices().getServicesCallHistory(true);
2004
- const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
2005
- const oneWeekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
2006
- const oneMonthAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
2007
- const [[{ usersDay, usersMonth, usersWeek, usersTotal, transactionsDay, transactionsMonth, transactionsWeek, transactionsTotal, txCompletedDay, txCompletedMonth, txCompletedWeek, txCompletedTotal, txFailedDay, txFailedMonth, txFailedWeek, txFailedTotal, txAbandonedDay, txAbandonedMonth, txAbandonedWeek, txAbandonedTotal, txUnprocessedDay, txUnprocessedMonth, txUnprocessedWeek, txUnprocessedTotal, txSendingDay, txSendingMonth, txSendingWeek, txSendingTotal, txUnprovenDay, txUnprovenMonth, txUnprovenWeek, txUnprovenTotal, txUnsignedDay, txUnsignedMonth, txUnsignedWeek, txUnsignedTotal, txNosendDay, txNosendMonth, txNosendWeek, txNosendTotal, txNonfinalDay, txNonfinalMonth, txNonfinalWeek, txNonfinalTotal, txUnfailDay, txUnfailMonth, txUnfailWeek, txUnfailTotal, satoshisDefaultDay, satoshisDefaultMonth, satoshisDefaultWeek, satoshisDefaultTotal, satoshisOtherDay, satoshisOtherMonth, satoshisOtherWeek, satoshisOtherTotal, basketsDay, basketsMonth, basketsWeek, basketsTotal, labelsDay, labelsMonth, labelsWeek, labelsTotal, tagsDay, tagsMonth, tagsWeek, tagsTotal }]] = await this.knex.raw(`
2008
- select
2009
- (select count(*) from users where created_at > '${oneDayAgo}') as usersDay,
2010
- (select count(*) from users where created_at > '${oneWeekAgo}') as usersWeek,
2011
- (select count(*) from users where created_at > '${oneMonthAgo}') as usersMonth,
2012
- (select count(*) from users) as usersTotal,
2013
- (select count(*) from transactions where created_at > '${oneDayAgo}') as transactionsDay,
2014
- (select count(*) from transactions where created_at > '${oneWeekAgo}') as transactionsWeek,
2015
- (select count(*) from transactions where created_at > '${oneMonthAgo}') as transactionsMonth,
2016
- (select count(*) from transactions) as transactionsTotal,
2017
- (select count(*) from transactions where status = 'completed' and created_at > '${oneDayAgo}') as txCompletedDay,
2018
- (select count(*) from transactions where status = 'completed' and created_at > '${oneWeekAgo}') as txCompletedWeek,
2019
- (select count(*) from transactions where status = 'completed' and created_at > '${oneMonthAgo}') as txCompletedMonth,
2020
- (select count(*) from transactions where status = 'completed') as txCompletedTotal,
2021
- (select count(*) from transactions where status = 'failed' and not txid is null and created_at > '${oneDayAgo}') as txFailedDay,
2022
- (select count(*) from transactions where status = 'failed' and not txid is null and created_at > '${oneWeekAgo}') as txFailedWeek,
2023
- (select count(*) from transactions where status = 'failed' and not txid is null and created_at > '${oneMonthAgo}') as txFailedMonth,
2024
- (select count(*) from transactions where status = 'failed' and not txid is null) as txFailedTotal,
2025
- (select count(*) from transactions where status = 'failed' and txid is null and created_at > '${oneDayAgo}') as txAbandonedDay,
2026
- (select count(*) from transactions where status = 'failed' and txid is null and created_at > '${oneWeekAgo}') as txAbandonedWeek,
2027
- (select count(*) from transactions where status = 'failed' and txid is null and created_at > '${oneMonthAgo}') as txAbandonedMonth,
2028
- (select count(*) from transactions where status = 'failed' and txid is null) as txAbandonedTotal,
2029
- (select count(*) from transactions where status = 'unprocessed' and created_at > '${oneDayAgo}') as txUnprocessedDay,
2030
- (select count(*) from transactions where status = 'unprocessed' and created_at > '${oneWeekAgo}') as txUnprocessedWeek,
2031
- (select count(*) from transactions where status = 'unprocessed' and created_at > '${oneMonthAgo}') as txUnprocessedMonth,
2032
- (select count(*) from transactions where status = 'unprocessed') as txUnprocessedTotal,
2033
- (select count(*) from transactions where status = 'sending' and created_at > '${oneDayAgo}') as txSendingDay,
2034
- (select count(*) from transactions where status = 'sending' and created_at > '${oneWeekAgo}') as txSendingWeek,
2035
- (select count(*) from transactions where status = 'sending' and created_at > '${oneMonthAgo}') as txSendingMonth,
2036
- (select count(*) from transactions where status = 'sending') as txSendingTotal,
2037
- (select count(*) from transactions where status = 'unproven' and created_at > '${oneDayAgo}') as txUnprovenDay,
2038
- (select count(*) from transactions where status = 'unproven' and created_at > '${oneWeekAgo}') as txUnprovenWeek,
2039
- (select count(*) from transactions where status = 'unproven' and created_at > '${oneMonthAgo}') as txUnprovenMonth,
2040
- (select count(*) from transactions where status = 'unproven') as txUnprovenTotal,
2041
- (select count(*) from transactions where status = 'unsigned' and created_at > '${oneDayAgo}') as txUnsignedDay,
2042
- (select count(*) from transactions where status = 'unsigned' and created_at > '${oneWeekAgo}') as txUnsignedWeek,
2043
- (select count(*) from transactions where status = 'unsigned' and created_at > '${oneMonthAgo}') as txUnsignedMonth,
2044
- (select count(*) from transactions where status = 'unsigned') as txUnsignedTotal,
2045
- (select count(*) from transactions where status = 'nosend' and created_at > '${oneDayAgo}') as txNosendDay,
2046
- (select count(*) from transactions where status = 'nosend' and created_at > '${oneWeekAgo}') as txNosendWeek,
2047
- (select count(*) from transactions where status = 'nosend' and created_at > '${oneMonthAgo}') as txNosendMonth,
2048
- (select count(*) from transactions where status = 'nosend') as txNosendTotal,
2049
- (select count(*) from transactions where status = 'nonfinal' and created_at > '${oneDayAgo}') as txNonfinalDay,
2050
- (select count(*) from transactions where status = 'nonfinal' and created_at > '${oneWeekAgo}') as txNonfinalWeek,
2051
- (select count(*) from transactions where status = 'nonfinal' and created_at > '${oneMonthAgo}') as txNonfinalMonth,
2052
- (select count(*) from transactions where status = 'nonfinal') as txNonfinalTotal,
2053
- (select count(*) from transactions where status = 'unfail' and created_at > '${oneDayAgo}') as txUnfailDay,
2054
- (select count(*) from transactions where status = 'unfail' and created_at > '${oneWeekAgo}') as txUnfailWeek,
2055
- (select count(*) from transactions where status = 'unfail' and created_at > '${oneMonthAgo}') as txUnfailMonth,
2056
- (select count(*) from transactions where status = 'unfail') as txUnfailTotal,
2057
- (select sum(o.satoshis) from outputs o, transactions t where o.transactionId = t.transactionId and t.status = 'completed' and o.spendable = 1 and o.change = 1 and o.created_at > '${oneDayAgo}') as satoshisDefaultDay,
2058
- (select sum(o.satoshis) from outputs o, transactions t where o.transactionId = t.transactionId and t.status = 'completed' and o.spendable = 1 and o.change = 1 and o.created_at > '${oneWeekAgo}') as satoshisDefaultWeek,
2059
- (select sum(o.satoshis) from outputs o, transactions t where o.transactionId = t.transactionId and t.status = 'completed' and o.spendable = 1 and o.change = 1 and o.created_at > '${oneMonthAgo}') as satoshisDefaultMonth,
2060
- (select sum(o.satoshis) from outputs o, transactions t where o.transactionId = t.transactionId and t.status = 'completed' and o.spendable = 1 and o.change = 1) as satoshisDefaultTotal,
2061
- (select sum(o.satoshis) from outputs o, transactions t where o.transactionId = t.transactionId and t.status = 'completed' and o.spendable = 1 and o.change = 0 and not o.basketId is null and o.created_at > '${oneDayAgo}') as satoshisOtherDay,
2062
- (select sum(o.satoshis) from outputs o, transactions t where o.transactionId = t.transactionId and t.status = 'completed' and o.spendable = 1 and o.change = 0 and not o.basketId is null and o.created_at > '${oneWeekAgo}') as satoshisOtherWeek,
2063
- (select sum(o.satoshis) from outputs o, transactions t where o.transactionId = t.transactionId and t.status = 'completed' and o.spendable = 1 and o.change = 0 and not o.basketId is null and o.created_at > '${oneMonthAgo}') as satoshisOtherMonth,
2064
- (select sum(o.satoshis) from outputs o, transactions t where o.transactionId = t.transactionId and t.status = 'completed' and o.spendable = 1 and o.change = 0 and not o.basketId is null) as satoshisOtherTotal,
2065
- (select count(*) from output_baskets where created_at > '${oneDayAgo}') as basketsDay,
2066
- (select count(*) from output_baskets where created_at > '${oneWeekAgo}') as basketsWeek,
2067
- (select count(*) from output_baskets where created_at > '${oneMonthAgo}') as basketsMonth,
2068
- (select count(*) from output_baskets) as basketsTotal,
2069
- (select count(*) from tx_labels where created_at > '${oneDayAgo}') as labelsDay,
2070
- (select count(*) from tx_labels where created_at > '${oneWeekAgo}') as labelsWeek,
2071
- (select count(*) from tx_labels where created_at > '${oneMonthAgo}') as labelsMonth,
2072
- (select count(*) from tx_labels) as labelsTotal,
2073
- (select count(*) from output_tags where created_at > '${oneDayAgo}') as tagsDay,
2074
- (select count(*) from output_tags where created_at > '${oneWeekAgo}') as tagsWeek,
2075
- (select count(*) from output_tags where created_at > '${oneMonthAgo}') as tagsMonth,
2076
- (select count(*) from output_tags) as tagsTotal
2077
- `);
2018
+ const day = 24 * 60 * 60 * 1000;
2019
+ const now = Date.now();
2020
+ const periods = [
2021
+ ['Day', new Date(now - day)],
2022
+ ['Week', new Date(now - 7 * day)],
2023
+ ['Month', new Date(now - 30 * day)],
2024
+ ['Total', undefined]
2025
+ ];
2026
+ const k = this.knex;
2027
+ const count = (table, createdAt = 'created_at') => (since) => {
2028
+ const q = k(table).count('*');
2029
+ if (since != null)
2030
+ void q.where(createdAt, '>', this.validateDateForWhere(since));
2031
+ return q;
2032
+ };
2033
+ const txCount = (status) => (since) => count('transactions')(since).where({ status });
2034
+ const satoshis = (change) => (since) => {
2035
+ const q = k('outputs as o')
2036
+ .join('transactions as t', 'o.transactionId', 't.transactionId')
2037
+ .where({ 't.status': 'completed', 'o.spendable': true, 'o.change': change })
2038
+ .sum('o.satoshis');
2039
+ if (!change)
2040
+ void q.whereNotNull('o.basketId');
2041
+ if (since != null)
2042
+ void q.where('o.created_at', '>', this.validateDateForWhere(since));
2043
+ return q;
2044
+ };
2045
+ const stats = [
2046
+ ['users', count('users')],
2047
+ ['transactions', count('transactions')],
2048
+ ['txCompleted', txCount('completed')],
2049
+ ['txFailed', since => txCount('failed')(since).whereNotNull('txid')],
2050
+ ['txAbandoned', since => txCount('failed')(since).whereNull('txid')],
2051
+ ['txUnprocessed', txCount('unprocessed')],
2052
+ ['txSending', txCount('sending')],
2053
+ ['txUnproven', txCount('unproven')],
2054
+ ['txUnsigned', txCount('unsigned')],
2055
+ ['txNosend', txCount('nosend')],
2056
+ ['txNonfinal', txCount('nonfinal')],
2057
+ ['txUnfail', txCount('unfail')],
2058
+ ['satoshisDefault', satoshis(true)],
2059
+ ['satoshisOther', satoshis(false)],
2060
+ ['baskets', count('output_baskets')],
2061
+ ['labels', count('tx_labels')],
2062
+ ['tags', count('output_tags')]
2063
+ ];
2064
+ // One statement of scalar subqueries; knex quotes the camelCase identifiers per dialect.
2065
+ const columns = {};
2066
+ for (const [name, query] of stats) {
2067
+ for (const [period, since] of periods)
2068
+ columns[`${name}${period}`] = query(since);
2069
+ }
2070
+ const [row] = (await k.select(columns));
2071
+ const values = Object.fromEntries(Object.keys(columns).map(name => [name, Number(row[name] ?? 0)]));
2078
2072
  const r = {
2079
2073
  monitorStats,
2080
2074
  servicesStats,
2081
2075
  requestedBy: adminIdentityKey,
2082
2076
  when: new Date().toISOString(),
2083
- usersDay,
2084
- usersWeek,
2085
- usersMonth,
2086
- usersTotal,
2087
- transactionsDay,
2088
- transactionsWeek,
2089
- transactionsMonth,
2090
- transactionsTotal,
2091
- txCompletedDay,
2092
- txCompletedWeek,
2093
- txCompletedMonth,
2094
- txCompletedTotal,
2095
- txFailedDay,
2096
- txFailedWeek,
2097
- txFailedMonth,
2098
- txFailedTotal,
2099
- txAbandonedDay,
2100
- txAbandonedWeek,
2101
- txAbandonedMonth,
2102
- txAbandonedTotal,
2103
- txUnprocessedDay,
2104
- txUnprocessedWeek,
2105
- txUnprocessedMonth,
2106
- txUnprocessedTotal,
2107
- txSendingDay,
2108
- txSendingWeek,
2109
- txSendingMonth,
2110
- txSendingTotal,
2111
- txUnprovenDay,
2112
- txUnprovenWeek,
2113
- txUnprovenMonth,
2114
- txUnprovenTotal,
2115
- txUnsignedDay,
2116
- txUnsignedWeek,
2117
- txUnsignedMonth,
2118
- txUnsignedTotal,
2119
- txNosendDay,
2120
- txNosendWeek,
2121
- txNosendMonth,
2122
- txNosendTotal,
2123
- txNonfinalDay,
2124
- txNonfinalWeek,
2125
- txNonfinalMonth,
2126
- txNonfinalTotal,
2127
- txUnfailDay,
2128
- txUnfailWeek,
2129
- txUnfailMonth,
2130
- txUnfailTotal,
2131
- satoshisDefaultDay: Number(satoshisDefaultDay),
2132
- satoshisDefaultWeek: Number(satoshisDefaultWeek),
2133
- satoshisDefaultMonth: Number(satoshisDefaultMonth),
2134
- satoshisDefaultTotal: Number(satoshisDefaultTotal),
2135
- satoshisOtherDay: Number(satoshisOtherDay),
2136
- satoshisOtherWeek: Number(satoshisOtherWeek),
2137
- satoshisOtherMonth: Number(satoshisOtherMonth),
2138
- satoshisOtherTotal: Number(satoshisOtherTotal),
2139
- basketsDay,
2140
- basketsWeek,
2141
- basketsMonth,
2142
- basketsTotal,
2143
- labelsDay,
2144
- labelsWeek,
2145
- labelsMonth,
2146
- labelsTotal,
2147
- tagsDay,
2148
- tagsWeek,
2149
- tagsMonth,
2150
- tagsTotal
2077
+ ...values
2151
2078
  };
2152
2079
  return r;
2153
2080
  }
@@ -2197,23 +2124,4 @@ function pagingKey(table) {
2197
2124
  return [];
2198
2125
  }
2199
2126
  }
2200
- const pgInt8Parsed = Symbol('pgInt8Parsed');
2201
- /**
2202
- * node-postgres returns int8 values (bigint columns such as satoshis, `count(*)`)
2203
- * as strings. Set a parser on each connection this knex acquires so they come
2204
- * back as numbers, as with mysql2 and better-sqlite3. The process-wide `pg.types`
2205
- * defaults are left unchanged.
2206
- */
2207
- function parsePostgresInt8AsNumber(knex) {
2208
- const client = knex.client;
2209
- if (client?.dialect !== 'postgresql' || client[pgInt8Parsed] === true)
2210
- return;
2211
- client[pgInt8Parsed] = true;
2212
- const acquireConnection = client.acquireConnection.bind(client);
2213
- client.acquireConnection = async () => {
2214
- const connection = await acquireConnection();
2215
- connection.setTypeParser(20, (value) => Number.parseInt(value, 10));
2216
- return connection;
2217
- };
2218
- }
2219
2127
  //# sourceMappingURL=StorageKnex.js.map