@bsv/wallet-toolbox 1.3.5 → 1.3.6

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 (32) hide show
  1. package/docs/client.md +137 -60
  2. package/docs/storage.md +243 -20
  3. package/docs/wallet.md +137 -60
  4. package/out/src/storage/StorageIdb.d.ts +2 -1
  5. package/out/src/storage/StorageIdb.d.ts.map +1 -1
  6. package/out/src/storage/StorageIdb.js +3 -0
  7. package/out/src/storage/StorageIdb.js.map +1 -1
  8. package/out/src/storage/StorageKnex.d.ts +2 -1
  9. package/out/src/storage/StorageKnex.d.ts.map +1 -1
  10. package/out/src/storage/StorageKnex.js +143 -0
  11. package/out/src/storage/StorageKnex.js.map +1 -1
  12. package/out/src/storage/StorageProvider.d.ts +69 -0
  13. package/out/src/storage/StorageProvider.d.ts.map +1 -1
  14. package/out/src/storage/StorageProvider.js.map +1 -1
  15. package/out/src/storage/__test/adminStats.man.test.d.ts +2 -0
  16. package/out/src/storage/__test/adminStats.man.test.d.ts.map +1 -0
  17. package/out/src/storage/__test/adminStats.man.test.js +29 -0
  18. package/out/src/storage/__test/adminStats.man.test.js.map +1 -0
  19. package/out/src/storage/__test/getBeefForTransaction.test.js +3 -0
  20. package/out/src/storage/__test/getBeefForTransaction.test.js.map +1 -1
  21. package/out/src/storage/remoting/StorageServer.d.ts +2 -0
  22. package/out/src/storage/remoting/StorageServer.d.ts.map +1 -1
  23. package/out/src/storage/remoting/StorageServer.js +10 -0
  24. package/out/src/storage/remoting/StorageServer.js.map +1 -1
  25. package/out/tsconfig.all.tsbuildinfo +1 -1
  26. package/package.json +1 -1
  27. package/src/storage/StorageIdb.ts +5 -1
  28. package/src/storage/StorageKnex.ts +216 -2
  29. package/src/storage/StorageProvider.ts +71 -0
  30. package/src/storage/__test/adminStats.man.test.ts +31 -0
  31. package/src/storage/__test/getBeefForTransaction.test.ts +5 -1
  32. package/src/storage/remoting/StorageServer.ts +12 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/wallet-toolbox",
3
- "version": "1.3.5",
3
+ "version": "1.3.6",
4
4
  "description": "BRC100 conforming wallet, wallet storage and wallet signer components",
5
5
  "main": "./out/src/index.js",
6
6
  "types": "./out/src/index.d.ts",
@@ -22,7 +22,7 @@ import {
22
22
  verifyOne,
23
23
  verifyOneOrNone
24
24
  } from '../index.client'
25
- import { StorageProvider, StorageProviderOptions } from './StorageProvider'
25
+ import { StorageAdminStats, StorageProvider, StorageProviderOptions } from './StorageProvider'
26
26
  import { StorageIdbSchema } from './schema/StorageIdbSchema'
27
27
  import { DBType } from './StorageReader'
28
28
  import { TransactionStatus } from '../sdk'
@@ -42,6 +42,10 @@ export class StorageIdb extends StorageProvider implements sdk.WalletStorageProv
42
42
  this.dbName = `wallet-toolbox-${this.chain}net`
43
43
  }
44
44
 
45
+ override adminStats(adminIdentityKey: string): Promise<StorageAdminStats> {
46
+ throw new Error('Method not implemented.')
47
+ }
48
+
45
49
  /**
46
50
  * This method must be called at least once before any other method accesses the database,
47
51
  * and each time the schema may have updated.
@@ -1,4 +1,4 @@
1
- import { ListActionsResult, ListOutputsResult } from '@bsv/sdk'
1
+ import { ListActionsResult, ListOutputsResult, Transaction } from '@bsv/sdk'
2
2
  import { sdk, verifyOne, verifyOneOrNone, verifyTruthy } from '../index.all'
3
3
  import {
4
4
  KnexMigrations,
@@ -25,7 +25,7 @@ import {
25
25
  } from './index.all'
26
26
 
27
27
  import { Knex } from 'knex'
28
- import { StorageProvider, StorageProviderOptions } from './StorageProvider'
28
+ import { StorageAdminStats, StorageProvider, StorageProviderOptions } from './StorageProvider'
29
29
  import { purgeData } from './methods/purgeData'
30
30
  import { listActions } from './methods/listActionsKnex'
31
31
  import { listOutputs } from './methods/listOutputsKnex'
@@ -1126,4 +1126,218 @@ export class StorageKnex extends StorageProvider implements sdk.WalletStoragePro
1126
1126
  }
1127
1127
  return entities
1128
1128
  }
1129
+
1130
+ async adminStats(adminIdentityKey: string): Promise<StorageAdminStats> {
1131
+ if (this.dbtype !== 'MySQL') throw new sdk.WERR_NOT_IMPLEMENTED('adminStats, only MySQL is supported')
1132
+
1133
+ const one_day_ago = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()
1134
+ const one_week_ago = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString()
1135
+ const one_month_ago = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString()
1136
+
1137
+ const [
1138
+ [
1139
+ {
1140
+ usersDay,
1141
+ usersMonth,
1142
+ usersWeek,
1143
+ usersTotal,
1144
+ transactionsDay,
1145
+ transactionsMonth,
1146
+ transactionsWeek,
1147
+ transactionsTotal,
1148
+ txCompletedDay,
1149
+ txCompletedMonth,
1150
+ txCompletedWeek,
1151
+ txCompletedTotal,
1152
+ txFailedDay,
1153
+ txFailedMonth,
1154
+ txFailedWeek,
1155
+ txFailedTotal,
1156
+ txUnprocessedDay,
1157
+ txUnprocessedMonth,
1158
+ txUnprocessedWeek,
1159
+ txUnprocessedTotal,
1160
+ txSendingDay,
1161
+ txSendingMonth,
1162
+ txSendingWeek,
1163
+ txSendingTotal,
1164
+ txUnprovenDay,
1165
+ txUnprovenMonth,
1166
+ txUnprovenWeek,
1167
+ txUnprovenTotal,
1168
+ txUnsignedDay,
1169
+ txUnsignedMonth,
1170
+ txUnsignedWeek,
1171
+ txUnsignedTotal,
1172
+ txNosendDay,
1173
+ txNosendMonth,
1174
+ txNosendWeek,
1175
+ txNosendTotal,
1176
+ txNonfinalDay,
1177
+ txNonfinalMonth,
1178
+ txNonfinalWeek,
1179
+ txNonfinalTotal,
1180
+ txUnfailDay,
1181
+ txUnfailMonth,
1182
+ txUnfailWeek,
1183
+ txUnfailTotal,
1184
+ satoshisDefaultDay,
1185
+ satoshisDefaultMonth,
1186
+ satoshisDefaultWeek,
1187
+ satoshisDefaultTotal,
1188
+ satoshisOtherDay,
1189
+ satoshisOtherMonth,
1190
+ satoshisOtherWeek,
1191
+ satoshisOtherTotal,
1192
+ basketsDay,
1193
+ basketsMonth,
1194
+ basketsWeek,
1195
+ basketsTotal,
1196
+ labelsDay,
1197
+ labelsMonth,
1198
+ labelsWeek,
1199
+ labelsTotal,
1200
+ tagsDay,
1201
+ tagsMonth,
1202
+ tagsWeek,
1203
+ tagsTotal
1204
+ }
1205
+ ]
1206
+ ] = await this.knex.raw(`
1207
+ select
1208
+ (select count(*) from users where created_at > '${one_day_ago}') as usersDay,
1209
+ (select count(*) from users where created_at > '${one_week_ago}') as usersWeek,
1210
+ (select count(*) from users where created_at > '${one_month_ago}') as usersMonth,
1211
+ (select count(*) from users) as usersTotal,
1212
+ (select count(*) from transactions where created_at > '${one_day_ago}') as transactionsDay,
1213
+ (select count(*) from transactions where created_at > '${one_week_ago}') as transactionsWeek,
1214
+ (select count(*) from transactions where created_at > '${one_month_ago}') as transactionsMonth,
1215
+ (select count(*) from transactions) as transactionsTotal,
1216
+ (select count(*) from transactions where status = 'completed' and created_at > '${one_day_ago}') as txCompletedDay,
1217
+ (select count(*) from transactions where status = 'completed' and created_at > '${one_week_ago}') as txCompletedWeek,
1218
+ (select count(*) from transactions where status = 'completed' and created_at > '${one_month_ago}') as txCompletedMonth,
1219
+ (select count(*) from transactions where status = 'completed') as txCompletedTotal,
1220
+ (select count(*) from transactions where status = 'failed' and created_at > '${one_day_ago}') as txFailedDay,
1221
+ (select count(*) from transactions where status = 'failed' and created_at > '${one_week_ago}') as txFailedWeek,
1222
+ (select count(*) from transactions where status = 'failed' and created_at > '${one_month_ago}') as txFailedMonth,
1223
+ (select count(*) from transactions where status = 'failed') as txFailedTotal,
1224
+ (select count(*) from transactions where status = 'unprocessed' and created_at > '${one_day_ago}') as txUnprocessedDay,
1225
+ (select count(*) from transactions where status = 'unprocessed' and created_at > '${one_week_ago}') as txUnprocessedWeek,
1226
+ (select count(*) from transactions where status = 'unprocessed' and created_at > '${one_month_ago}') as txUnprocessedMonth,
1227
+ (select count(*) from transactions where status = 'unprocessed') as txUnprocessedTotal,
1228
+ (select count(*) from transactions where status = 'sending' and created_at > '${one_day_ago}') as txSendingDay,
1229
+ (select count(*) from transactions where status = 'sending' and created_at > '${one_week_ago}') as txSendingWeek,
1230
+ (select count(*) from transactions where status = 'sending' and created_at > '${one_month_ago}') as txSendingMonth,
1231
+ (select count(*) from transactions where status = 'sending') as txSendingTotal,
1232
+ (select count(*) from transactions where status = 'unproven' and created_at > '${one_day_ago}') as txUnprovenDay,
1233
+ (select count(*) from transactions where status = 'unproven' and created_at > '${one_week_ago}') as txUnprovenWeek,
1234
+ (select count(*) from transactions where status = 'unproven' and created_at > '${one_month_ago}') as txUnprovenMonth,
1235
+ (select count(*) from transactions where status = 'unproven') as txUnprovenTotal,
1236
+ (select count(*) from transactions where status = 'unsigned' and created_at > '${one_day_ago}') as txUnsignedDay,
1237
+ (select count(*) from transactions where status = 'unsigned' and created_at > '${one_week_ago}') as txUnsignedWeek,
1238
+ (select count(*) from transactions where status = 'unsigned' and created_at > '${one_month_ago}') as txUnsignedMonth,
1239
+ (select count(*) from transactions where status = 'unsigned') as txUnsignedTotal,
1240
+ (select count(*) from transactions where status = 'nosend' and created_at > '${one_day_ago}') as txNosendDay,
1241
+ (select count(*) from transactions where status = 'nosend' and created_at > '${one_week_ago}') as txNosendWeek,
1242
+ (select count(*) from transactions where status = 'nosend' and created_at > '${one_month_ago}') as txNosendMonth,
1243
+ (select count(*) from transactions where status = 'nosend') as txNosendTotal,
1244
+ (select count(*) from transactions where status = 'nonfinal' and created_at > '${one_day_ago}') as txNonfinalDay,
1245
+ (select count(*) from transactions where status = 'nonfinal' and created_at > '${one_week_ago}') as txNonfinalWeek,
1246
+ (select count(*) from transactions where status = 'nonfinal' and created_at > '${one_month_ago}') as txNonfinalMonth,
1247
+ (select count(*) from transactions where status = 'nonfinal') as txNonfinalTotal,
1248
+ (select count(*) from transactions where status = 'unfail' and created_at > '${one_day_ago}') as txUnfailDay,
1249
+ (select count(*) from transactions where status = 'unfail' and created_at > '${one_week_ago}') as txUnfailWeek,
1250
+ (select count(*) from transactions where status = 'unfail' and created_at > '${one_month_ago}') as txUnfailMonth,
1251
+ (select count(*) from transactions where status = 'unfail') as txUnfailTotal,
1252
+ (select sum(satoshis) from outputs where spendable = 1 and \`change\` = 1 and created_at > '${one_day_ago}') as satoshisDefaultDay,
1253
+ (select sum(satoshis) from outputs where spendable = 1 and \`change\` = 1 and created_at > '${one_week_ago}') as satoshisDefaultWeek,
1254
+ (select sum(satoshis) from outputs where spendable = 1 and \`change\` = 1 and created_at > '${one_month_ago}') as satoshisDefaultMonth,
1255
+ (select sum(satoshis) from outputs where spendable = 1 and \`change\` = 1) as satoshisDefaultTotal,
1256
+ (select sum(satoshis) from outputs where spendable = 1 and \`change\` = 0 and not basketId is null and created_at > '${one_day_ago}') as satoshisOtherDay,
1257
+ (select sum(satoshis) from outputs where spendable = 1 and \`change\` = 0 and not basketId is null and created_at > '${one_week_ago}') as satoshisOtherWeek,
1258
+ (select sum(satoshis) from outputs where spendable = 1 and \`change\` = 0 and not basketId is null and created_at > '${one_month_ago}') as satoshisOtherMonth,
1259
+ (select sum(satoshis) from outputs where spendable = 1 and \`change\` = 0 and not basketId is null) as satoshisOtherTotal,
1260
+ (select count(*) from output_baskets where created_at > '${one_day_ago}') as basketsDay,
1261
+ (select count(*) from output_baskets where created_at > '${one_week_ago}') as basketsWeek,
1262
+ (select count(*) from output_baskets where created_at > '${one_month_ago}') as basketsMonth,
1263
+ (select count(*) from output_baskets) as basketsTotal,
1264
+ (select count(*) from tx_labels where created_at > '${one_day_ago}') as labelsDay,
1265
+ (select count(*) from tx_labels where created_at > '${one_week_ago}') as labelsWeek,
1266
+ (select count(*) from tx_labels where created_at > '${one_month_ago}') as labelsMonth,
1267
+ (select count(*) from tx_labels) as labelsTotal,
1268
+ (select count(*) from output_tags where created_at > '${one_day_ago}') as tagsDay,
1269
+ (select count(*) from output_tags where created_at > '${one_week_ago}') as tagsWeek,
1270
+ (select count(*) from output_tags where created_at > '${one_month_ago}') as tagsMonth,
1271
+ (select count(*) from output_tags) as tagsTotal
1272
+ `)
1273
+ const r: StorageAdminStats = {
1274
+ requestedBy: adminIdentityKey,
1275
+ when: new Date().toISOString(),
1276
+ usersDay,
1277
+ usersWeek,
1278
+ usersMonth,
1279
+ usersTotal,
1280
+ transactionsDay,
1281
+ transactionsWeek,
1282
+ transactionsMonth,
1283
+ transactionsTotal,
1284
+ txCompletedDay,
1285
+ txCompletedWeek,
1286
+ txCompletedMonth,
1287
+ txCompletedTotal,
1288
+ txFailedDay,
1289
+ txFailedWeek,
1290
+ txFailedMonth,
1291
+ txFailedTotal,
1292
+ txUnprocessedDay,
1293
+ txUnprocessedWeek,
1294
+ txUnprocessedMonth,
1295
+ txUnprocessedTotal,
1296
+ txSendingDay,
1297
+ txSendingWeek,
1298
+ txSendingMonth,
1299
+ txSendingTotal,
1300
+ txUnprovenDay,
1301
+ txUnprovenWeek,
1302
+ txUnprovenMonth,
1303
+ txUnprovenTotal,
1304
+ txUnsignedDay,
1305
+ txUnsignedWeek,
1306
+ txUnsignedMonth,
1307
+ txUnsignedTotal,
1308
+ txNosendDay,
1309
+ txNosendWeek,
1310
+ txNosendMonth,
1311
+ txNosendTotal,
1312
+ txNonfinalDay,
1313
+ txNonfinalWeek,
1314
+ txNonfinalMonth,
1315
+ txNonfinalTotal,
1316
+ txUnfailDay,
1317
+ txUnfailWeek,
1318
+ txUnfailMonth,
1319
+ txUnfailTotal,
1320
+ satoshisDefaultDay,
1321
+ satoshisDefaultWeek,
1322
+ satoshisDefaultMonth,
1323
+ satoshisDefaultTotal,
1324
+ satoshisOtherDay,
1325
+ satoshisOtherWeek,
1326
+ satoshisOtherMonth,
1327
+ satoshisOtherTotal,
1328
+ basketsDay,
1329
+ basketsWeek,
1330
+ basketsMonth,
1331
+ basketsTotal,
1332
+ labelsDay,
1333
+ labelsWeek,
1334
+ labelsMonth,
1335
+ labelsTotal,
1336
+ tagsDay,
1337
+ tagsWeek,
1338
+ tagsMonth,
1339
+ tagsTotal
1340
+ }
1341
+ return r
1342
+ }
1129
1343
  }
@@ -106,6 +106,8 @@ export abstract class StorageProvider extends StorageReaderWriter implements sdk
106
106
  abstract findOutputsAuth(auth: sdk.AuthId, args: sdk.FindOutputsArgs): Promise<TableOutput[]>
107
107
  abstract insertCertificateAuth(auth: sdk.AuthId, certificate: TableCertificateX): Promise<number>
108
108
 
109
+ abstract adminStats(adminIdentityKey: string): Promise<StorageAdminStats>
110
+
109
111
  override isStorageProvider(): boolean {
110
112
  return true
111
113
  }
@@ -694,3 +696,72 @@ export function validateStorageFeeModel(v?: sdk.StorageFeeModel): sdk.StorageFee
694
696
  }
695
697
  return r
696
698
  }
699
+
700
+ export interface StorageAdminStats {
701
+ requestedBy: string
702
+ when: string
703
+ usersDay: number
704
+ usersWeek: number
705
+ usersMonth: number
706
+ usersTotal: number
707
+ transactionsDay: number
708
+ transactionsWeek: number
709
+ transactionsMonth: number
710
+ transactionsTotal: number
711
+ txCompletedDay: number
712
+ txCompletedWeek: number
713
+ txCompletedMonth: number
714
+ txCompletedTotal: number
715
+ txFailedDay: number
716
+ txFailedWeek: number
717
+ txFailedMonth: number
718
+ txFailedTotal: number
719
+ txUnprocessedDay: number
720
+ txUnprocessedWeek: number
721
+ txUnprocessedMonth: number
722
+ txUnprocessedTotal: number
723
+ txSendingDay: number
724
+ txSendingWeek: number
725
+ txSendingMonth: number
726
+ txSendingTotal: number
727
+ txUnprovenDay: number
728
+ txUnprovenWeek: number
729
+ txUnprovenMonth: number
730
+ txUnprovenTotal: number
731
+ txUnsignedDay: number
732
+ txUnsignedWeek: number
733
+ txUnsignedMonth: number
734
+ txUnsignedTotal: number
735
+ txNosendDay: number
736
+ txNosendWeek: number
737
+ txNosendMonth: number
738
+ txNosendTotal: number
739
+ txNonfinalDay: number
740
+ txNonfinalWeek: number
741
+ txNonfinalMonth: number
742
+ txNonfinalTotal: number
743
+ txUnfailDay: number
744
+ txUnfailWeek: number
745
+ txUnfailMonth: number
746
+ txUnfailTotal: number
747
+ satoshisDefaultDay: number
748
+ satoshisDefaultWeek: number
749
+ satoshisDefaultMonth: number
750
+ satoshisDefaultTotal: number
751
+ satoshisOtherDay: number
752
+ satoshisOtherWeek: number
753
+ satoshisOtherMonth: number
754
+ satoshisOtherTotal: number
755
+ basketsDay: number
756
+ basketsWeek: number
757
+ basketsMonth: number
758
+ basketsTotal: number
759
+ labelsDay: number
760
+ labelsWeek: number
761
+ labelsMonth: number
762
+ labelsTotal: number
763
+ tagsDay: number
764
+ tagsWeek: number
765
+ tagsMonth: number
766
+ tagsTotal: number
767
+ }
@@ -0,0 +1,31 @@
1
+ import { before } from 'node:test'
2
+ import { _tu } from '../../../test/utils/TestUtilsWalletStorage'
3
+ import { Setup } from '../../Setup'
4
+ import { StorageKnex } from '../StorageKnex'
5
+
6
+ describe('storage adminStats tests', () => {
7
+ jest.setTimeout(99999999)
8
+
9
+ const env = _tu.getEnv('main')
10
+ const knex = Setup.createMySQLKnex(process.env.MAIN_CLOUD_MYSQL_CONNECTION!)
11
+ const storage = new StorageKnex({
12
+ chain: env.chain,
13
+ knex: knex,
14
+ commissionSatoshis: 0,
15
+ commissionPubKeyHex: undefined,
16
+ feeModel: { model: 'sat/kb', value: 1 }
17
+ })
18
+
19
+ beforeAll(async () => {
20
+ await storage.makeAvailable()
21
+ })
22
+ afterAll(async () => {
23
+ await storage.destroy()
24
+ })
25
+
26
+ test('0 adminStats', async () => {
27
+ const r = await storage.adminStats(env.identityKey)
28
+ expect(r.requestedBy).toBe(env.identityKey)
29
+ expect(r.usersTotal).toBeGreaterThan(0)
30
+ })
31
+ })
@@ -46,7 +46,8 @@ import {
46
46
  TableUser,
47
47
  sdk,
48
48
  Services,
49
- EntityProvenTx
49
+ EntityProvenTx,
50
+ StorageAdminStats
50
51
  } from '../../index.client'
51
52
 
52
53
  describe('getBeefForTransaction tests', () => {
@@ -371,4 +372,7 @@ class ProtoStorage extends StorageProvider {
371
372
  override getOutputTagMapsForUser(args: FindForUserSincePagedArgs): Promise<TableOutputTagMap[]> {
372
373
  throw this.nip
373
374
  }
375
+ override adminStats(adminIdentityKey: string): Promise<StorageAdminStats> {
376
+ throw this.nip
377
+ }
374
378
  }
@@ -11,13 +11,12 @@ import { AuthMiddlewareOptions, createAuthMiddleware } from '@bsv/auth-express-m
11
11
  import { createPaymentMiddleware } from '@bsv/payment-express-middleware'
12
12
  import { sdk, Wallet, StorageProvider } from '../../index.all'
13
13
 
14
- import { StorageKnex } from '../StorageKnex'
15
-
16
14
  export interface WalletStorageServerOptions {
17
15
  port: number
18
16
  wallet: Wallet
19
17
  monetize: boolean
20
18
  calculateRequestPrice?: (req: Request) => number | Promise<number>
19
+ adminIdentityKeys?: string[]
21
20
  }
22
21
 
23
22
  export class StorageServer {
@@ -27,6 +26,7 @@ export class StorageServer {
27
26
  private wallet: Wallet
28
27
  private monetize: boolean
29
28
  private calculateRequestPrice?: (req: Request) => number | Promise<number>
29
+ private adminIdentityKeys?: string[]
30
30
 
31
31
  constructor(storage: StorageProvider, options: WalletStorageServerOptions) {
32
32
  this.storage = storage
@@ -34,6 +34,7 @@ export class StorageServer {
34
34
  this.wallet = options.wallet
35
35
  this.monetize = options.monetize
36
36
  this.calculateRequestPrice = options.calculateRequestPrice
37
+ this.adminIdentityKeys = options.adminIdentityKeys
37
38
 
38
39
  this.setupRoutes()
39
40
  }
@@ -103,6 +104,15 @@ export class StorageServer {
103
104
  throw new sdk.WERR_UNAUTHORIZED('function may only access authenticated user.')
104
105
  }
105
106
  break
107
+ case 'adminStats':
108
+ {
109
+ // TODO: add check for admin user
110
+ if (params[0] !== req.auth.identityKey)
111
+ throw new sdk.WERR_UNAUTHORIZED('function may only access authenticated admin user.')
112
+ if (!this.adminIdentityKeys || !this.adminIdentityKeys.includes(req.auth.identityKey))
113
+ throw new sdk.WERR_UNAUTHORIZED('function may only be accessed by admin user.')
114
+ }
115
+ break
106
116
  case 'processSyncChunk':
107
117
  {
108
118
  await this.validateParam0(params, req)