From e8a933af25438cd7db7ba8f65dce1e3107c0060e Mon Sep 17 00:00:00 2001 From: GuticaStefan Date: Tue, 7 Apr 2026 16:13:30 +0300 Subject: [PATCH 01/15] search after support --- src/common/entities/query.pagination.ts | 1 + .../circuit.breaker.proxy.service.ts | 6 ++- .../elastic/elastic.indexer.service.ts | 6 +-- .../indexer/entities/account.history.ts | 4 +- .../indexer/entities/account.token.history.ts | 4 +- src/common/indexer/entities/account.ts | 4 +- src/common/indexer/entities/block.ts | 4 +- src/common/indexer/entities/collection.ts | 4 +- .../indexer/entities/elastic.sortable.ts | 3 ++ src/common/indexer/entities/events.ts | 1 + src/common/indexer/entities/index.ts | 1 + src/common/indexer/entities/miniblock.ts | 4 +- .../indexer/entities/provider.delegators.ts | 1 + src/common/indexer/entities/round.ts | 4 +- src/common/indexer/entities/sc.deploy.ts | 4 +- src/common/indexer/entities/sc.result.ts | 4 +- src/common/indexer/entities/tag.ts | 4 +- src/common/indexer/entities/token.account.ts | 3 +- .../indexer/entities/transaction.log.ts | 6 ++- .../indexer/entities/transaction.receipt.ts | 4 +- src/common/indexer/entities/transaction.ts | 4 +- src/endpoints/accounts/account.controller.ts | 41 +++++++++++++------ .../applications/application.controller.ts | 4 +- src/endpoints/blocks/block.controller.ts | 4 +- .../collections/collection.controller.ts | 15 ++++--- src/endpoints/events/events.controller.ts | 4 +- .../marketplace/nft.marketplace.controller.ts | 12 ++++-- src/endpoints/mex/mex.controller.ts | 2 +- .../miniblocks/mini.block.controller.ts | 4 +- src/endpoints/nfts/nft.controller.ts | 12 ++++-- src/endpoints/nfts/nft.service.ts | 4 +- src/endpoints/nfttags/tag.controller.ts | 4 +- .../providers/provider.controller.ts | 7 +++- .../sc-results/scresult.controller.ts | 4 +- src/endpoints/tokens/token.controller.ts | 15 ++++--- .../transactions/transaction.controller.ts | 4 +- .../transfers/transfer.controller.ts | 4 +- 37 files changed, 152 insertions(+), 64 deletions(-) create mode 100644 src/common/indexer/entities/elastic.sortable.ts diff --git a/src/common/entities/query.pagination.ts b/src/common/entities/query.pagination.ts index 65950b1de..047764ad8 100644 --- a/src/common/entities/query.pagination.ts +++ b/src/common/entities/query.pagination.ts @@ -8,4 +8,5 @@ export class QueryPagination { before?: number; after?: number; + searchAfter?: string; } diff --git a/src/common/indexer/elastic/circuit-breaker/circuit.breaker.proxy.service.ts b/src/common/indexer/elastic/circuit-breaker/circuit.breaker.proxy.service.ts index 7cff25aea..ea79a21bd 100644 --- a/src/common/indexer/elastic/circuit-breaker/circuit.breaker.proxy.service.ts +++ b/src/common/indexer/elastic/circuit-breaker/circuit.breaker.proxy.service.ts @@ -68,8 +68,10 @@ export class EsCircuitBreakerProxy { } // eslint-disable-next-line require-await - async getList(index: string, id: string, query: ElasticQuery): Promise { - return this.withCircuitBreaker(() => this.elasticService.getList(index, id, query)); + async getList(index: string, id: string, query: ElasticQuery, searchAfter?: string): Promise { + //TODO: update package + // @ts-ignore + return this.withCircuitBreaker(() => this.elasticService.getList(index, id, query, undefined, searchAfter)); } // eslint-disable-next-line require-await diff --git a/src/common/indexer/elastic/elastic.indexer.service.ts b/src/common/indexer/elastic/elastic.indexer.service.ts index c3189b334..cc06c47ea 100644 --- a/src/common/indexer/elastic/elastic.indexer.service.ts +++ b/src/common/indexer/elastic/elastic.indexer.service.ts @@ -136,7 +136,7 @@ export class ElasticIndexerService implements IndexerInterface { query = this.buildTokenFilter(query, filter); - return await this.elasticService.getList('accountsesdt', 'token', query); + return await this.elasticService.getList('accountsesdt', 'token', query, queryPagination.searchAfter); } async getTokenAccountsCount(identifier: string): Promise { @@ -294,7 +294,7 @@ export class ElasticIndexerService implements IndexerInterface { .withPagination({ from: pagination.from, size: pagination.size }) .withSort([timestamp, nonce]); - const elasticOperations = await this.elasticService.getList('operations', 'txHash', elasticQuery); + const elasticOperations = await this.elasticService.getList('operations', 'txHash', elasticQuery, pagination.searchAfter); this.bulkProcessTransactions(elasticOperations); @@ -559,7 +559,7 @@ export class ElasticIndexerService implements IndexerInterface { .withPagination({ from: pagination.from, size: pagination.size }) .withSort([timestamp, nonce]); - const transactions = await this.elasticService.getList('operations', 'txHash', elasticQuery); + const transactions = await this.elasticService.getList('operations', 'txHash', elasticQuery, pagination.searchAfter); this.bulkProcessTransactions(transactions); diff --git a/src/common/indexer/entities/account.history.ts b/src/common/indexer/entities/account.history.ts index 80ba09b75..e3b8ca794 100644 --- a/src/common/indexer/entities/account.history.ts +++ b/src/common/indexer/entities/account.history.ts @@ -1,4 +1,6 @@ -export interface AccountHistory { +import { ElasticSortable } from "./elastic.sortable"; + +export interface AccountHistory extends ElasticSortable { address: string; timestamp: number; balance: string; diff --git a/src/common/indexer/entities/account.token.history.ts b/src/common/indexer/entities/account.token.history.ts index 4cbc4a5a5..1e6f384cf 100644 --- a/src/common/indexer/entities/account.token.history.ts +++ b/src/common/indexer/entities/account.token.history.ts @@ -1,4 +1,6 @@ -export interface AccountTokenHistory { +import { ElasticSortable } from "./elastic.sortable"; + +export interface AccountTokenHistory extends ElasticSortable { address: string; timestamp: number; balance: string; diff --git a/src/common/indexer/entities/account.ts b/src/common/indexer/entities/account.ts index fc5ff3296..d46241b14 100644 --- a/src/common/indexer/entities/account.ts +++ b/src/common/indexer/entities/account.ts @@ -1,4 +1,6 @@ -export interface Account { +import { ElasticSortable } from "./elastic.sortable"; + +export interface Account extends ElasticSortable { address: string; nonce: number; timestampMs: number; diff --git a/src/common/indexer/entities/block.ts b/src/common/indexer/entities/block.ts index ebfd4aef1..eb9c7bbe0 100644 --- a/src/common/indexer/entities/block.ts +++ b/src/common/indexer/entities/block.ts @@ -1,4 +1,6 @@ -export interface Block { +import { ElasticSortable } from "./elastic.sortable"; + +export interface Block extends ElasticSortable { hash: string; nonce: number; round: number; diff --git a/src/common/indexer/entities/collection.ts b/src/common/indexer/entities/collection.ts index 5190e17f6..b5ce61a03 100644 --- a/src/common/indexer/entities/collection.ts +++ b/src/common/indexer/entities/collection.ts @@ -1,3 +1,5 @@ +import { ElasticSortable } from "./elastic.sortable"; + export interface CollectionProperties { canMint?: boolean; canBurn?: boolean; @@ -11,7 +13,7 @@ export interface CollectionProperties { canCreateMultiShard?: boolean; } -export interface Collection { +export interface Collection extends ElasticSortable { _id: string; name: string; ticker: string; diff --git a/src/common/indexer/entities/elastic.sortable.ts b/src/common/indexer/entities/elastic.sortable.ts new file mode 100644 index 000000000..f5847fb47 --- /dev/null +++ b/src/common/indexer/entities/elastic.sortable.ts @@ -0,0 +1,3 @@ +export interface ElasticSortable { + searchAfter?: string; +} \ No newline at end of file diff --git a/src/common/indexer/entities/events.ts b/src/common/indexer/entities/events.ts index add007142..82c26262d 100644 --- a/src/common/indexer/entities/events.ts +++ b/src/common/indexer/entities/events.ts @@ -1,4 +1,5 @@ export class Events { + searchAfter?: string; _id: string = ''; logAddress: string = ''; identifier: string = ''; diff --git a/src/common/indexer/entities/index.ts b/src/common/indexer/entities/index.ts index 2cc7597c3..1c8d7045a 100644 --- a/src/common/indexer/entities/index.ts +++ b/src/common/indexer/entities/index.ts @@ -14,3 +14,4 @@ export { TokenAccount, TokenType } from './token.account'; export { Transaction } from './transaction'; export { TransactionLog, TransactionLogEvent, ElasticTransactionLogEvent } from './transaction.log'; export { TransactionReceipt } from './transaction.receipt'; +export { ElasticSortable } from './elastic.sortable'; diff --git a/src/common/indexer/entities/miniblock.ts b/src/common/indexer/entities/miniblock.ts index 71e086355..fafaa082e 100644 --- a/src/common/indexer/entities/miniblock.ts +++ b/src/common/indexer/entities/miniblock.ts @@ -1,4 +1,6 @@ -export interface MiniBlock { +import { ElasticSortable } from "./elastic.sortable"; + +export interface MiniBlock extends ElasticSortable { miniBlockHash: string; senderShard: number; receiverShard: number; diff --git a/src/common/indexer/entities/provider.delegators.ts b/src/common/indexer/entities/provider.delegators.ts index b4f27bbe5..c81c106a0 100644 --- a/src/common/indexer/entities/provider.delegators.ts +++ b/src/common/indexer/entities/provider.delegators.ts @@ -1,4 +1,5 @@ export class ProviderDelegators { + searchAfter?: string; contract: string = ''; address: string = ''; activeStake: string = ''; diff --git a/src/common/indexer/entities/round.ts b/src/common/indexer/entities/round.ts index 867c67d47..5a248665f 100644 --- a/src/common/indexer/entities/round.ts +++ b/src/common/indexer/entities/round.ts @@ -1,4 +1,6 @@ -export interface Round { +import { ElasticSortable } from "./elastic.sortable"; + +export interface Round extends ElasticSortable { round: number, signersIndexes: number[], blockWasProposed: boolean, diff --git a/src/common/indexer/entities/sc.deploy.ts b/src/common/indexer/entities/sc.deploy.ts index 0233d0e72..942130871 100644 --- a/src/common/indexer/entities/sc.deploy.ts +++ b/src/common/indexer/entities/sc.deploy.ts @@ -1,4 +1,6 @@ -export interface ScDeploy { +import { ElasticSortable } from "./elastic.sortable"; + +export interface ScDeploy extends ElasticSortable { address: string; contract: string; deployTxHash: string; diff --git a/src/common/indexer/entities/sc.result.ts b/src/common/indexer/entities/sc.result.ts index 882898b59..104acafe4 100644 --- a/src/common/indexer/entities/sc.result.ts +++ b/src/common/indexer/entities/sc.result.ts @@ -1,4 +1,6 @@ -export interface ScResult { +import { ElasticSortable } from "./elastic.sortable"; + +export interface ScResult extends ElasticSortable { scHash: string nonce: number; gasLimit: string; diff --git a/src/common/indexer/entities/tag.ts b/src/common/indexer/entities/tag.ts index c56f9077f..b7e5a91a7 100644 --- a/src/common/indexer/entities/tag.ts +++ b/src/common/indexer/entities/tag.ts @@ -1,4 +1,6 @@ -export interface Tag { +import { ElasticSortable } from "./elastic.sortable"; + +export interface Tag extends ElasticSortable { count: number; tag: string; } diff --git a/src/common/indexer/entities/token.account.ts b/src/common/indexer/entities/token.account.ts index 0a05b0332..d14dbbec9 100644 --- a/src/common/indexer/entities/token.account.ts +++ b/src/common/indexer/entities/token.account.ts @@ -1,6 +1,7 @@ import { registerEnumType } from "@nestjs/graphql"; +import { ElasticSortable } from "./elastic.sortable"; -export interface TokenAccount { +export interface TokenAccount extends ElasticSortable { identifier: string; address: string; balance: string; diff --git a/src/common/indexer/entities/transaction.log.ts b/src/common/indexer/entities/transaction.log.ts index 3a71225a3..79f048902 100644 --- a/src/common/indexer/entities/transaction.log.ts +++ b/src/common/indexer/entities/transaction.log.ts @@ -1,4 +1,6 @@ -export interface TransactionLog { +import { ElasticSortable } from "./elastic.sortable"; + +export interface TransactionLog extends ElasticSortable { id: string; originalTxHash: string; address: string; @@ -14,7 +16,7 @@ export interface TransactionLogEvent { order: number; } -export interface ElasticTransactionLogEvent { +export interface ElasticTransactionLogEvent extends ElasticSortable { address: string; identifier: string; topics: string[]; diff --git a/src/common/indexer/entities/transaction.receipt.ts b/src/common/indexer/entities/transaction.receipt.ts index 61a91d87c..043402ed2 100644 --- a/src/common/indexer/entities/transaction.receipt.ts +++ b/src/common/indexer/entities/transaction.receipt.ts @@ -1,4 +1,6 @@ -export interface TransactionReceipt { +import { ElasticSortable } from "./elastic.sortable"; + +export interface TransactionReceipt extends ElasticSortable { receiptHash: string; value: string; sender: string; diff --git a/src/common/indexer/entities/transaction.ts b/src/common/indexer/entities/transaction.ts index a65abd524..92f7e59cd 100644 --- a/src/common/indexer/entities/transaction.ts +++ b/src/common/indexer/entities/transaction.ts @@ -1,4 +1,6 @@ -export interface Transaction { +import { ElasticSortable } from "./elastic.sortable"; + +export interface Transaction extends ElasticSortable { hash: string; miniBlockHash: string; nonce: number; diff --git a/src/endpoints/accounts/account.controller.ts b/src/endpoints/accounts/account.controller.ts index 40910e8a7..768fedd26 100644 --- a/src/endpoints/accounts/account.controller.ts +++ b/src/endpoints/accounts/account.controller.ts @@ -85,6 +85,7 @@ export class AccountController { @ApiOkResponse({ type: [Account] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) @ApiQuery({ name: 'ownerAddress', description: 'Search by owner address', required: false }) @ApiQuery({ name: 'sort', description: 'Sort criteria (balance / timestamp)', required: false, enum: AccountSort }) @ApiQuery({ name: 'order', description: 'Sort order (asc/desc)', required: false, enum: SortOrder }) @@ -102,6 +103,7 @@ export class AccountController { getAccounts( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query("ownerAddress", ParseAddressPipe) ownerAddress?: string, @Query("name") name?: string, @Query("tags", ParseArrayPipe) tags?: string[], @@ -136,7 +138,7 @@ export class AccountController { }); queryOptions.validate(size); return this.accountService.getAccounts( - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), queryOptions, ); } @@ -277,6 +279,7 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('type', new ParseEnumPipe(TokenType)) type?: TokenType, @Query('subType', new ParseEnumPipe(NftSubType)) subType?: NftSubType, @Query('search') search?: string, @@ -288,7 +291,7 @@ export class AccountController { @Query('mexPairType', new ParseEnumArrayPipe(MexPairType)) mexPairType?: MexPairType[], ): Promise { try { - return await this.tokenService.getTokensForAddress(address, new QueryPagination({ from, size }), new TokenFilter({ type, subType, search, name, identifier, identifiers, includeMetaESDT, mexPairType })); + return await this.tokenService.getTokensForAddress(address, new QueryPagination({ from, size, searchAfter }), new TokenFilter({ type, subType, search, name, identifier, identifiers, includeMetaESDT, mexPairType })); } catch (error) { this.logger.error(`Error in getAccountTokens for address ${address}`); this.logger.error(error); @@ -392,6 +395,7 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('search') search?: string, @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], @Query('subType', new ParseEnumArrayPipe(NftSubType)) subType?: NftSubType[], @@ -418,7 +422,7 @@ export class AccountController { canAddUri, canTransferRole, excludeMetaESDT, - }), new QueryPagination({ from, size })); + }), new QueryPagination({ from, size, searchAfter })); } @Get("/accounts/:address/roles/collections/count") @@ -493,13 +497,14 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('search') search?: string, @Query('owner', ParseAddressPipe) owner?: string, @Query('canMint', ParseBoolPipe) canMint?: boolean, @Query('canBurn', ParseBoolPipe) canBurn?: boolean, @Query('includeMetaESDT', ParseBoolPipe) includeMetaESDT?: boolean, ): Promise { - return await this.tokenService.getTokensWithRolesForAddress(address, new TokenWithRolesFilter({ search, owner, canMint, canBurn, includeMetaESDT }), new QueryPagination({ from, size })); + return await this.tokenService.getTokensWithRolesForAddress(address, new TokenWithRolesFilter({ search, owner, canMint, canBurn, includeMetaESDT }), new QueryPagination({ from, size, searchAfter })); } @Get("/accounts/:address/roles/tokens/count") @@ -674,7 +679,7 @@ export class AccountController { return await this.nftService.getNftsForAddress( address, - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), new NftFilter({ search, identifiers, @@ -871,6 +876,7 @@ export class AccountController { @ApiOkResponse({ type: [Transaction] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) @ApiQuery({ name: 'sender', description: 'Address of the transaction sender', required: false }) @ApiQuery({ name: 'receiver', description: 'Search by multiple receiver addresses, comma-separated', required: false }) @ApiQuery({ name: 'token', description: 'Identifier of the token', required: false }) @@ -901,6 +907,7 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('sender', ParseAddressPipe) sender?: string, @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @Query('token') token?: string, @@ -949,7 +956,7 @@ export class AccountController { withRelayedScresults, }); TransactionFilter.validate(transactionFilter, size); - return await this.transactionService.getTransactions(transactionFilter, new QueryPagination({ from, size }), options, address, fields); + return await this.transactionService.getTransactions(transactionFilter, new QueryPagination({ from, size, searchAfter }), options, address, fields); } @Get("/accounts/:address/transactions/count") @@ -1018,6 +1025,7 @@ export class AccountController { @ApplyComplexity({ target: TransactionDetailed }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) @ApiQuery({ name: 'sender', description: 'Address of the transfer sender', required: false }) @ApiQuery({ name: 'receiver', description: 'Search by multiple receiver addresses, comma-separated', required: false }) @ApiQuery({ name: 'token', description: 'Identifier of the token', required: false }) @@ -1047,6 +1055,7 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('sender', ParseAddressArrayPipe) sender?: string[], @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @Query('token') token?: string, @@ -1097,7 +1106,7 @@ export class AccountController { withTxsRelayedByAddress, isScCall, }), - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), options, fields ); @@ -1213,8 +1222,9 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, ): Promise { - return this.accountService.getAccountDeploys(new QueryPagination({ from, size }), address); + return this.accountService.getAccountDeploys(new QueryPagination({ from, size, searchAfter }), address); } @Get("/accounts/:address/deploys/count") @@ -1239,8 +1249,9 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, ): Promise { - return this.accountService.getAccountContracts(new QueryPagination({ from, size }), address); + return this.accountService.getAccountContracts(new QueryPagination({ from, size, searchAfter }), address); } @Get("/accounts/:address/contracts/count") @@ -1278,8 +1289,9 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, ): Promise { - return this.scResultService.getAccountScResults(address, new QueryPagination({ from, size })); + return this.scResultService.getAccountScResults(address, new QueryPagination({ from, size, searchAfter })); } @Get("/accounts/:address/results/count") @@ -1317,12 +1329,13 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('before', TimestampParsePipe) before?: number, @Query('after', TimestampParsePipe) after?: number, ): Promise { return this.accountService.getAccountHistory( address, - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), new AccountHistoryFilter({ before, after })); } @@ -1375,6 +1388,7 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('before', TimestampParsePipe) before?: number, @Query('after', TimestampParsePipe) after?: number, @Query('identifier', ParseArrayPipe) identifier?: string[], @@ -1382,7 +1396,7 @@ export class AccountController { ): Promise { return await this.accountService.getAccountEsdtHistory( address, - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), new AccountHistoryFilter({ before, after, identifiers: identifier, token })); } @@ -1416,6 +1430,7 @@ export class AccountController { @Param('tokenIdentifier', ParseTokenOrNftPipe) tokenIdentifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('before', TimestampParsePipe) before?: number, @Query('after', TimestampParsePipe) after?: number, ): Promise { @@ -1426,7 +1441,7 @@ export class AccountController { return await this.accountService.getAccountTokenHistory( address, tokenIdentifier, - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), new AccountHistoryFilter({ before, after })); } } diff --git a/src/endpoints/applications/application.controller.ts b/src/endpoints/applications/application.controller.ts index e9fcbd0bc..085062acb 100644 --- a/src/endpoints/applications/application.controller.ts +++ b/src/endpoints/applications/application.controller.ts @@ -19,19 +19,21 @@ export class ApplicationController { @ApiOkResponse({ type: [Application] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) @ApiQuery({ name: 'before', description: 'Before timestamp or timestampMs', required: false }) @ApiQuery({ name: 'after', description: 'After timestamp or timestampMs', required: false }) @ApiQuery({ name: 'withTxCount', description: 'Include transaction count', required: false, type: Boolean }) async getApplications( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('before', TimestampParsePipe) before?: number, @Query('after', TimestampParsePipe) after?: number, @Query('withTxCount', new ParseBoolPipe()) withTxCount?: boolean, ): Promise { const applicationFilter = new ApplicationFilter({ before, after, withTxCount }); return await this.applicationService.getApplications( - new QueryPagination({ size, from }), + new QueryPagination({ size, from, searchAfter }), applicationFilter ); } diff --git a/src/endpoints/blocks/block.controller.ts b/src/endpoints/blocks/block.controller.ts index 71e832222..8541dd078 100644 --- a/src/endpoints/blocks/block.controller.ts +++ b/src/endpoints/blocks/block.controller.ts @@ -22,6 +22,7 @@ export class BlockController { @ApiQuery({ name: 'epoch', description: 'Filter by epoch', required: false }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiQuery({ name: 'nonce', description: 'Filter by nonce', required: false }) @ApiQuery({ name: 'hashes', description: 'Search by blocks hashes, comma-separated', required: false }) @ApiQuery({ name: 'order', description: 'Order blocks (asc/desc) by timestamp', required: false, enum: SortOrder }) @@ -29,6 +30,7 @@ export class BlockController { getBlocks( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('shard', ParseIntPipe) shard?: number, @Query('proposer', ParseBlsHashPipe) proposer?: string, @Query('validator', ParseBlsHashPipe) validator?: string, @@ -42,7 +44,7 @@ export class BlockController { new BlockFilter( { shard, proposer, validator, epoch, nonce, hashes, order }), new QueryPagination( - { from, size }), withProposerIdentity); + { from, size, searchAfter }), withProposerIdentity); } @Get("/blocks/count") diff --git a/src/endpoints/collections/collection.controller.ts b/src/endpoints/collections/collection.controller.ts index a49f6483c..af7166e43 100644 --- a/src/endpoints/collections/collection.controller.ts +++ b/src/endpoints/collections/collection.controller.ts @@ -62,6 +62,7 @@ export class CollectionController { async getNftCollections( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('search') search?: string, @Query('identifiers', ParseCollectionArrayPipe) identifiers?: string[], @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], @@ -79,7 +80,7 @@ export class CollectionController { @Query('sort', new ParseEnumPipe(SortCollections)) sort?: SortCollections, @Query('order', new ParseEnumPipe(SortOrder)) order?: SortOrder, ): Promise { - return await this.collectionService.getNftCollections(new QueryPagination({ from, size }), new CollectionFilter({ + return await this.collectionService.getNftCollections(new QueryPagination({ from, size, searchAfter }), new CollectionFilter({ search, type, subType, @@ -233,6 +234,7 @@ export class CollectionController { @Param('collection', ParseCollectionPipe) collection: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('search') search?: string, @Query('identifiers', ParseNftArrayPipe) identifiers?: string[], @Query('name') name?: string, @@ -256,7 +258,7 @@ export class CollectionController { } return await this.nftService.getNfts( - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), new NftFilter({ search, identifiers, collection, name, tags, creator, hasUris, isWhitelistedStorage, isNsfw, traits, nonceBefore, nonceAfter, sort, order }), new NftQueryOptions({ withOwner, withSupply, withAssets }), ); @@ -307,8 +309,9 @@ export class CollectionController { @Param('identifier', ParseCollectionPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, ): Promise { - const owners = await this.nftService.getCollectionOwners(identifier, new QueryPagination({ from, size })); + const owners = await this.nftService.getCollectionOwners(identifier, new QueryPagination({ from, size, searchAfter })); if (!owners) { throw new HttpException('Collection not found', HttpStatus.NOT_FOUND); } @@ -345,6 +348,7 @@ export class CollectionController { @Param('collection', ParseCollectionPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('sender', ParseAddressPipe) sender?: string, @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @Query('senderShard', ParseIntPipe) senderShard?: number, @@ -389,7 +393,7 @@ export class CollectionController { }); TransactionFilter.validate(transactionFilter, size); - return await this.transactionService.getTransactions(transactionFilter, new QueryPagination({ from, size }), options); + return await this.transactionService.getTransactions(transactionFilter, new QueryPagination({ from, size, searchAfter }), options); } @Get("/collections/:collection/transactions/count") @@ -470,6 +474,7 @@ export class CollectionController { @Param('collection', ParseCollectionPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('sender', ParseAddressPipe) sender?: string, @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @Query('senderShard', ParseIntPipe) senderShard?: number, @@ -509,7 +514,7 @@ export class CollectionController { after, order, round, - }), new QueryPagination({ from, size }), options); + }), new QueryPagination({ from, size, searchAfter }), options); } @Get("/collections/:collection/transfers/count") diff --git a/src/endpoints/events/events.controller.ts b/src/endpoints/events/events.controller.ts index dc7ce3a0a..437561e8a 100644 --- a/src/endpoints/events/events.controller.ts +++ b/src/endpoints/events/events.controller.ts @@ -20,6 +20,7 @@ export class EventsController { @ApiOkResponse({ type: [Events] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiQuery({ name: 'address', description: 'Event address', required: false }) @ApiQuery({ name: 'logAddress', description: 'Event log address', required: false }) @ApiQuery({ name: 'identifier', description: 'Event identifier', required: false }) @@ -32,6 +33,7 @@ export class EventsController { async getEvents( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('address', ParseAddressPipe) address: string, @Query('logAddress', ParseAddressPipe) logAddress: string, @Query('identifier') identifier: string, @@ -44,7 +46,7 @@ export class EventsController { ): Promise { const topicsArray = topics ? (Array.isArray(topics) ? topics : [topics]) : []; return await this.eventsService.getEvents( - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), new EventsFilter({ address, logAddress, identifier, txHash, shard, after, before, order, topics: topicsArray })); } diff --git a/src/endpoints/marketplace/nft.marketplace.controller.ts b/src/endpoints/marketplace/nft.marketplace.controller.ts index 8a07a7374..b529f55ce 100644 --- a/src/endpoints/marketplace/nft.marketplace.controller.ts +++ b/src/endpoints/marketplace/nft.marketplace.controller.ts @@ -20,11 +20,13 @@ export class NftMarketplaceController { @ApiOperation({ summary: 'Explore auctions', description: 'Returns auctions available in marketplaces ' }) @ApiOkResponse({ type: [Auctions] }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) async getAuctions( @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, ): Promise { return await this.nftMarketplaceService.getAuctions( - new QueryPagination({ size }), + new QueryPagination({ size, searchAfter }), ); } @@ -77,15 +79,17 @@ export class NftMarketplaceController { @ApiOperation({ summary: 'Account auctions', description: 'Returns account auctions for a given address' }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) @ApiQuery({ name: 'status', description: 'Returns auctions with specified status', required: false }) @ApiOkResponse({ type: Auction }) async getAccountAuctions( @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('status', new ParseEnumPipe(AuctionStatus)) status?: AuctionStatus, ): Promise { - const account = await this.nftMarketplaceService.getAccountAuctions(new QueryPagination({ from, size }), address, status); + const account = await this.nftMarketplaceService.getAccountAuctions(new QueryPagination({ from, size, searchAfter }), address, status); if (!account) { throw new NotFoundException('Account not found'); } @@ -116,12 +120,14 @@ export class NftMarketplaceController { @ApiOperation({ summary: 'Collection auctions', description: 'Returns all auctions for a specific collection ' }) @ApiOkResponse({ type: [Auctions] }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) @ApiQuery({ name: 'collection', description: 'Collection identifier', required: true }) async getCollectionAuctions( @Param('collection', ParseCollectionPipe) collection: string, @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, ): Promise { - return await this.nftMarketplaceService.getCollectionAuctions(new QueryPagination({ size }), collection); + return await this.nftMarketplaceService.getCollectionAuctions(new QueryPagination({ size, searchAfter }), collection); } @Get('/collections/:collection/auctions/count') diff --git a/src/endpoints/mex/mex.controller.ts b/src/endpoints/mex/mex.controller.ts index b520de22a..1da343e90 100644 --- a/src/endpoints/mex/mex.controller.ts +++ b/src/endpoints/mex/mex.controller.ts @@ -133,7 +133,7 @@ export class MexController { @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) async getMexFarms( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, - @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number + @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, ): Promise { return await this.mexFarmsService.getMexFarms(new QueryPagination({ from, size })); } diff --git a/src/endpoints/miniblocks/mini.block.controller.ts b/src/endpoints/miniblocks/mini.block.controller.ts index d530f598d..864b26b00 100644 --- a/src/endpoints/miniblocks/mini.block.controller.ts +++ b/src/endpoints/miniblocks/mini.block.controller.ts @@ -18,15 +18,17 @@ export class MiniBlockController { @ApiOkResponse({ type: [MiniBlockDetailed] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) @ApiQuery({ name: 'hashes', description: 'Filter by a comma-separated list of miniblocks hashes', required: false }) @ApiQuery({ name: 'type', description: 'Sorting criteria by type', required: false, enum: MiniBlockType }) async getMiniBlocks( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('hashes', ParseArrayPipe) hashes?: string[], @Query('type', new ParseEnumPipe(MiniBlockType)) type?: MiniBlockType, ): Promise { - return await this.miniBlockService.getMiniBlocks(new QueryPagination({ from, size }), new MiniBlockFilter({ hashes, type })); + return await this.miniBlockService.getMiniBlocks(new QueryPagination({ from, size, searchAfter }), new MiniBlockFilter({ hashes, type })); } @Get("/miniblocks/:miniBlockHash") diff --git a/src/endpoints/nfts/nft.controller.ts b/src/endpoints/nfts/nft.controller.ts index 3c04ea1e8..374f631cd 100644 --- a/src/endpoints/nfts/nft.controller.ts +++ b/src/endpoints/nfts/nft.controller.ts @@ -60,6 +60,7 @@ export class NftController { async getNfts( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('search') search?: string, @Query('identifiers', ParseNftArrayPipe) identifiers?: string[], @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], @@ -81,7 +82,7 @@ export class NftController { @Query('withSupply', ParseBoolPipe) withSupply?: boolean, ): Promise { return await this.nftService.getNfts( - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), new NftFilter({ search, identifiers, @@ -253,8 +254,9 @@ export class NftController { @Param('identifier', ParseNftPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, ): Promise { - const owners = await this.nftService.getNftOwners(identifier, new QueryPagination({ from, size })); + const owners = await this.nftService.getNftOwners(identifier, new QueryPagination({ from, size, searchAfter })); if (owners === undefined) { throw new HttpException('NFT not found', HttpStatus.NOT_FOUND); } @@ -303,6 +305,7 @@ export class NftController { @Param('identifier', ParseNftPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('sender', ParseAddressPipe) sender?: string, @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @Query('senderShard', ParseIntPipe) senderShard?: number, @@ -341,7 +344,7 @@ export class NftController { }); TransactionFilter.validate(transactionFilter, size); - return await this.transactionService.getTransactions(transactionFilter, new QueryPagination({ from, size }), options); + return await this.transactionService.getTransactions(transactionFilter, new QueryPagination({ from, size, searchAfter }), options); } @Get("/nfts/:identifier/transactions/count") @@ -414,6 +417,7 @@ export class NftController { @Param('identifier', ParseNftPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('sender', ParseAddressPipe) sender?: string, @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @Query('senderShard', ParseIntPipe) senderShard?: number, @@ -446,7 +450,7 @@ export class NftController { before, after, order, - }), new QueryPagination({ from, size }), options); + }), new QueryPagination({ from, size, searchAfter }), options); } @Get("/nfts/:identifier/transfers/count") diff --git a/src/endpoints/nfts/nft.service.ts b/src/endpoints/nfts/nft.service.ts index 6c2f0cf9d..9d5b7fc6a 100644 --- a/src/endpoints/nfts/nft.service.ts +++ b/src/endpoints/nfts/nft.service.ts @@ -79,9 +79,7 @@ export class NftService { } private async fetchAndProcessNfts(queryPagination: QueryPagination, filter: NftFilter, queryOptions?: NftQueryOptions): Promise { - const { from, size } = queryPagination; - - const nfts = await this.getNftsInternal({ from, size }, filter); + const nfts = await this.getNftsInternal(queryPagination, filter); await Promise.all([ this.conditionallyApplyAssetsAndTicker(nfts, undefined, queryOptions), diff --git a/src/endpoints/nfttags/tag.controller.ts b/src/endpoints/nfttags/tag.controller.ts index 62a91db38..9a97ba262 100644 --- a/src/endpoints/nfttags/tag.controller.ts +++ b/src/endpoints/nfttags/tag.controller.ts @@ -17,13 +17,15 @@ export class TagController { @ApiOkResponse({ type: [Tag] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiQuery({ name: 'search', description: 'Search by tag name', required: false }) async getTags( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('search') search: string | undefined, ): Promise { - return await this.nftTagsService.getNftTags(new QueryPagination({ from, size }), search); + return await this.nftTagsService.getNftTags(new QueryPagination({ from, size, searchAfter }), search); } @Get("/tags/count") diff --git a/src/endpoints/providers/provider.controller.ts b/src/endpoints/providers/provider.controller.ts index eb444c9ee..67673c245 100644 --- a/src/endpoints/providers/provider.controller.ts +++ b/src/endpoints/providers/provider.controller.ts @@ -41,11 +41,14 @@ export class ProviderController { @ApiNotFoundResponse({ description: 'Provider not found' }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) async getProviderAccounts( @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, - @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number,): Promise { - const provider = await this.providerService.getProviderAccounts(address, new QueryPagination({ from, size })); + @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, + ): Promise { + const provider = await this.providerService.getProviderAccounts(address, new QueryPagination({ from, size, searchAfter })); if (provider === undefined) { throw new HttpException(`Provider '${address}' not found`, HttpStatus.NOT_FOUND); diff --git a/src/endpoints/sc-results/scresult.controller.ts b/src/endpoints/sc-results/scresult.controller.ts index 1111f5648..5c80da032 100644 --- a/src/endpoints/sc-results/scresult.controller.ts +++ b/src/endpoints/sc-results/scresult.controller.ts @@ -17,6 +17,7 @@ export class SmartContractResultController { @ApiOperation({ summary: 'Smart contract results', description: 'Returns all smart contract results available on the blockchain' }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiQuery({ name: 'miniBlockHash', description: 'The hash of the parent miniBlock', required: false }) @ApiQuery({ name: 'originalTxHashes', description: 'Original transaction hashes', required: false }) @ApiQuery({ name: 'sender', description: 'Sender address', required: false }) @@ -27,6 +28,7 @@ export class SmartContractResultController { getScResults( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('miniBlockHash', ParseBlockHashPipe) miniBlockHash?: string, @Query('originalTxHashes', ParseArrayPipe, ParseTransactionHashPipe) originalTxHashes?: string[], @Query('sender', ParseAddressPipe) sender?: string, @@ -35,7 +37,7 @@ export class SmartContractResultController { @Query('withActionTransferValue', ParseBoolPipe) withActionTransferValue?: boolean, ): Promise { return this.scResultService.getScResults( - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), new SmartContractResultFilter({ miniBlockHash, originalTxHashes, sender, receiver, functions }), new SmartContractResultOptions({ withActionTransferValue }), ); diff --git a/src/endpoints/tokens/token.controller.ts b/src/endpoints/tokens/token.controller.ts index ca67cb242..7ca2e1848 100644 --- a/src/endpoints/tokens/token.controller.ts +++ b/src/endpoints/tokens/token.controller.ts @@ -40,6 +40,7 @@ export class TokenController { @ApiOkResponse({ type: [TokenDetailed] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiQuery({ name: 'type', description: 'Token type', required: false, enum: TokenType }) @ApiQuery({ name: 'search', description: 'Search by collection identifier', required: false }) @ApiQuery({ name: 'name', description: 'Search by token name', required: false }) @@ -53,6 +54,7 @@ export class TokenController { async getTokens( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('type', new ParseEnumPipe(TokenType)) type?: TokenType, @Query('search') search?: string, @Query('name') name?: string, @@ -66,7 +68,7 @@ export class TokenController { ): Promise { return await this.tokenService.getTokens( - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), new TokenFilter({ type, search, name, identifier, identifiers, includeMetaESDT, sort, order, mexPairType, priceSource }) ); } @@ -159,14 +161,15 @@ export class TokenController { async getTokenAccounts( @Param('identifier', ParseTokenPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, - @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number + @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, ): Promise { const isToken = await this.tokenService.isToken(identifier); if (!isToken) { throw new HttpException('Token not found', HttpStatus.NOT_FOUND); } - const accounts = await this.tokenService.getTokenAccounts(new QueryPagination({ from, size }), identifier); + const accounts = await this.tokenService.getTokenAccounts(new QueryPagination({ from, size, searchAfter }), identifier); if (!accounts) { throw new NotFoundException('Token not found'); } @@ -226,6 +229,7 @@ export class TokenController { @Param('identifier', ParseTokenPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('sender', ParseAddressPipe) sender?: string, @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @Query('senderShard', ParseIntPipe) senderShard?: number, @@ -277,7 +281,7 @@ export class TokenController { return await this.transactionService.getTransactions( transactionFilter, - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), options, undefined, fields, @@ -406,6 +410,7 @@ export class TokenController { @Param('identifier', ParseTokenPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('sender', ParseAddressArrayPipe) sender?: string[], @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @Query('senderShard', ParseIntPipe) senderShard?: number, @@ -448,7 +453,7 @@ export class TokenController { round, isScCall, }), - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), options, fields ); diff --git a/src/endpoints/transactions/transaction.controller.ts b/src/endpoints/transactions/transaction.controller.ts index a3829b949..1d460ed0b 100644 --- a/src/endpoints/transactions/transaction.controller.ts +++ b/src/endpoints/transactions/transaction.controller.ts @@ -42,6 +42,7 @@ export class TransactionController { @ApiQuery({ name: 'fields', description: 'List of fields to filter by', required: false, isArray: true, style: 'form', explode: false }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) @ApiQuery({ name: 'condition', description: 'Condition for elastic search queries', required: false, deprecated: true }) @ApiQuery({ name: 'withScResults', description: 'Return results for transactions. When "withScResults" parameter is applied, complexity estimation is 200', required: false, type: Boolean }) @ApiQuery({ name: 'withOperations', description: 'Return operations for transactions. When "withOperations" parameter is applied, complexity estimation is 200', required: false, type: Boolean }) @@ -57,6 +58,7 @@ export class TransactionController { getTransactions( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('sender', ParseAddressAndMetachainPipe) sender?: string, @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @Query('relayer', ParseAddressPipe) relayer?: string, @@ -108,7 +110,7 @@ export class TransactionController { }); TransactionFilter.validate(transactionFilter, size); return this.transactionService.getTransactions(transactionFilter, - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), options, undefined, fields, diff --git a/src/endpoints/transfers/transfer.controller.ts b/src/endpoints/transfers/transfer.controller.ts index 8e02225fd..76b7204a5 100644 --- a/src/endpoints/transfers/transfer.controller.ts +++ b/src/endpoints/transfers/transfer.controller.ts @@ -25,6 +25,7 @@ export class TransferController { @ApiOkResponse({ type: [Transaction] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiQuery({ name: 'sender', description: 'Search by multiple sender addresses, comma-separated', required: false }) @ApiQuery({ name: 'receiver', description: 'Search by multiple receiver addresses, comma-separated', required: false }) @ApiQuery({ name: 'token', description: 'Identifier of the token', required: false }) @@ -53,6 +54,7 @@ export class TransferController { async getAccountTransfers( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @Query('sender', ParseAddressArrayPipe) sender?: string[], @Query('token') token?: string, @@ -102,7 +104,7 @@ export class TransferController { withRefunds, isScCall, }), - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), options, fields ); From b74c3e815d688d73e136b34d7070688bfab390ff Mon Sep 17 00:00:00 2001 From: GuticaStefan Date: Mon, 20 Apr 2026 16:29:20 +0300 Subject: [PATCH 02/15] fixes --- src/endpoints/accounts/account.controller.ts | 2 +- src/endpoints/events/events.controller.ts | 2 +- src/endpoints/nfttags/tag.controller.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/endpoints/accounts/account.controller.ts b/src/endpoints/accounts/account.controller.ts index 768fedd26..16e4fb715 100644 --- a/src/endpoints/accounts/account.controller.ts +++ b/src/endpoints/accounts/account.controller.ts @@ -672,8 +672,8 @@ export class AccountController { @Query('scamType', new ParseEnumPipe(ScamType)) scamType?: ScamType, @Query('timestamp', ParseIntPipe) _timestamp?: number, @Query('withReceivedAt', ParseBoolPipe) withReceivedAt?: boolean, + @Query('searchAfter') searchAfter?: string, ): Promise { - // Validăm opțiunile de interogare const queryOptions = new NftQueryOptions({ withSupply, withReceivedAt }); queryOptions.validate(size); diff --git a/src/endpoints/events/events.controller.ts b/src/endpoints/events/events.controller.ts index 437561e8a..6272c788e 100644 --- a/src/endpoints/events/events.controller.ts +++ b/src/endpoints/events/events.controller.ts @@ -33,7 +33,6 @@ export class EventsController { async getEvents( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, - @Query('searchAfter') searchAfter?: string, @Query('address', ParseAddressPipe) address: string, @Query('logAddress', ParseAddressPipe) logAddress: string, @Query('identifier') identifier: string, @@ -43,6 +42,7 @@ export class EventsController { @Query('after', TimestampParsePipe) after: number, @Query('order', ParseIntPipe) order: number, @Query('topics') topics: string | string[], + @Query('searchAfter') searchAfter?: string, ): Promise { const topicsArray = topics ? (Array.isArray(topics) ? topics : [topics]) : []; return await this.eventsService.getEvents( diff --git a/src/endpoints/nfttags/tag.controller.ts b/src/endpoints/nfttags/tag.controller.ts index 9a97ba262..1cf07961d 100644 --- a/src/endpoints/nfttags/tag.controller.ts +++ b/src/endpoints/nfttags/tag.controller.ts @@ -22,8 +22,8 @@ export class TagController { async getTags( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('search') search: string, @Query('searchAfter') searchAfter?: string, - @Query('search') search: string | undefined, ): Promise { return await this.nftTagsService.getNftTags(new QueryPagination({ from, size, searchAfter }), search); } @@ -33,7 +33,7 @@ export class TagController { @ApiQuery({ name: 'search', description: 'Search by tag name', required: false }) @ApiOkResponse({ type: Number }) async getTagCount( - @Query('search') search: string | undefined, + @Query('search') search: string, ): Promise { return await this.nftTagsService.getNftTagCount(search); } From ef51d9a60eeac16f8ccab60f0978f257c0321ad0 Mon Sep 17 00:00:00 2001 From: GuticaStefan Date: Wed, 10 Jun 2026 13:19:06 +0300 Subject: [PATCH 03/15] fixes & improvements --- .../elastic/elastic.indexer.service.ts | 273 +++++++++++++----- src/common/indexer/entities/events.ts | 2 +- src/endpoints/accounts/account.controller.ts | 23 +- src/endpoints/accounts/entities/account.ts | 3 + .../applications/application.service.ts | 1 + .../applications/entities/application.ts | 3 + src/endpoints/blocks/entities/block.ts | 3 + .../collections/collection.controller.ts | 5 + .../entities/collection.account.ts | 3 + src/endpoints/events/entities/events.ts | 3 + src/endpoints/events/events.service.ts | 1 + .../marketplace/nft.marketplace.controller.ts | 11 +- .../entities/mini.block.detailed.ts | 3 + src/endpoints/nfts/entities/nft.owner.ts | 3 + src/endpoints/nfts/nft.controller.ts | 3 +- src/endpoints/nfts/nft.service.ts | 3 +- src/endpoints/nfttags/entities/tag.ts | 3 + src/endpoints/nfttags/tag.controller.ts | 3 +- .../providers/entities/provider.accounts.ts | 3 + src/endpoints/providers/provider.service.ts | 1 + src/endpoints/rounds/entities/round.ts | 3 + src/endpoints/rounds/round.controller.ts | 4 +- .../entities/smart.contract.result.ts | 3 + .../tokens/entities/token.account.ts | 3 + src/endpoints/tokens/token.controller.ts | 7 +- src/endpoints/tokens/token.service.ts | 1 + .../entities/transaction.detailed.ts | 3 + .../transactions/entities/transaction.ts | 3 + src/endpoints/transfers/transfer.service.ts | 6 +- 29 files changed, 292 insertions(+), 94 deletions(-) diff --git a/src/common/indexer/elastic/elastic.indexer.service.ts b/src/common/indexer/elastic/elastic.indexer.service.ts index cc06c47ea..be9942256 100644 --- a/src/common/indexer/elastic/elastic.indexer.service.ts +++ b/src/common/indexer/elastic/elastic.indexer.service.ts @@ -74,15 +74,16 @@ export class ElasticIndexerService implements IndexerInterface { const order = filter.order === SortOrder.asc ? ElasticSortOrder.ascending : ElasticSortOrder.descending; let elasticQuery = ElasticQuery.create() - .withPagination(queryPagination) + .withPagination({ from: queryPagination.from, size: queryPagination.size }) .withCondition(QueryConditionOptions.must, await this.indexerHelper.buildElasticBlocksFilter(filter)); elasticQuery = elasticQuery.withSort([ { name: "timestamp", order: order }, + { name: "timestampMs", order: order, missing: 0 }, { name: "shardId", order: ElasticSortOrder.ascending }, ]); - const result = await this.elasticService.getList('blocks', 'hash', elasticQuery); + const result = await this.elasticService.getList('blocks', 'hash', elasticQuery, queryPagination.searchAfter); return result; } @@ -132,7 +133,14 @@ export class ElasticIndexerService implements IndexerInterface { async getTokensForAddress(address: string, queryPagination: QueryPagination, filter: TokenFilter): Promise { let query = ElasticQuery.create() .withMustCondition(QueryType.Match('address', address)) - .withPagination({ from: queryPagination.from, size: queryPagination.size }); + .withPagination({ from: queryPagination.from, size: queryPagination.size }) + .withSort([ + { name: 'timestamp', order: ElasticSortOrder.descending }, + { name: 'timestampMs', order: ElasticSortOrder.descending, missing: 0 }, + { name: 'token', order: ElasticSortOrder.descending }, + { name: 'tokenNonce', order: ElasticSortOrder.ascending, missing: 0 }, + { name: 'address', order: ElasticSortOrder.descending }, + ]); query = this.buildTokenFilter(query, filter); @@ -149,12 +157,19 @@ export class ElasticIndexerService implements IndexerInterface { async getTokenAccounts(pagination: QueryPagination, identifier: string): Promise { const elasticQuery: ElasticQuery = ElasticQuery.create() - .withPagination(pagination) - .withSort([{ name: "balanceNum", order: ElasticSortOrder.descending }]) + .withPagination({ from: pagination.from, size: pagination.size }) + .withSort([ + { name: "balanceNum", order: ElasticSortOrder.descending }, + { name: 'timestamp', order: ElasticSortOrder.descending }, + { name: 'timestampMs', order: ElasticSortOrder.descending, missing: 0 }, + { name: 'token', order: ElasticSortOrder.descending }, + { name: 'tokenNonce', order: ElasticSortOrder.ascending, missing: 0 }, + { name: 'address', order: ElasticSortOrder.descending }, + ]) .withCondition(QueryConditionOptions.must, [QueryType.Match("token", identifier, QueryOperator.AND)]) .withCondition(QueryConditionOptions.mustNot, [QueryType.Match('address', 'pending')]); - return await this.elasticService.getList("accountsesdt", identifier, elasticQuery); + return await this.elasticService.getList("accountsesdt", identifier, elasticQuery, pagination.searchAfter); } async getTokensWithRolesForAddressCount(address: string, filter: TokenWithRolesFilter): Promise { @@ -270,7 +285,11 @@ export class ElasticIndexerService implements IndexerInterface { async getBlockByMiniBlockHash(miniBlockHash: string): Promise { const elasticQuery = ElasticQuery.create() .withCondition(QueryConditionOptions.must, [QueryType.Match('miniBlocksHashes', miniBlockHash)]) - .withSort([{ name: 'timestamp', order: ElasticSortOrder.descending }]); + .withSort([ + { name: 'timestamp', order: ElasticSortOrder.descending }, + { name: 'timestampMs', order: ElasticSortOrder.descending, missing: 0 }, + { name: 'shardId', order: ElasticSortOrder.descending }, + ]) const result = await this.elasticService.getList('blocks', '_search', elasticQuery); return result.length > 0 ? result[0] : undefined; @@ -288,11 +307,13 @@ export class ElasticIndexerService implements IndexerInterface { const sortOrder: ElasticSortOrder = !filter.order || filter.order === SortOrder.desc ? ElasticSortOrder.descending : ElasticSortOrder.ascending; const timestamp: ElasticSortProperty = { name: 'timestamp', order: sortOrder }; + const timestampMs: ElasticSortProperty = { name: 'timestampMs', order: sortOrder, missing: 0 }; const nonce: ElasticSortProperty = { name: 'nonce', order: sortOrder }; + const uuid: ElasticSortProperty = { name: 'uuid.keyword', order: sortOrder }; const elasticQuery = this.indexerHelper.buildTransferFilterQuery(filter) .withPagination({ from: pagination.from, size: pagination.size }) - .withSort([timestamp, nonce]); + .withSort([timestamp, timestampMs, nonce, uuid]); const elasticOperations = await this.elasticService.getList('operations', 'txHash', elasticQuery, pagination.searchAfter); @@ -309,18 +330,23 @@ export class ElasticIndexerService implements IndexerInterface { async getRounds(filter: RoundFilter): Promise { const { from, size } = filter; + const { searchAfter } = filter; const elasticQuery = ElasticQuery.create() .withPagination({ from, size }) - .withSort([{ name: 'timestamp', order: ElasticSortOrder.descending }]) + .withSort([ + { name: 'timestamp', order: ElasticSortOrder.descending }, + { name: 'timestampMs', order: ElasticSortOrder.descending, missing: 0 }, + { name: 'shardId', order: ElasticSortOrder.descending }, + ]) .withCondition(filter.condition ?? QueryConditionOptions.must, await this.indexerHelper.buildElasticRoundsFilter(filter)); - return await this.elasticService.getList('rounds', 'round', elasticQuery); + return await this.elasticService.getList('rounds', 'round', elasticQuery, searchAfter); } async getNftCollections(pagination: QueryPagination, filter: CollectionFilter, address?: string): Promise { let elasticQuery = this.indexerHelper.buildCollectionRolesFilter(filter, address) - .withPagination(pagination); + .withPagination({ from: pagination.from, size: pagination.size }); const sort = filter.sort ?? SortCollections.timestamp; const order = filter.order === SortOrder.asc ? ElasticSortOrder.ascending : ElasticSortOrder.descending; @@ -329,14 +355,21 @@ export class ElasticIndexerService implements IndexerInterface { elasticQuery = elasticQuery.withSort([ { name: 'api_isVerified', order }, { name: 'api_holderCount', order }, + { name: 'token', order }, + { name: 'nonce', order, missing: 0 }, + { name: 'timestamp', order }, + { name: 'timestampMs', order, missing: 0 }, ]); } else { elasticQuery = elasticQuery.withSort([ { name: 'timestamp', order }, + { name: 'timestampMs', order, missing: 0 }, + { name: 'token', order }, + { name: 'nonce', order, missing: 0 }, ]); } - return await this.elasticService.getList('tokens', 'identifier', elasticQuery); + return await this.elasticService.getList('tokens', 'identifier', elasticQuery, pagination.searchAfter); } async getNftCollectionsByIds(identifiers: string[]): Promise { @@ -352,7 +385,11 @@ export class ElasticIndexerService implements IndexerInterface { const elasticQuery = ElasticQuery.create() .withMustMatchCondition('type', 'unsigned') .withPagination({ from: 0, size: transactionHashes.length + 1 }) - .withSort([{ name: 'timestamp', order: ElasticSortOrder.ascending }]) + .withSort([ + { name: 'timestamp', order: ElasticSortOrder.ascending }, + { name: 'timestampMs', order: ElasticSortOrder.ascending, missing: 0 }, + { name: 'uuid.keyword', order: ElasticSortOrder.ascending }, + ]) .withMustMultiShouldCondition(transactionHashes, hash => QueryType.Match('originalTxHash', hash)); return await this.elasticService.getList('operations', 'scHash', elasticQuery); @@ -385,7 +422,7 @@ export class ElasticIndexerService implements IndexerInterface { async getNftTags(pagination: QueryPagination, search?: string): Promise { const elasticQuery = ElasticQuery.create() - .withPagination(pagination) + .withPagination({ from: pagination.from, size: pagination.size }) .withSearchWildcardCondition(search, ['tag']) .withSort([{ name: 'count', order: ElasticSortOrder.descending }]); @@ -394,9 +431,14 @@ export class ElasticIndexerService implements IndexerInterface { async getScResults(pagination: QueryPagination, filter: SmartContractResultFilter): Promise { const elasticQuery: ElasticQuery = this.indexerHelper.buildResultsFilterQuery(filter) - .withPagination(pagination); + .withPagination({ from: pagination.from, size: pagination.size }) + .withSort([ + { name: 'timestamp', order: ElasticSortOrder.descending }, + { name: 'timestampMs', order: ElasticSortOrder.descending, missing: 0 }, + { name: 'uuid.keyword', order: ElasticSortOrder.descending }, + ]); - const results = await this.elasticService.getList('operations', 'hash', elasticQuery); + const results = await this.elasticService.getList('operations', 'hash', elasticQuery, pagination.searchAfter); this.bulkProcessTransactions(results); @@ -405,8 +447,13 @@ export class ElasticIndexerService implements IndexerInterface { async getMiniBlocks(pagination: QueryPagination, filter: MiniBlockFilter): Promise { let query = ElasticQuery.create() - .withPagination(pagination) - .withSort([{ name: 'timestamp', order: ElasticSortOrder.descending }]); + .withPagination({ from: pagination.from, size: pagination.size }) + .withSort([ + { name: 'timestamp', order: ElasticSortOrder.descending }, + { name: 'timestampMs', order: ElasticSortOrder.descending, missing: 0 }, + { name: 'senderShard', order: ElasticSortOrder.descending }, + { name: 'receiverShard', order: ElasticSortOrder.descending }, + ]); if (filter.hashes) { query = query.withShouldCondition(filter.hashes.map(hash => QueryType.Match('_id', hash))); @@ -416,15 +463,19 @@ export class ElasticIndexerService implements IndexerInterface { query = query.withCondition(QueryConditionOptions.must, [QueryType.Match("type", filter.type)]); } - return await this.elasticService.getList('miniblocks', 'miniBlockHash', query); + return await this.elasticService.getList('miniblocks', 'miniBlockHash', query, pagination.searchAfter); } async getAccountScResults(address: string, pagination: QueryPagination): Promise { const elasticQuery: ElasticQuery = this.indexerHelper.buildSmartContractResultFilterQuery(address) - .withPagination(pagination) - .withSort([{ name: 'timestamp', order: ElasticSortOrder.descending }]); + .withPagination({ from: pagination.from, size: pagination.size }) + .withSort([ + { name: 'timestamp', order: ElasticSortOrder.descending }, + { name: 'timestampMs', order: ElasticSortOrder.descending, missing: 0 }, + { name: 'uuid.keyword', order: ElasticSortOrder.descending }, + ]); - return await this.elasticService.getList('operations', 'hash', elasticQuery); + return await this.elasticService.getList('operations', 'hash', elasticQuery, pagination.searchAfter); } async getAccounts(queryPagination: QueryPagination, filter: AccountQueryOptions, fields?: string[]): Promise { @@ -434,29 +485,46 @@ export class ElasticIndexerService implements IndexerInterface { switch (sort) { case AccountSort.balance: - elasticQuery = elasticQuery.withSort([{ name: 'balanceNum', order: sortOrder }]); + elasticQuery = elasticQuery.withSort([ + { name: 'balanceNum', order: sortOrder }, + { name: 'address', order: sortOrder }, + { name: 'timestamp', order: sortOrder }, + { name: 'timestampMs', order: sortOrder, missing: 0 }, + ]); break; case AccountSort.transfersLast24h: if (this.apiConfigService.getAccountExtraDetailsTransfersLast24hUrl()) { - elasticQuery = elasticQuery.withSort([{ name: 'api_transfersLast24h', order: sortOrder }]); + elasticQuery = elasticQuery.withSort([ + { name: 'api_transfersLast24h', order: sortOrder }, + { name: 'address', order: sortOrder }, + ]); } else { elasticQuery = elasticQuery - .withSort([{ name: 'timestamp', order: sortOrder }]) + .withSort([ + { name: 'timestamp', order: sortOrder }, + { name: 'timestampMs', order: sortOrder, missing: 0 }, + { name: 'address', order: sortOrder }, + ]) .withMustExistCondition('currentOwner'); } break; default: - elasticQuery = elasticQuery.withSort([{ name: sort.toString(), order: sortOrder }]); + // case timestamp + elasticQuery = elasticQuery.withSort([ + { name: 'timestamp', order: sortOrder }, + { name: 'timestampMs', order: sortOrder, missing: 0 }, + { name: 'address', order: sortOrder }, + ]); break; } - elasticQuery = elasticQuery.withPagination(queryPagination); + elasticQuery = elasticQuery.withPagination({ from: queryPagination.from, size: queryPagination.size }); if (fields && fields.length > 0) { elasticQuery = elasticQuery.withFields(fields); } - return await this.elasticService.getList('accounts', 'address', elasticQuery); + return await this.elasticService.getList('accounts', 'address', elasticQuery, queryPagination.searchAfter); } async getAccount(address: string): Promise { @@ -469,20 +537,29 @@ export class ElasticIndexerService implements IndexerInterface { async getAccountDeploys(pagination: QueryPagination, address: string): Promise { const elasticQuery: ElasticQuery = ElasticQuery.create() - .withPagination(pagination) + .withPagination({ from: pagination.from, size: pagination.size }) .withCondition(QueryConditionOptions.must, [QueryType.Match("deployer", address)]) - .withSort([{ name: 'timestamp', order: ElasticSortOrder.descending }]); + .withSort([ + { name: 'timestamp', order: ElasticSortOrder.descending }, + { name: 'timestampMs', order: ElasticSortOrder.descending, missing: 0 }, + { name: 'deployTxHash', order: ElasticSortOrder.descending }, + ]); return await this.elasticService.getList('scdeploys', "contract", elasticQuery); } async getAccountContracts(pagination: QueryPagination, address: string): Promise { const elasticQuery: ElasticQuery = ElasticQuery.create() - .withPagination(pagination) + .withPagination({ from: pagination.from, size: pagination.size }) .withCondition(QueryConditionOptions.must, [QueryType.Match("currentOwner", address)]) - .withSort([{ name: 'timestamp', order: ElasticSortOrder.descending }]); + .withSort([ + { name: 'timestamp', order: ElasticSortOrder.descending }, + { name: 'timestampMs', order: ElasticSortOrder.descending, missing: 0 }, + { name: 'deployTxHash', order: ElasticSortOrder.descending }, + ]); - return await this.elasticService.getList('scdeploys', "contract", elasticQuery); + + return await this.elasticService.getList('scdeploys', "contract", elasticQuery, pagination.searchAfter); } async getAccountContractsCount(address: string): Promise { @@ -494,11 +571,16 @@ export class ElasticIndexerService implements IndexerInterface { async getProviderDelegators(address: string, pagination: QueryPagination): Promise { const elasticQuery: ElasticQuery = ElasticQuery.create() - .withPagination(pagination) + .withPagination({ from: pagination.from, size: pagination.size }) .withCondition(QueryConditionOptions.must, [QueryType.Match("contract", address)]) - .withSort([{ name: 'activeStake', order: ElasticSortOrder.descending }]); + .withSort([ + { name: 'activeStake', order: ElasticSortOrder.descending }, + { name: 'timestamp', order: ElasticSortOrder.descending, missing: 0 }, + { name: 'timestampMs', order: ElasticSortOrder.descending, missing: 0 }, + { name: 'address', order: ElasticSortOrder.descending }, + ]); - return await this.elasticService.getList("delegators", "contract", elasticQuery); + return await this.elasticService.getList("delegators", "contract", elasticQuery, pagination.searchAfter); } async getProviderDelegatorsCount(address: string): Promise { @@ -509,18 +591,27 @@ export class ElasticIndexerService implements IndexerInterface { } async getAccountHistory(address: string, pagination: QueryPagination, filter?: AccountHistoryFilter): Promise { const elasticQuery: ElasticQuery = this.indexerHelper.buildAccountHistoryFilterQuery(address, undefined, filter) - .withPagination(pagination) - .withSort([{ name: 'timestamp', order: ElasticSortOrder.descending }]); + .withPagination({ from: pagination.from, size: pagination.size }) + .withSort([ + { name: 'timestamp', order: ElasticSortOrder.descending }, + { name: 'timestampMs', order: ElasticSortOrder.descending, missing: 0 }, + { name: 'address', order: ElasticSortOrder.descending }, + ]); - return await this.elasticService.getList('accountshistory', 'address', elasticQuery); + return await this.elasticService.getList('accountshistory', 'address', elasticQuery, pagination.searchAfter); } async getAccountTokenHistory(address: string, tokenIdentifier: string, pagination: QueryPagination, filter: AccountHistoryFilter): Promise { const elasticQuery: ElasticQuery = this.indexerHelper.buildAccountHistoryFilterQuery(address, tokenIdentifier, filter) - .withPagination(pagination) - .withSort([{ name: 'timestamp', order: ElasticSortOrder.descending }]); + .withPagination({ from: pagination.from, size: pagination.size }) + .withSort([ + { name: 'timestamp', order: ElasticSortOrder.descending }, + { name: 'timestampMs', order: ElasticSortOrder.descending, missing: 0 }, + { name: 'address', order: ElasticSortOrder.descending }, + { name: 'token', order: ElasticSortOrder.descending } + ]); - return await this.elasticService.getList('accountsesdthistory', 'address', elasticQuery); + return await this.elasticService.getList('accountsesdthistory', 'address', elasticQuery, pagination.searchAfter); } async getAccountHistoryCount(address: string, filter?: AccountHistoryFilter): Promise { @@ -537,10 +628,15 @@ export class ElasticIndexerService implements IndexerInterface { async getAccountEsdtHistory(address: string, pagination: QueryPagination, filter: AccountHistoryFilter): Promise { const elasticQuery: ElasticQuery = this.indexerHelper.buildAccountHistoryFilterQuery(address, undefined, filter) - .withPagination(pagination) - .withSort([{ name: 'timestamp', order: ElasticSortOrder.descending }]); + .withPagination({ from: pagination.from, size: pagination.size }) + .withSort([ + { name: 'timestamp', order: ElasticSortOrder.descending }, + { name: 'timestampMs', order: ElasticSortOrder.descending, missing: 0 }, + { name: 'address', order: ElasticSortOrder.descending }, + { name: 'token', order: ElasticSortOrder.descending } + ]); - return await this.elasticService.getList('accountsesdthistory', 'address', elasticQuery); + return await this.elasticService.getList('accountsesdthistory', 'address', elasticQuery, pagination.searchAfter); } async getAccountEsdtHistoryCount(address: string, filter?: AccountHistoryFilter): Promise { @@ -553,11 +649,13 @@ export class ElasticIndexerService implements IndexerInterface { const sortOrder: ElasticSortOrder = !filter.order || filter.order === SortOrder.desc ? ElasticSortOrder.descending : ElasticSortOrder.ascending; const timestamp: ElasticSortProperty = { name: 'timestamp', order: sortOrder }; + const timestampMs: ElasticSortProperty = { name: 'timestampMs', order: sortOrder, missing: 0 }; const nonce: ElasticSortProperty = { name: 'nonce', order: sortOrder }; + const uuid: ElasticSortProperty = { name: 'uuid.keyword', order: sortOrder }; const elasticQuery = this.indexerHelper.buildTransactionFilterQuery(filter, address) .withPagination({ from: pagination.from, size: pagination.size }) - .withSort([timestamp, nonce]); + .withSort([timestamp, timestampMs, nonce, uuid]); const transactions = await this.elasticService.getList('operations', 'txHash', elasticQuery, pagination.searchAfter); @@ -631,11 +729,13 @@ export class ElasticIndexerService implements IndexerInterface { async getTransactionScResults(txHash: string): Promise { const originalTxHashQuery = QueryType.Match('originalTxHash', txHash); const timestamp: ElasticSortProperty = { name: 'timestamp', order: ElasticSortOrder.ascending }; + const timestampMs: ElasticSortProperty = { name: 'timestampMs', order: ElasticSortOrder.ascending, missing: 0 }; + const uuid: ElasticSortProperty = { name: 'uuid.keyword', order: ElasticSortOrder.ascending }; const elasticQuerySc = ElasticQuery.create() .withMustMatchCondition('type', 'unsigned') .withPagination({ from: 0, size: 100 }) - .withSort([timestamp]) + .withSort([timestamp, timestampMs, uuid]) .withCondition(QueryConditionOptions.must, [originalTxHashQuery]); const results = await this.elasticService.getList('operations', 'hash', elasticQuerySc); @@ -659,7 +759,11 @@ export class ElasticIndexerService implements IndexerInterface { const elasticQuery = ElasticQuery.create() .withMustMatchCondition('type', 'unsigned') .withPagination({ from: 0, size: maxSize }) - .withSort([{ name: 'timestamp', order: ElasticSortOrder.ascending }]) + .withSort([ + { name: 'timestamp', order: ElasticSortOrder.ascending }, + { name: 'timestampMs', order: ElasticSortOrder.ascending, missing: 0 }, + { name: 'uuid.keyword', order: ElasticSortOrder.ascending } + ]) .withMustMultiShouldCondition(hashes, hash => QueryType.Match('originalTxHash', hash)); return await this.elasticService.getList('operations', 'scHash', elasticQuery); @@ -673,18 +777,22 @@ export class ElasticIndexerService implements IndexerInterface { let elasticQuery = ElasticQuery.create(); if (pagination) { - elasticQuery = elasticQuery.withPagination(pagination); + elasticQuery = elasticQuery.withPagination({ from: pagination.from, size: pagination.size }); } elasticQuery = elasticQuery .withSort([ { name: "balanceNum", order: ElasticSortOrder.descending }, { name: 'timestamp', order: ElasticSortOrder.descending }, + { name: 'timestampMs', order: ElasticSortOrder.descending, missing: 0 }, + { name: 'token', order: ElasticSortOrder.descending }, + { name: 'tokenNonce', order: ElasticSortOrder.ascending, missing: 0 }, + { name: 'address', order: ElasticSortOrder.descending }, ]) .withCondition(QueryConditionOptions.mustNot, [QueryType.Match('address', 'pending')]) .withMustMultiShouldCondition(identifiers, identifier => QueryType.Match('identifier', identifier, QueryOperator.AND)); - return await this.elasticService.getList('accountsesdt', 'identifier', elasticQuery); + return await this.elasticService.getList('accountsesdt', 'identifier', elasticQuery, pagination?.searchAfter); } async getAccountsEsdtByCollection(identifiers: string[], pagination?: QueryPagination) { @@ -695,60 +803,75 @@ export class ElasticIndexerService implements IndexerInterface { let elasticQuery = ElasticQuery.create(); if (pagination) { - elasticQuery = elasticQuery.withPagination(pagination); + elasticQuery = elasticQuery.withPagination({ from: pagination.from, size: pagination.size }); } elasticQuery = elasticQuery .withSort([ { name: "balanceNum", order: ElasticSortOrder.descending }, { name: 'timestamp', order: ElasticSortOrder.descending }, + { name: 'timestampMs', order: ElasticSortOrder.descending, missing: 0 }, + { name: 'token', order: ElasticSortOrder.descending }, + { name: 'tokenNonce', order: ElasticSortOrder.ascending, missing: 0 }, + { name: 'address', order: ElasticSortOrder.descending }, ]) .withCondition(QueryConditionOptions.mustNot, [QueryType.Match('address', 'pending')]) .withMustMultiShouldCondition(identifiers, identifier => QueryType.Match('collection', identifier, QueryOperator.AND)); - return await this.elasticService.getList('accountsesdt', 'identifier', elasticQuery); + return await this.elasticService.getList('accountsesdt', 'identifier', elasticQuery, pagination?.searchAfter); } async getNftsForAddress(address: string, filter: NftFilter, pagination: QueryPagination): Promise { const elasticQuery = this.indexerHelper.buildElasticNftFilter(filter, undefined, address) - .withPagination(pagination) + .withPagination({ from: pagination.from, size: pagination.size }) .withSort([ { name: 'timestamp', order: ElasticSortOrder.descending }, - { name: 'tokenNonce', order: ElasticSortOrder.descending }, + { name: 'timestampMs', order: ElasticSortOrder.descending, missing: 0 }, + { name: 'token', order: ElasticSortOrder.descending }, + { name: 'tokenNonce', order: ElasticSortOrder.descending, missing: 0 }, ]); - return await this.elasticService.getList('accountsesdt', 'identifier', elasticQuery); + return await this.elasticService.getList('accountsesdt', 'identifier', elasticQuery, pagination.searchAfter); } async getNfts(pagination: QueryPagination, filter: NftFilter, identifier?: string): Promise { let elasticQuery = this.indexerHelper.buildElasticNftFilter(filter, identifier) - .withPagination(pagination); + .withPagination({ from: pagination.from, size: pagination.size }); if (filter.sort) { elasticQuery = elasticQuery.withSort([ { name: filter.sort, order: filter.order === SortOrder.desc ? ElasticSortOrder.descending : ElasticSortOrder.ascending }, ]); + if (filter.sort === 'timestamp') { + elasticQuery.sort.push({ name: 'timestampMs', order: filter.order === SortOrder.desc ? ElasticSortOrder.descending : ElasticSortOrder.ascending, missing: 0 }); + } + elasticQuery.sort.push( + { name: 'token', order: filter.order === SortOrder.desc ? ElasticSortOrder.descending : ElasticSortOrder.ascending }, + { name: 'nonce', order: filter.order === SortOrder.desc ? ElasticSortOrder.descending : ElasticSortOrder.ascending }, + ); } else { elasticQuery = elasticQuery.withSort([ { name: 'timestamp', order: ElasticSortOrder.descending }, - { name: 'nonce', order: ElasticSortOrder.descending }, + { name: 'timestampMs', order: ElasticSortOrder.descending, missing: 0 }, + { name: 'token', order: ElasticSortOrder.descending }, + { name: 'nonce', order: ElasticSortOrder.descending, missing: 0 }, ]); } if (identifier !== undefined) { const [tokensResult, accountsResult] = await Promise.all([ - this.elasticService.getList('tokens', 'identifier', elasticQuery).catch(() => []), + this.elasticService.getList('tokens', 'identifier', elasticQuery, pagination.searchAfter).catch(() => []), this.elasticService.getList('accountsesdt', 'identifier', ElasticQuery.create() .withMustMatchCondition('identifier', identifier, QueryOperator.AND) - .withPagination(pagination) + .withPagination({ from: pagination.from, size: pagination.size }) ).catch(() => []), ]); const elasticNfts = tokensResult.length > 0 ? tokensResult : accountsResult; return elasticNfts; } else { - const elasticNfts = await this.elasticService.getList('tokens', 'identifier', elasticQuery); + const elasticNfts = await this.elasticService.getList('tokens', 'identifier', elasticQuery, pagination.searchAfter); return elasticNfts; } } @@ -1066,7 +1189,7 @@ export class ElasticIndexerService implements IndexerInterface { .withCondition(QueryConditionOptions.must, [QueryType.Match('shardId', shardId, QueryOperator.AND)]) .withSort([ { name: 'timestamp', order: ElasticSortOrder.ascending }, - { name: 'timestampMs', order: ElasticSortOrder.ascending }, + { name: 'timestampMs', order: ElasticSortOrder.ascending, missing: 0 }, ]); const blocks: Block[] = await this.elasticService.getList('blocks', '_search', elasticQuery); @@ -1076,11 +1199,15 @@ export class ElasticIndexerService implements IndexerInterface { async getApplications(filter: ApplicationFilter, pagination: QueryPagination): Promise { const elasticQuery = this.indexerHelper.buildApplicationFilter(filter) - .withPagination(pagination) + .withPagination({ from: pagination.from, size: pagination.size }) .withFields(['address', 'deployer', 'currentOwner', 'initialCodeHash', 'timestamp']) - .withSort([{ name: 'timestamp', order: ElasticSortOrder.descending }]); + .withSort([ + { name: 'timestamp', order: ElasticSortOrder.descending }, + { name: 'timestampMs', order: ElasticSortOrder.descending, missing: 0 }, + { name: 'deployTxHash', order: ElasticSortOrder.descending }, + ]); - return await this.elasticService.getList('scdeploys', 'address', elasticQuery); + return await this.elasticService.getList('scdeploys', 'address', elasticQuery, pagination.searchAfter); } async getApplication(address: string): Promise { @@ -1105,10 +1232,14 @@ export class ElasticIndexerService implements IndexerInterface { async getEvents(pagination: QueryPagination, filter: EventsFilter): Promise { const elasticQuery = this.indexerHelper.buildEventsFilter(filter) - .withPagination(pagination) - .withSort([{ name: 'timestamp', order: ElasticSortOrder.descending }]); + .withPagination({ from: pagination.from, size: pagination.size }) + .withSort([ + { name: 'timestamp', order: ElasticSortOrder.descending }, + { name: 'timestampMs', order: ElasticSortOrder.descending, missing: 0 }, + { name: 'uuid.keyword', order: ElasticSortOrder.descending }, + ]); - return await this.elasticService.getList('events', '_id', elasticQuery); + return await this.elasticService.getList('events', '_id', elasticQuery, pagination.searchAfter); } async getEvent(txHash: string): Promise { @@ -1131,7 +1262,13 @@ export class ElasticIndexerService implements IndexerInterface { const elasticQuery = ElasticQuery.create() .withMustMatchCondition('address', address) .withMustMultiShouldCondition(identifiers, identifier => QueryType.Match('identifier', identifier, QueryOperator.AND)) - .withSort([{ name: 'timestamp', order: ElasticSortOrder.ascending }]) + .withSort([ + { name: 'timestamp', order: ElasticSortOrder.ascending }, + { name: 'timestampMs', order: ElasticSortOrder.ascending, missing: 0 }, + { name: 'address', order: ElasticSortOrder.ascending }, + { name: 'token', order: ElasticSortOrder.ascending }, + { name: 'tokenNonce', order: ElasticSortOrder.ascending, missing: 0 }, + ]) .withPagination({ from: 0, size: 10000 }); const history = await this.elasticService.getList('accountsesdthistory', 'address', elasticQuery); diff --git a/src/common/indexer/entities/events.ts b/src/common/indexer/entities/events.ts index 82c26262d..26e5e2f86 100644 --- a/src/common/indexer/entities/events.ts +++ b/src/common/indexer/entities/events.ts @@ -1,5 +1,4 @@ export class Events { - searchAfter?: string; _id: string = ''; logAddress: string = ''; identifier: string = ''; @@ -12,4 +11,5 @@ export class Events { order: number = 0; timestamp: number = 0; timestampMs?: number; + searchAfter?: string; } diff --git a/src/endpoints/accounts/account.controller.ts b/src/endpoints/accounts/account.controller.ts index 16e4fb715..6cf29cfb4 100644 --- a/src/endpoints/accounts/account.controller.ts +++ b/src/endpoints/accounts/account.controller.ts @@ -85,7 +85,7 @@ export class AccountController { @ApiOkResponse({ type: [Account] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) - @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiQuery({ name: 'ownerAddress', description: 'Search by owner address', required: false }) @ApiQuery({ name: 'sort', description: 'Sort criteria (balance / timestamp)', required: false, enum: AccountSort }) @ApiQuery({ name: 'order', description: 'Sort order (asc/desc)', required: false, enum: SortOrder }) @@ -275,11 +275,12 @@ export class AccountController { @ApiQuery({ name: 'timestamp', description: 'Retrieve entries from timestamp', required: false, type: Number }) @ApiQuery({ name: 'mexPairType', description: 'Token Mex Pair', required: false, enum: MexPairType }) @ApiOkResponse({ type: [TokenWithBalance] }) + // @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) async getAccountTokens( @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, - @Query('searchAfter') searchAfter?: string, + // @Query('searchAfter') searchAfter?: string, @Query('type', new ParseEnumPipe(TokenType)) type?: TokenType, @Query('subType', new ParseEnumPipe(NftSubType)) subType?: NftSubType, @Query('search') search?: string, @@ -291,7 +292,7 @@ export class AccountController { @Query('mexPairType', new ParseEnumArrayPipe(MexPairType)) mexPairType?: MexPairType[], ): Promise { try { - return await this.tokenService.getTokensForAddress(address, new QueryPagination({ from, size, searchAfter }), new TokenFilter({ type, subType, search, name, identifier, identifiers, includeMetaESDT, mexPairType })); + return await this.tokenService.getTokensForAddress(address, new QueryPagination({ from, size }), new TokenFilter({ type, subType, search, name, identifier, identifiers, includeMetaESDT, mexPairType })); } catch (error) { this.logger.error(`Error in getAccountTokens for address ${address}`); this.logger.error(error); @@ -391,6 +392,7 @@ export class AccountController { @ApiQuery({ name: 'canTransferRole', description: 'Filter by property canTransferRole (boolean)', required: false }) @ApiQuery({ name: 'excludeMetaESDT', description: 'Exclude collections of type "MetaESDT" in the response', required: false, type: Boolean }) @ApiOkResponse({ type: [NftCollectionWithRoles] }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) async getAccountCollectionsWithRoles( @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @@ -493,6 +495,7 @@ export class AccountController { @ApiQuery({ name: 'canBurn', description: 'Filter by property canBurn (boolean)', required: false }) @ApiQuery({ name: 'includeMetaESDT', description: 'Include MetaESDTs in response', required: false, type: Boolean }) @ApiOkResponse({ type: [TokenWithRoles] }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) async getAccountTokensWithRoles( @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @@ -649,6 +652,7 @@ export class AccountController { @ApiQuery({ name: 'scamType', description: 'Filter by type (scam/potentialScam)', required: false }) @ApiQuery({ name: 'timestamp', description: 'Retrieve entry from timestamp', required: false, type: Number }) @ApiQuery({ name: 'withReceivedAt', description: 'Include receivedAt timestamp in the response (when the NFT was received by the address)', required: false, type: Boolean }) + // @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) async getAccountNfts( @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @@ -672,14 +676,14 @@ export class AccountController { @Query('scamType', new ParseEnumPipe(ScamType)) scamType?: ScamType, @Query('timestamp', ParseIntPipe) _timestamp?: number, @Query('withReceivedAt', ParseBoolPipe) withReceivedAt?: boolean, - @Query('searchAfter') searchAfter?: string, + // @Query('searchAfter') searchAfter?: string, ): Promise { const queryOptions = new NftQueryOptions({ withSupply, withReceivedAt }); queryOptions.validate(size); return await this.nftService.getNftsForAddress( address, - new QueryPagination({ from, size, searchAfter }), + new QueryPagination({ from, size }), new NftFilter({ search, identifiers, @@ -876,7 +880,7 @@ export class AccountController { @ApiOkResponse({ type: [Transaction] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) - @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiQuery({ name: 'sender', description: 'Address of the transaction sender', required: false }) @ApiQuery({ name: 'receiver', description: 'Search by multiple receiver addresses, comma-separated', required: false }) @ApiQuery({ name: 'token', description: 'Identifier of the token', required: false }) @@ -1025,7 +1029,7 @@ export class AccountController { @ApplyComplexity({ target: TransactionDetailed }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) - @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiQuery({ name: 'sender', description: 'Address of the transfer sender', required: false }) @ApiQuery({ name: 'receiver', description: 'Search by multiple receiver addresses, comma-separated', required: false }) @ApiQuery({ name: 'token', description: 'Identifier of the token', required: false }) @@ -1244,6 +1248,7 @@ export class AccountController { @ApiOperation({ summary: 'Account contracts details', description: 'Returns contracts details for a given account' }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiOkResponse({ type: [DeployedContract] }) getAccountContracts( @Param('address', ParseAddressPipe) address: string, @@ -1284,6 +1289,7 @@ export class AccountController { @ApiOperation({ summary: 'Account smart contract results', description: 'Returns smart contract results where the account is sender or receiver' }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiOkResponse({ type: [SmartContractResult] }) getAccountScResults( @Param('address', ParseAddressPipe) address: string, @@ -1325,6 +1331,7 @@ export class AccountController { @ApiQuery({ name: 'before', description: 'Before timestamp or timestampMs', required: false }) @ApiQuery({ name: 'after', description: 'After timestamp or timestampMs', required: false }) @ApiOkResponse({ type: [AccountHistory] }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) getAccountHistory( @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @@ -1384,6 +1391,7 @@ export class AccountController { @ApiQuery({ name: 'identifier', description: 'Filter by multiple esdt identifiers, comma-separated', required: false }) @ApiQuery({ name: 'token', description: 'Token identifier', required: false }) @ApiOkResponse({ type: [AccountEsdtHistory] }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) async getAccountEsdtHistory( @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @@ -1425,6 +1433,7 @@ export class AccountController { @ApiQuery({ name: 'before', description: 'Before timestamp or timestampMs', required: false }) @ApiQuery({ name: 'after', description: 'After timestamp or timestampMs', required: false }) @ApiOkResponse({ type: [AccountEsdtHistory] }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) async getAccountTokenHistory( @Param('address', ParseAddressPipe) address: string, @Param('tokenIdentifier', ParseTokenOrNftPipe) tokenIdentifier: string, diff --git a/src/endpoints/accounts/entities/account.ts b/src/endpoints/accounts/entities/account.ts index 14df44e8a..04e7ed8d6 100644 --- a/src/endpoints/accounts/entities/account.ts +++ b/src/endpoints/accounts/entities/account.ts @@ -51,4 +51,7 @@ export class Account { @ApiProperty({ type: Number, description: 'Transfers in the last 24 hours', required: false }) transfersLast24h: number | undefined = undefined; + + @ApiProperty({ type: String, description: 'Base64 encoded cursor to continue from the last document', required: false }) + searchAfter?: string; } diff --git a/src/endpoints/applications/application.service.ts b/src/endpoints/applications/application.service.ts index 49a6ff96a..670b4014d 100644 --- a/src/endpoints/applications/application.service.ts +++ b/src/endpoints/applications/application.service.ts @@ -54,6 +54,7 @@ export class ApplicationService { timestamp: item.timestamp, assets: assets[item.address], balance: '0', + searchAfter: item.searchAfter, ...(filter.withTxCount && { txCount: 0 }), })); diff --git a/src/endpoints/applications/entities/application.ts b/src/endpoints/applications/entities/application.ts index 88ef36da9..fbb03139c 100644 --- a/src/endpoints/applications/entities/application.ts +++ b/src/endpoints/applications/entities/application.ts @@ -29,4 +29,7 @@ export class Application { @ApiProperty({ type: Number, required: false }) txCount?: number; + + @ApiProperty({ type: String, nullable: true, required: false }) + searchAfter?: string; } diff --git a/src/endpoints/blocks/entities/block.ts b/src/endpoints/blocks/entities/block.ts index 2d5137726..8c8816acc 100644 --- a/src/endpoints/blocks/entities/block.ts +++ b/src/endpoints/blocks/entities/block.ts @@ -83,6 +83,9 @@ export class Block { @ApiProperty({ type: BlockProofDto, nullable: true, required: false }) proof: BlockProofDto | undefined = undefined; + @ApiProperty({ type: String, nullable: true, required: false }) + searchAfter?: string; + static mergeWithElasticResponse(newBlock: T, blockRaw: any): T { blockRaw.shard = blockRaw.shardId; diff --git a/src/endpoints/collections/collection.controller.ts b/src/endpoints/collections/collection.controller.ts index af7166e43..9d4f3fdf9 100644 --- a/src/endpoints/collections/collection.controller.ts +++ b/src/endpoints/collections/collection.controller.ts @@ -59,6 +59,7 @@ export class CollectionController { @ApiQuery({ name: 'excludeMetaESDT', description: 'Do not include collections of type "MetaESDT" in the response', required: false }) @ApiQuery({ name: 'sort', description: 'Sorting criteria', required: false, enum: SortCollections }) @ApiQuery({ name: 'order', description: 'Sorting order (asc / desc)', required: false, enum: SortOrder }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) async getNftCollections( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, @@ -230,6 +231,7 @@ export class CollectionController { @ApiQuery({ name: 'withAssets', description: 'Return assets information (defaults to true)', required: false, type: Boolean }) @ApiQuery({ name: 'sort', description: 'Sorting criteria', required: false, enum: SortCollectionNfts }) @ApiQuery({ name: 'order', description: 'Sorting order (asc / desc)', required: false, enum: SortOrder }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) async getNfts( @Param('collection', ParseCollectionPipe) collection: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @@ -305,6 +307,7 @@ export class CollectionController { @ApiNotFoundResponse({ description: 'Collection not found' }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) async getNftAccounts( @Param('identifier', ParseCollectionPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @@ -344,6 +347,7 @@ export class CollectionController { @ApiQuery({ name: 'withScamInfo', description: 'Returns scam information', required: false, type: Boolean }) @ApiQuery({ name: 'withUsername', description: 'Integrates username in assets for all addresses present in the transactions', required: false, type: Boolean }) @ApiQuery({ name: 'withRelayedScresults', description: 'If set to true, will include smart contract results that resemble relayed transactions', required: false, type: Boolean }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) async getCollectionTransactions( @Param('collection', ParseCollectionPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @@ -470,6 +474,7 @@ export class CollectionController { @ApiQuery({ name: 'withLogs', description: 'Return logs for transactions', required: false, type: Boolean }) @ApiQuery({ name: 'withScamInfo', description: 'Returns scam information', required: false, type: Boolean }) @ApiQuery({ name: 'withUsername', description: 'Integrates username in assets for all addresses present in the transactions', required: false, type: Boolean }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) async getCollectionTransfers( @Param('collection', ParseCollectionPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, diff --git a/src/endpoints/collections/entities/collection.account.ts b/src/endpoints/collections/entities/collection.account.ts index 0b02cb4a6..8c3478ff9 100644 --- a/src/endpoints/collections/entities/collection.account.ts +++ b/src/endpoints/collections/entities/collection.account.ts @@ -10,4 +10,7 @@ export class CollectionAccount { @ApiProperty({ type: String, default: '1' }) balance: string = ''; + + @ApiProperty({ type: String, required: false }) + searchAfter: string | undefined = undefined; } diff --git a/src/endpoints/events/entities/events.ts b/src/endpoints/events/entities/events.ts index 1bc7720f5..d2f4bb6f6 100644 --- a/src/endpoints/events/entities/events.ts +++ b/src/endpoints/events/entities/events.ts @@ -42,4 +42,7 @@ export class Events { @ApiProperty({ description: "Event timestamp in milliseconds.", nullable: true, required: false }) timestampMs?: number; + + @ApiProperty({ description: "Base64 encoded cursor to continue from the last document.", nullable: true, required: false }) + searchAfter?: string; } diff --git a/src/endpoints/events/events.service.ts b/src/endpoints/events/events.service.ts index e8c265600..5b5f60a42 100644 --- a/src/endpoints/events/events.service.ts +++ b/src/endpoints/events/events.service.ts @@ -40,6 +40,7 @@ export class EventsService { txOrder: eventData.txOrder, order: eventData.order, timestamp: eventData.timestamp, + searchAfter: eventData.searchAfter, }); } } diff --git a/src/endpoints/marketplace/nft.marketplace.controller.ts b/src/endpoints/marketplace/nft.marketplace.controller.ts index b529f55ce..69fdd3c0e 100644 --- a/src/endpoints/marketplace/nft.marketplace.controller.ts +++ b/src/endpoints/marketplace/nft.marketplace.controller.ts @@ -20,13 +20,11 @@ export class NftMarketplaceController { @ApiOperation({ summary: 'Explore auctions', description: 'Returns auctions available in marketplaces ' }) @ApiOkResponse({ type: [Auctions] }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) - @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) async getAuctions( @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, - @Query('searchAfter') searchAfter?: string, ): Promise { return await this.nftMarketplaceService.getAuctions( - new QueryPagination({ size, searchAfter }), + new QueryPagination({ size }), ); } @@ -86,10 +84,9 @@ export class NftMarketplaceController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, - @Query('searchAfter') searchAfter?: string, @Query('status', new ParseEnumPipe(AuctionStatus)) status?: AuctionStatus, ): Promise { - const account = await this.nftMarketplaceService.getAccountAuctions(new QueryPagination({ from, size, searchAfter }), address, status); + const account = await this.nftMarketplaceService.getAccountAuctions(new QueryPagination({ from, size }), address, status); if (!account) { throw new NotFoundException('Account not found'); } @@ -120,14 +117,12 @@ export class NftMarketplaceController { @ApiOperation({ summary: 'Collection auctions', description: 'Returns all auctions for a specific collection ' }) @ApiOkResponse({ type: [Auctions] }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) - @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) @ApiQuery({ name: 'collection', description: 'Collection identifier', required: true }) async getCollectionAuctions( @Param('collection', ParseCollectionPipe) collection: string, @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, - @Query('searchAfter') searchAfter?: string, ): Promise { - return await this.nftMarketplaceService.getCollectionAuctions(new QueryPagination({ size, searchAfter }), collection); + return await this.nftMarketplaceService.getCollectionAuctions(new QueryPagination({ size }), collection); } @Get('/collections/:collection/auctions/count') diff --git a/src/endpoints/miniblocks/entities/mini.block.detailed.ts b/src/endpoints/miniblocks/entities/mini.block.detailed.ts index fc30c38d5..e691818ae 100644 --- a/src/endpoints/miniblocks/entities/mini.block.detailed.ts +++ b/src/endpoints/miniblocks/entities/mini.block.detailed.ts @@ -25,4 +25,7 @@ export class MiniBlockDetailed { @ApiProperty({ type: String, example: 'TxBlock' }) type: string = ''; + + @ApiProperty({ type: String, required: false }) + searchAfter?: string; } diff --git a/src/endpoints/nfts/entities/nft.owner.ts b/src/endpoints/nfts/entities/nft.owner.ts index bd262a605..622f3a835 100644 --- a/src/endpoints/nfts/entities/nft.owner.ts +++ b/src/endpoints/nfts/entities/nft.owner.ts @@ -10,4 +10,7 @@ export class NftOwner { @ApiProperty({ type: String, default: '1' }) balance: string = ''; + + @ApiProperty({ type: String, required: false }) + searchAfter?: string; } diff --git a/src/endpoints/nfts/nft.controller.ts b/src/endpoints/nfts/nft.controller.ts index 374f631cd..a8d8a2e11 100644 --- a/src/endpoints/nfts/nft.controller.ts +++ b/src/endpoints/nfts/nft.controller.ts @@ -60,7 +60,6 @@ export class NftController { async getNfts( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, - @Query('searchAfter') searchAfter?: string, @Query('search') search?: string, @Query('identifiers', ParseNftArrayPipe) identifiers?: string[], @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], @@ -82,7 +81,7 @@ export class NftController { @Query('withSupply', ParseBoolPipe) withSupply?: boolean, ): Promise { return await this.nftService.getNfts( - new QueryPagination({ from, size, searchAfter }), + new QueryPagination({ from, size }), new NftFilter({ search, identifiers, diff --git a/src/endpoints/nfts/nft.service.ts b/src/endpoints/nfts/nft.service.ts index 9d5b7fc6a..fa592df87 100644 --- a/src/endpoints/nfts/nft.service.ts +++ b/src/endpoints/nfts/nft.service.ts @@ -355,7 +355,7 @@ export class NftService { let attributes = nft.attributes; if (!attributes || attributes.length === 0) { - const nftsForAddress = await this.esdtAddressService.getNftsForAddress(nft.owner, new NftFilter({identifiers: [nft.identifier]}), new QueryPagination({ + const nftsForAddress = await this.esdtAddressService.getNftsForAddress(nft.owner, new NftFilter({ identifiers: [nft.identifier] }), new QueryPagination({ from: 0, size: 1, })); @@ -412,6 +412,7 @@ export class NftService { return accountsEsdt.map((esdt: any) => new CollectionAccount({ address: esdt.address, balance: esdt.balance, + searchAfter: esdt.searchAfter, })); } diff --git a/src/endpoints/nfttags/entities/tag.ts b/src/endpoints/nfttags/entities/tag.ts index b2732bb11..bf0ff0b23 100644 --- a/src/endpoints/nfttags/entities/tag.ts +++ b/src/endpoints/nfttags/entities/tag.ts @@ -10,4 +10,7 @@ export class Tag { @ApiProperty({ type: Number, nullable: true, example: 46135 }) count: number | undefined = undefined; + + @ApiProperty({ type: String, required: false }) + searchAfter?: string; } diff --git a/src/endpoints/nfttags/tag.controller.ts b/src/endpoints/nfttags/tag.controller.ts index 1cf07961d..6cd685d44 100644 --- a/src/endpoints/nfttags/tag.controller.ts +++ b/src/endpoints/nfttags/tag.controller.ts @@ -23,9 +23,8 @@ export class TagController { @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, @Query('search') search: string, - @Query('searchAfter') searchAfter?: string, ): Promise { - return await this.nftTagsService.getNftTags(new QueryPagination({ from, size, searchAfter }), search); + return await this.nftTagsService.getNftTags(new QueryPagination({ from, size }), search); } @Get("/tags/count") diff --git a/src/endpoints/providers/entities/provider.accounts.ts b/src/endpoints/providers/entities/provider.accounts.ts index 3ddfe5d5c..b0daeba80 100644 --- a/src/endpoints/providers/entities/provider.accounts.ts +++ b/src/endpoints/providers/entities/provider.accounts.ts @@ -10,4 +10,7 @@ export class ProviderAccounts { @ApiProperty({ type: String, nullable: true, example: '9999109666430000000' }) stake: string = ''; + + @ApiProperty({ type: String, nullable: true }) + searchAfter: string = ''; } diff --git a/src/endpoints/providers/provider.service.ts b/src/endpoints/providers/provider.service.ts index 0cd9d3396..ac3fad32c 100644 --- a/src/endpoints/providers/provider.service.ts +++ b/src/endpoints/providers/provider.service.ts @@ -497,6 +497,7 @@ export class ProviderService { return elasticResults.map(account => ApiUtils.mergeObjects(new ProviderAccounts(), { address: account.address, stake: account.activeStake, + searchAfter: account.searchAfter, })); } diff --git a/src/endpoints/rounds/entities/round.ts b/src/endpoints/rounds/entities/round.ts index 3183bc39d..99e80661b 100644 --- a/src/endpoints/rounds/entities/round.ts +++ b/src/endpoints/rounds/entities/round.ts @@ -23,4 +23,7 @@ export class Round { // only available for rounds after Barnard protocol upgrade @ApiProperty({ type: Number, example: 1651148112000, required: false }) timestampMs?: number; + + @ApiProperty({ type: String }) + searchAfter?: string = ''; } diff --git a/src/endpoints/rounds/round.controller.ts b/src/endpoints/rounds/round.controller.ts index 9fefac936..26e81c072 100644 --- a/src/endpoints/rounds/round.controller.ts +++ b/src/endpoints/rounds/round.controller.ts @@ -21,6 +21,7 @@ export class RoundController { @ApiQuery({ name: 'condition', description: 'Filter by condition', required: false }) @ApiQuery({ name: 'shard', description: 'Filter by shard identifier', required: false }) @ApiQuery({ name: 'epoch', description: 'Filter by epoch number', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) getRounds( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, @@ -28,8 +29,9 @@ export class RoundController { @Query('condition', new ParseEnumPipe(QueryConditionOptions)) condition?: QueryConditionOptions, @Query("shard", ParseIntPipe) shard?: number, @Query("epoch", ParseIntPipe) epoch?: number, + @Query('searchAfter') searchAfter?: string, ): Promise { - return this.roundService.getRounds(new RoundFilter({ from, size, condition, validator, shard, epoch })); + return this.roundService.getRounds(new RoundFilter({ from, size, condition, validator, shard, epoch, searchAfter })); } @Get("/rounds/count") diff --git a/src/endpoints/sc-results/entities/smart.contract.result.ts b/src/endpoints/sc-results/entities/smart.contract.result.ts index dbaa06ac0..1bb22920d 100644 --- a/src/endpoints/sc-results/entities/smart.contract.result.ts +++ b/src/endpoints/sc-results/entities/smart.contract.result.ts @@ -71,4 +71,7 @@ export class SmartContractResult { @ApiProperty({ type: String, nullable: true }) status: string | undefined = undefined; + + @ApiProperty({ type: String, nullable: true }) + searchAfter?: string = undefined; } diff --git a/src/endpoints/tokens/entities/token.account.ts b/src/endpoints/tokens/entities/token.account.ts index 8ce3b2837..9e1f422a8 100644 --- a/src/endpoints/tokens/entities/token.account.ts +++ b/src/endpoints/tokens/entities/token.account.ts @@ -21,4 +21,7 @@ export class TokenAccount { @ApiProperty({ type: AccountAssets, nullable: true, description: 'Account assets' }) assets: AccountAssets | undefined = undefined; + + @ApiProperty({ type: String, nullable: true }) + searchAfter?: string | undefined = undefined; } diff --git a/src/endpoints/tokens/token.controller.ts b/src/endpoints/tokens/token.controller.ts index 7ca2e1848..bde39a62d 100644 --- a/src/endpoints/tokens/token.controller.ts +++ b/src/endpoints/tokens/token.controller.ts @@ -40,7 +40,7 @@ export class TokenController { @ApiOkResponse({ type: [TokenDetailed] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) - @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) + // @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiQuery({ name: 'type', description: 'Token type', required: false, enum: TokenType }) @ApiQuery({ name: 'search', description: 'Search by collection identifier', required: false }) @ApiQuery({ name: 'name', description: 'Search by token name', required: false }) @@ -54,7 +54,7 @@ export class TokenController { async getTokens( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, - @Query('searchAfter') searchAfter?: string, + // @Query('searchAfter') searchAfter?: string, @Query('type', new ParseEnumPipe(TokenType)) type?: TokenType, @Query('search') search?: string, @Query('name') name?: string, @@ -68,7 +68,7 @@ export class TokenController { ): Promise { return await this.tokenService.getTokens( - new QueryPagination({ from, size, searchAfter }), + new QueryPagination({ from, size }), new TokenFilter({ type, search, name, identifier, identifiers, includeMetaESDT, sort, order, mexPairType, priceSource }) ); } @@ -158,6 +158,7 @@ export class TokenController { @ApiNotFoundResponse({ description: 'Token not found' }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) async getTokenAccounts( @Param('identifier', ParseTokenPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, diff --git a/src/endpoints/tokens/token.service.ts b/src/endpoints/tokens/token.service.ts index e4672b6d1..542055e52 100644 --- a/src/endpoints/tokens/token.service.ts +++ b/src/endpoints/tokens/token.service.ts @@ -473,6 +473,7 @@ export class TokenService { assets: assets[tokenAccount.address], attributes: tokenAccount.data?.attributes, identifier: tokenAccount.type === TokenType.MetaESDT ? tokenAccount.identifier : undefined, + searchAfter: tokenAccount.searchAfter, })); } diff --git a/src/endpoints/transactions/entities/transaction.detailed.ts b/src/endpoints/transactions/entities/transaction.detailed.ts index a98e28fa3..da785d2f5 100644 --- a/src/endpoints/transactions/entities/transaction.detailed.ts +++ b/src/endpoints/transactions/entities/transaction.detailed.ts @@ -50,4 +50,7 @@ export class TransactionDetailed extends Transaction { @ApiProperty({ type: String, nullable: true }) relayedVersion: string | undefined = undefined; + + @ApiProperty({ type: String, nullable: true }) + searchAfter: string | undefined = undefined; } diff --git a/src/endpoints/transactions/entities/transaction.ts b/src/endpoints/transactions/entities/transaction.ts index 5e72409f3..35fa8066a 100644 --- a/src/endpoints/transactions/entities/transaction.ts +++ b/src/endpoints/transactions/entities/transaction.ts @@ -119,6 +119,9 @@ export class Transaction { @ApiProperty({ type: Boolean, nullable: true, required: false }) isScCall: boolean | undefined = undefined; + @ApiProperty({ type: String, nullable: true, required: false }) + searchAfter?: string | undefined = undefined; + getDate(): Date | undefined { if (this.timestamp) { return new Date(this.timestamp * 1000); diff --git a/src/endpoints/transfers/transfer.service.ts b/src/endpoints/transfers/transfer.service.ts index f827849e8..313f2e8c4 100644 --- a/src/endpoints/transfers/transfer.service.ts +++ b/src/endpoints/transfers/transfer.service.ts @@ -1,4 +1,4 @@ -import { forwardRef, Inject, Injectable } from "@nestjs/common"; +import { BadRequestException, forwardRef, Inject, Injectable } from "@nestjs/common"; import { QueryPagination } from "src/common/entities/query.pagination"; import { TransactionFilter } from "../transactions/entities/transaction.filter"; import { TransactionType } from "../transactions/entities/transaction.type"; @@ -115,6 +115,10 @@ export class TransferService { let elasticOperations = await this.indexerService.getTransfers(filter, pagination); if (queryOptions.withTxsOrder && filter.miniBlockHash) { + if (pagination.searchAfter) { + throw new BadRequestException('searchAfter pagination is not supported when withTxsOrder and miniBlockHash filters are used'); + } + elasticOperations = await this.sortElasticTransfersByTxsOrder(elasticOperations, filter.miniBlockHash); } else { elasticOperations = this.sortElasticTransfers(elasticOperations); From f03b385a58154cbb55513552888e60e449363915 Mon Sep 17 00:00:00 2001 From: GuticaStefan Date: Wed, 10 Jun 2026 14:32:39 +0300 Subject: [PATCH 04/15] fix unit test --- src/common/indexer/entities/round.ts | 1 + src/endpoints/rounds/entities/round.ts | 2 +- src/test/unit/utils/circuit.breaker.proxy.spec.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/common/indexer/entities/round.ts b/src/common/indexer/entities/round.ts index 5a248665f..2b13fd03f 100644 --- a/src/common/indexer/entities/round.ts +++ b/src/common/indexer/entities/round.ts @@ -8,4 +8,5 @@ export interface Round extends ElasticSortable { epoch: number, timestamp: number, timestampMs?: number, + searchAfter?: string, } diff --git a/src/endpoints/rounds/entities/round.ts b/src/endpoints/rounds/entities/round.ts index 99e80661b..ca3110d7e 100644 --- a/src/endpoints/rounds/entities/round.ts +++ b/src/endpoints/rounds/entities/round.ts @@ -25,5 +25,5 @@ export class Round { timestampMs?: number; @ApiProperty({ type: String }) - searchAfter?: string = ''; + searchAfter?: string; } diff --git a/src/test/unit/utils/circuit.breaker.proxy.spec.ts b/src/test/unit/utils/circuit.breaker.proxy.spec.ts index e92d88f1d..2c45fad86 100644 --- a/src/test/unit/utils/circuit.breaker.proxy.spec.ts +++ b/src/test/unit/utils/circuit.breaker.proxy.spec.ts @@ -154,7 +154,7 @@ describe('EsCircuitBreakerProxy', () => { const result = await proxy.getList('test', 'id', query); expect(result).toEqual(mockData); - expect(mockElasticService.getList).toHaveBeenCalledWith('test', 'id', query); + expect(mockElasticService.getList).toHaveBeenCalledWith('test', 'id', query, undefined, undefined); }); it('should proxy getItem calls', async () => { From f74a96407bf348fcddc45fdd35a9572c8ed82669 Mon Sep 17 00:00:00 2001 From: GuticaStefan Date: Wed, 10 Jun 2026 14:37:05 +0300 Subject: [PATCH 05/15] fix lint --- src/common/indexer/elastic/elastic.indexer.service.ts | 2 +- src/common/indexer/entities/elastic.sortable.ts | 2 +- src/endpoints/blocks/block.service.ts | 1 - src/endpoints/tokens/token.service.ts | 1 - src/endpoints/transactions/transaction.get.service.ts | 1 - src/test/unit/services/network.spec.ts | 4 ++-- 6 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/common/indexer/elastic/elastic.indexer.service.ts b/src/common/indexer/elastic/elastic.indexer.service.ts index be9942256..befaff8b7 100644 --- a/src/common/indexer/elastic/elastic.indexer.service.ts +++ b/src/common/indexer/elastic/elastic.indexer.service.ts @@ -289,7 +289,7 @@ export class ElasticIndexerService implements IndexerInterface { { name: 'timestamp', order: ElasticSortOrder.descending }, { name: 'timestampMs', order: ElasticSortOrder.descending, missing: 0 }, { name: 'shardId', order: ElasticSortOrder.descending }, - ]) + ]); const result = await this.elasticService.getList('blocks', '_search', elasticQuery); return result.length > 0 ? result[0] : undefined; diff --git a/src/common/indexer/entities/elastic.sortable.ts b/src/common/indexer/entities/elastic.sortable.ts index f5847fb47..99d2ed65c 100644 --- a/src/common/indexer/entities/elastic.sortable.ts +++ b/src/common/indexer/entities/elastic.sortable.ts @@ -1,3 +1,3 @@ export interface ElasticSortable { searchAfter?: string; -} \ No newline at end of file +} diff --git a/src/endpoints/blocks/block.service.ts b/src/endpoints/blocks/block.service.ts index cc3214f90..8d533158b 100644 --- a/src/endpoints/blocks/block.service.ts +++ b/src/endpoints/blocks/block.service.ts @@ -112,7 +112,6 @@ export class BlockService { const nodeIdentities = await ConcurrencyUtils.executeWithConcurrencyLimit( relevantNodes, async (node) => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const identity = await this.identitiesService.getIdentity(node.identity!); return { node, identity }; }, diff --git a/src/endpoints/tokens/token.service.ts b/src/endpoints/tokens/token.service.ts index 542055e52..1ad7448e6 100644 --- a/src/endpoints/tokens/token.service.ts +++ b/src/endpoints/tokens/token.service.ts @@ -372,7 +372,6 @@ export class TokenService { tokenWithBalance = new TokenDetailedWithBalance({ ...token, ...esdtData }); } - // eslint-disable-next-line require-await const esdt = await this.gatewayService.getAddressEsdt(address, identifier); if (!esdt || esdt.balance === '0') { diff --git a/src/endpoints/transactions/transaction.get.service.ts b/src/endpoints/transactions/transaction.get.service.ts index 5a3b0dff8..02b60b0a4 100644 --- a/src/endpoints/transactions/transaction.get.service.ts +++ b/src/endpoints/transactions/transaction.get.service.ts @@ -277,7 +277,6 @@ export class TransactionGetService { async tryGetTransactionFromGateway(txHash: string, queryInElastic: boolean = true): Promise { try { - // eslint-disable-next-line require-await const transactionResult = await this.gatewayService.getTransaction(txHash); if (!transactionResult) { diff --git a/src/test/unit/services/network.spec.ts b/src/test/unit/services/network.spec.ts index 85401fe85..588eb482f 100644 --- a/src/test/unit/services/network.spec.ts +++ b/src/test/unit/services/network.spec.ts @@ -277,11 +277,11 @@ describe('NetworkService', () => { }); jest.spyOn(ApiConfigService.prototype, 'getNetwork') - // eslint-disable-next-line require-await + .mockImplementation(jest.fn(() => ('mainnet'))); jest.spyOn(ApiConfigService.prototype, 'getCluster') - // eslint-disable-next-line require-await + .mockImplementation(jest.fn(() => undefined)); const result = await networkService.getAboutRaw(); From 3d156683ccd325b963a41218842b3fae6bc8f8e4 Mon Sep 17 00:00:00 2001 From: GuticaStefan Date: Wed, 10 Jun 2026 14:38:27 +0300 Subject: [PATCH 06/15] remove comments --- .../elastic/circuit-breaker/circuit.breaker.proxy.service.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/common/indexer/elastic/circuit-breaker/circuit.breaker.proxy.service.ts b/src/common/indexer/elastic/circuit-breaker/circuit.breaker.proxy.service.ts index ea79a21bd..c744fcb94 100644 --- a/src/common/indexer/elastic/circuit-breaker/circuit.breaker.proxy.service.ts +++ b/src/common/indexer/elastic/circuit-breaker/circuit.breaker.proxy.service.ts @@ -69,8 +69,6 @@ export class EsCircuitBreakerProxy { // eslint-disable-next-line require-await async getList(index: string, id: string, query: ElasticQuery, searchAfter?: string): Promise { - //TODO: update package - // @ts-ignore return this.withCircuitBreaker(() => this.elasticService.getList(index, id, query, undefined, searchAfter)); } From 1b02db07b7136b9f34534637993a3222d6f231ab Mon Sep 17 00:00:00 2001 From: GuticaStefan Date: Wed, 17 Jun 2026 15:23:19 +0300 Subject: [PATCH 07/15] fixes after review --- src/common/entities/query.pagination.ts | 6 ++++++ src/common/indexer/elastic/elastic.indexer.service.ts | 2 +- src/endpoints/marketplace/nft.marketplace.controller.ts | 1 - src/endpoints/nfts/nft.controller.ts | 3 +++ src/endpoints/nfttags/tag.controller.ts | 5 ++--- src/endpoints/providers/entities/provider.accounts.ts | 2 +- src/endpoints/rounds/entities/round.ts | 2 +- src/endpoints/tokens/entities/token.account.ts | 2 +- src/endpoints/transactions/entities/transaction.ts | 2 +- src/endpoints/transactions/transaction.service.ts | 8 ++++---- src/endpoints/transfers/transfer.service.ts | 6 ++++-- src/test/unit/services/network.spec.ts | 2 -- 12 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/common/entities/query.pagination.ts b/src/common/entities/query.pagination.ts index 047764ad8..a5f73c051 100644 --- a/src/common/entities/query.pagination.ts +++ b/src/common/entities/query.pagination.ts @@ -1,6 +1,12 @@ +import { BadRequestException } from "@nestjs/common"; + export class QueryPagination { constructor(init?: Partial) { Object.assign(this, init); + + if (this.searchAfter !== undefined && this.from !== 0) { + throw new BadRequestException("'from' must be 0 when 'searchAfter' is provided."); + } } from: number = 0; diff --git a/src/common/indexer/elastic/elastic.indexer.service.ts b/src/common/indexer/elastic/elastic.indexer.service.ts index befaff8b7..03fb64c00 100644 --- a/src/common/indexer/elastic/elastic.indexer.service.ts +++ b/src/common/indexer/elastic/elastic.indexer.service.ts @@ -545,7 +545,7 @@ export class ElasticIndexerService implements IndexerInterface { { name: 'deployTxHash', order: ElasticSortOrder.descending }, ]); - return await this.elasticService.getList('scdeploys', "contract", elasticQuery); + return await this.elasticService.getList('scdeploys', "contract", elasticQuery, pagination.searchAfter); } async getAccountContracts(pagination: QueryPagination, address: string): Promise { diff --git a/src/endpoints/marketplace/nft.marketplace.controller.ts b/src/endpoints/marketplace/nft.marketplace.controller.ts index 69fdd3c0e..8a07a7374 100644 --- a/src/endpoints/marketplace/nft.marketplace.controller.ts +++ b/src/endpoints/marketplace/nft.marketplace.controller.ts @@ -77,7 +77,6 @@ export class NftMarketplaceController { @ApiOperation({ summary: 'Account auctions', description: 'Returns account auctions for a given address' }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) - @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) @ApiQuery({ name: 'status', description: 'Returns auctions with specified status', required: false }) @ApiOkResponse({ type: Auction }) async getAccountAuctions( diff --git a/src/endpoints/nfts/nft.controller.ts b/src/endpoints/nfts/nft.controller.ts index a8d8a2e11..fb6dac59e 100644 --- a/src/endpoints/nfts/nft.controller.ts +++ b/src/endpoints/nfts/nft.controller.ts @@ -249,6 +249,7 @@ export class NftController { @ApiNotFoundResponse({ description: 'Token not found' }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) async getNftAccounts( @Param('identifier', ParseNftPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @@ -300,6 +301,7 @@ export class NftController { @ApiQuery({ name: 'withScamInfo', description: 'Returns scam information', required: false, type: Boolean }) @ApiQuery({ name: 'withUsername', description: 'Integrates username in assets for all addresses present in the transactions', required: false, type: Boolean }) @ApiQuery({ name: 'withRelayedScresults', description: 'If set to true, will include smart contract results that resemble relayed transactions', required: false, type: Boolean }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) async getNftTransactions( @Param('identifier', ParseNftPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @@ -412,6 +414,7 @@ export class NftController { @ApiQuery({ name: 'withLogs', description: 'Return logs for transfers', required: false, type: Boolean }) @ApiQuery({ name: 'withScamInfo', description: 'Returns scam information', required: false, type: Boolean }) @ApiQuery({ name: 'withUsername', description: 'Integrates username in assets for all addresses present in the transfers', required: false, type: Boolean }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) async getNftTransfers( @Param('identifier', ParseNftPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, diff --git a/src/endpoints/nfttags/tag.controller.ts b/src/endpoints/nfttags/tag.controller.ts index 6cd685d44..62a91db38 100644 --- a/src/endpoints/nfttags/tag.controller.ts +++ b/src/endpoints/nfttags/tag.controller.ts @@ -17,12 +17,11 @@ export class TagController { @ApiOkResponse({ type: [Tag] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) - @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiQuery({ name: 'search', description: 'Search by tag name', required: false }) async getTags( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, - @Query('search') search: string, + @Query('search') search: string | undefined, ): Promise { return await this.nftTagsService.getNftTags(new QueryPagination({ from, size }), search); } @@ -32,7 +31,7 @@ export class TagController { @ApiQuery({ name: 'search', description: 'Search by tag name', required: false }) @ApiOkResponse({ type: Number }) async getTagCount( - @Query('search') search: string, + @Query('search') search: string | undefined, ): Promise { return await this.nftTagsService.getNftTagCount(search); } diff --git a/src/endpoints/providers/entities/provider.accounts.ts b/src/endpoints/providers/entities/provider.accounts.ts index b0daeba80..008011af6 100644 --- a/src/endpoints/providers/entities/provider.accounts.ts +++ b/src/endpoints/providers/entities/provider.accounts.ts @@ -12,5 +12,5 @@ export class ProviderAccounts { stake: string = ''; @ApiProperty({ type: String, nullable: true }) - searchAfter: string = ''; + searchAfter?: string; } diff --git a/src/endpoints/rounds/entities/round.ts b/src/endpoints/rounds/entities/round.ts index ca3110d7e..a2b1d310b 100644 --- a/src/endpoints/rounds/entities/round.ts +++ b/src/endpoints/rounds/entities/round.ts @@ -24,6 +24,6 @@ export class Round { @ApiProperty({ type: Number, example: 1651148112000, required: false }) timestampMs?: number; - @ApiProperty({ type: String }) + @ApiProperty({ type: String, required: false }) searchAfter?: string; } diff --git a/src/endpoints/tokens/entities/token.account.ts b/src/endpoints/tokens/entities/token.account.ts index 9e1f422a8..2190fd009 100644 --- a/src/endpoints/tokens/entities/token.account.ts +++ b/src/endpoints/tokens/entities/token.account.ts @@ -23,5 +23,5 @@ export class TokenAccount { assets: AccountAssets | undefined = undefined; @ApiProperty({ type: String, nullable: true }) - searchAfter?: string | undefined = undefined; + searchAfter?: string | undefined; } diff --git a/src/endpoints/transactions/entities/transaction.ts b/src/endpoints/transactions/entities/transaction.ts index 35fa8066a..9eb99e5c2 100644 --- a/src/endpoints/transactions/entities/transaction.ts +++ b/src/endpoints/transactions/entities/transaction.ts @@ -120,7 +120,7 @@ export class Transaction { isScCall: boolean | undefined = undefined; @ApiProperty({ type: String, nullable: true, required: false }) - searchAfter?: string | undefined = undefined; + searchAfter?: string | undefined; getDate(): Date | undefined { if (this.timestamp) { diff --git a/src/endpoints/transactions/transaction.service.ts b/src/endpoints/transactions/transaction.service.ts index 765b9429e..68564059a 100644 --- a/src/endpoints/transactions/transaction.service.ts +++ b/src/endpoints/transactions/transaction.service.ts @@ -201,7 +201,7 @@ export class TransactionService { } async getTransactions(filter: TransactionFilter, pagination: QueryPagination, queryOptions?: TransactionQueryOptions, address?: string, fields?: string[]): Promise { - if (this.isCacheableTransactionList(filter, queryOptions, fields, address)) { + if (this.isCacheableTransactionList(filter, queryOptions, fields, address, pagination)) { const cacheInfo = CacheInfo.Transactions(pagination); return await this.cachingService.getOrSet( cacheInfo.key, @@ -223,7 +223,7 @@ export class TransactionService { const hasSenderFilter = filter.sender || (filter.senders && filter.senders.length > 0); const hasReceiverFilter = filter.receivers && filter.receivers.length > 0; - if (address && !hasSenderFilter && !hasReceiverFilter) { + if (address && !hasSenderFilter && !hasReceiverFilter && pagination.searchAfter === undefined) { transactions = this.reorderAccountSentTransactionsByNonce(transactions, address); } @@ -871,9 +871,9 @@ export class TransactionService { filter.withTxsRelayedByAddress === undefined; } - private isCacheableTransactionList(filter: TransactionFilter, queryOptions?: TransactionQueryOptions, fields?: string[], address?: string): boolean { + private isCacheableTransactionList(filter: TransactionFilter, queryOptions?: TransactionQueryOptions, fields?: string[], address?: string, pagination?: QueryPagination): boolean { const hasFieldSelection = Array.isArray(fields) && fields.length > 0; - if (address || hasFieldSelection || !this.isEmptyTransactionFilter(filter) || !queryOptions) { + if (address || hasFieldSelection || !this.isEmptyTransactionFilter(filter) || !queryOptions || pagination?.searchAfter) { return false; } diff --git a/src/endpoints/transfers/transfer.service.ts b/src/endpoints/transfers/transfer.service.ts index 313f2e8c4..41c5b8c81 100644 --- a/src/endpoints/transfers/transfer.service.ts +++ b/src/endpoints/transfers/transfer.service.ts @@ -121,7 +121,9 @@ export class TransferService { elasticOperations = await this.sortElasticTransfersByTxsOrder(elasticOperations, filter.miniBlockHash); } else { - elasticOperations = this.sortElasticTransfers(elasticOperations); + if (!pagination.searchAfter) { + elasticOperations = this.sortElasticTransfers(elasticOperations); + } } let transactions: TransactionDetailed[] = []; @@ -149,7 +151,7 @@ export class TransferService { const hasSenderFilter = filter.sender || (filter.senders && filter.senders.length > 0); const hasReceiverFilter = filter.receivers && filter.receivers.length > 0; - if (filter.address && !hasSenderFilter && !hasReceiverFilter) { + if (filter.address && !hasSenderFilter && !hasReceiverFilter && pagination.searchAfter === undefined) { transactions = this.transactionService.reorderAccountSentTransactionsByNonce(transactions, filter.address); } diff --git a/src/test/unit/services/network.spec.ts b/src/test/unit/services/network.spec.ts index 588eb482f..b05c730ae 100644 --- a/src/test/unit/services/network.spec.ts +++ b/src/test/unit/services/network.spec.ts @@ -277,11 +277,9 @@ describe('NetworkService', () => { }); jest.spyOn(ApiConfigService.prototype, 'getNetwork') - .mockImplementation(jest.fn(() => ('mainnet'))); jest.spyOn(ApiConfigService.prototype, 'getCluster') - .mockImplementation(jest.fn(() => undefined)); const result = await networkService.getAboutRaw(); From 3bd6b5312fcfed08c397d4012e7c606b47961143 Mon Sep 17 00:00:00 2001 From: GuticaStefan Date: Wed, 17 Jun 2026 15:28:45 +0300 Subject: [PATCH 08/15] add package-lock --- package-lock.json | 1150 ++++++++++++++++++++++++--------------------- 1 file changed, 603 insertions(+), 547 deletions(-) diff --git a/package-lock.json b/package-lock.json index f04c475e3..ab0d18e11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -129,9 +129,9 @@ "license": "MIT" }, "node_modules/@angular-devkit/core": { - "version": "19.2.24", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.24.tgz", - "integrity": "sha512-Kd49warf6U/EyWe5BszF/eebN3zQ3bk7tgfEljAw8q/rX95UUtriJubWvp6pgzHfzBA4jwq8f+QiNZB8eBEXPA==", + "version": "19.2.27", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.27.tgz", + "integrity": "sha512-3amNzoCVSKd7ah6l6lBQL4onwwJvqvam7FMoQBILrxtW5LB5ezh8gMSPuA4zJjKjoRzf9uoWdlzqv/84I52xZA==", "dev": true, "license": "MIT", "dependencies": { @@ -167,13 +167,13 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "19.2.24", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.24.tgz", - "integrity": "sha512-lnw+ZM1Io+cJAkReC0NPDjqObL8NtKzKIkdgEEKC8CUmkhurYhedbicN8Y8NYHgG1uLd2GozW3+/QqPRZaN+Lw==", + "version": "19.2.27", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.27.tgz", + "integrity": "sha512-/PZmyAlb2NGWPikRRuiWLdfHQd8Wrx6lX4HqvTcaDhlU43M3T0ud4PH2T3QDp7BzHYY92xtD8iPxX2asg67G1A==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "19.2.24", + "@angular-devkit/core": "19.2.27", "jsonc-parser": "3.3.1", "magic-string": "0.30.17", "ora": "5.4.1", @@ -186,14 +186,14 @@ } }, "node_modules/@angular-devkit/schematics-cli": { - "version": "19.2.24", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-19.2.24.tgz", - "integrity": "sha512-bsStZQG67J1HBqTmWxtIcobvgrn32L4UOdL7hGyOru5VxDWPNA8pRnDYavT3hnJeBkJYPoQIw8u7Dm0ecoQprw==", + "version": "19.2.27", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-19.2.27.tgz", + "integrity": "sha512-wHYH6SVXVykhLzovUHtYor3Nl4SpIiITi7r9DQDaKYUD4hpRBx25W6N9eGuakT9Vd5tV/x6wmvQFWQZQwFB7eA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "19.2.24", - "@angular-devkit/schematics": "19.2.24", + "@angular-devkit/core": "19.2.27", + "@angular-devkit/schematics": "19.2.27", "@inquirer/prompts": "7.3.2", "ansi-colors": "4.1.3", "symbol-observable": "4.0.0", @@ -673,61 +673,63 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-s3": { - "version": "3.1056.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1056.0.tgz", - "integrity": "sha512-WfMZEM2eC96anIT0RzR/QmhaefWKsNjOHNv09ORBkcA++ULBnm1fRau+KKGEIbr5nDnf9BTG7E7U3oOVklQS8g==", + "node_modules/@aws-sdk/checksums": { + "version": "3.1000.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.6.tgz", + "integrity": "sha512-RMCrCteiUwYTEv2G9zfP/BEuKHv57665vVieJyp9cf8VgilWxP/KrWVtMdfdDlIH8nFhvu3rIMc29z3ebGEZ1w==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha1-browser": "5.2.0", - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.15", - "@aws-sdk/credential-provider-node": "^3.972.46", - "@aws-sdk/middleware-bucket-endpoint": "^3.972.17", - "@aws-sdk/middleware-expect-continue": "^3.972.14", - "@aws-sdk/middleware-flexible-checksums": "^3.974.23", - "@aws-sdk/middleware-location-constraint": "^3.972.11", - "@aws-sdk/middleware-sdk-s3": "^3.972.44", - "@aws-sdk/middleware-ssec": "^3.972.11", - "@aws-sdk/signature-v4-multi-region": "^3.996.30", - "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.5", - "@smithy/fetch-http-handler": "^5.4.5", - "@smithy/node-http-handler": "^4.7.5", - "@smithy/types": "^4.14.2", + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "^3.974.21", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/core": { - "version": "3.974.15", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.15.tgz", - "integrity": "sha512-UpA0rTGW/tHGITcCqHisbuuEPraYg9GG+mWmXjY5+RxZBMLGe6aL9oe0ix50LztwAcPIkGZLH0yWdMIkCM10hw==", + "node_modules/@aws-sdk/client-s3": { + "version": "3.1070.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1070.0.tgz", + "integrity": "sha512-B/OUiCqGQ4Zr7v9gFFyiuitKN2c0PIgvOlQb5bYg1SM2y0F8a5JQ7FNsjRcl+d2PqYWLHwHx12CvZDyLn4KxIw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.9", - "@aws-sdk/xml-builder": "^3.972.26", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/core": "^3.24.5", - "@smithy/signature-v4": "^5.4.5", - "@smithy/types": "^4.14.2", - "bowser": "^2.11.0", + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.21", + "@aws-sdk/credential-provider-node": "^3.972.56", + "@aws-sdk/middleware-flexible-checksums": "^3.974.31", + "@aws-sdk/middleware-sdk-s3": "^3.972.52", + "@aws-sdk/signature-v4-multi-region": "^3.996.35", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/fetch-http-handler": "^5.4.6", + "@smithy/node-http-handler": "^4.7.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/crc64-nvme": { - "version": "3.972.9", - "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.9.tgz", - "integrity": "sha512-P+QGozmXn2mZZI7sDgk+aUm+RTI61MPSFB+Ir2vjEjEbEsE4e7hYtzrDvAUxZy9ko81h53e11+F/GYlvwDkaOQ==", + "node_modules/@aws-sdk/core": { + "version": "3.974.21", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.21.tgz", + "integrity": "sha512-P5JAHvn4dTi96UsAGS67LVOqqpUNNRhnfFXqzCYtdBIGZtqBue4CXvRr9YenOO7PALj/Pn8uuyw53FBCiCYw8w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.2", + "@aws-sdk/types": "^3.973.13", + "@aws-sdk/xml-builder": "^3.972.30", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/core": "^3.24.6", + "@smithy/signature-v4": "^5.4.6", + "@smithy/types": "^4.14.3", + "bowser": "^2.11.0", "tslib": "^2.6.2" }, "engines": { @@ -735,15 +737,15 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.41.tgz", - "integrity": "sha512-n1EbJ98yvPWWdHZZv8bRBMqqDQJrtgtxyJ4xLy2Uqrh25BCOZQ7nnS1CsFXvuH8r0b0KVHDZEGEH5FxmEMP8jg==", + "version": "3.972.47", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.47.tgz", + "integrity": "sha512-3YoPwJczcc+MtX2xxXaYaOOWO6xKUJr1ZIIDIFuninr51BYONVVcF/CP8K2xfVRC/PztJjqKWxNGFH7BWQAw1Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.15", - "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.5", - "@smithy/types": "^4.14.2", + "@aws-sdk/core": "^3.974.21", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -751,17 +753,17 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.43", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.43.tgz", - "integrity": "sha512-TT76RN1NkI9WoyZqCNxOw6/WBMF7pYOTJcXbMokNFU+euSG40Kaf/t/FhDACVZWP+43wEM6ZynIPIkzS1wR1iA==", + "version": "3.972.49", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.49.tgz", + "integrity": "sha512-2UtGUPy+x3lqyceHrtC1uEuVxBZbDalPF6KAFqBwYgm4edWdBrZKNnCqzDs7KynWUvEC6mrR+ojRk+ZgQz9C2w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.15", - "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.5", - "@smithy/fetch-http-handler": "^5.4.5", - "@smithy/node-http-handler": "^4.7.5", - "@smithy/types": "^4.14.2", + "@aws-sdk/core": "^3.974.21", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/fetch-http-handler": "^5.4.6", + "@smithy/node-http-handler": "^4.7.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -769,23 +771,23 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.45", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.45.tgz", - "integrity": "sha512-sJe5ZWibO4s7RWjFQ8Zol76KxoJcIYyEZH1/wxQSBMSIAAxzaJ8cS/ITAaIHWUQvDKQdt18+cJAHKWB7n1Jmrg==", + "version": "3.972.54", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.54.tgz", + "integrity": "sha512-Hx4gO4YRjFwitf3MVl3cDwYe1aryJthC4txVl9b+JAURovA50M2ywf9r8j1E/Q6SCTPT4qQpjOAbKYIC9CG+Vw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.15", - "@aws-sdk/credential-provider-env": "^3.972.41", - "@aws-sdk/credential-provider-http": "^3.972.43", - "@aws-sdk/credential-provider-login": "^3.972.45", - "@aws-sdk/credential-provider-process": "^3.972.41", - "@aws-sdk/credential-provider-sso": "^3.972.45", - "@aws-sdk/credential-provider-web-identity": "^3.972.45", - "@aws-sdk/nested-clients": "^3.997.13", - "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.5", - "@smithy/credential-provider-imds": "^4.3.5", - "@smithy/types": "^4.14.2", + "@aws-sdk/core": "^3.974.21", + "@aws-sdk/credential-provider-env": "^3.972.47", + "@aws-sdk/credential-provider-http": "^3.972.49", + "@aws-sdk/credential-provider-login": "^3.972.53", + "@aws-sdk/credential-provider-process": "^3.972.47", + "@aws-sdk/credential-provider-sso": "^3.972.53", + "@aws-sdk/credential-provider-web-identity": "^3.972.53", + "@aws-sdk/nested-clients": "^3.997.21", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/credential-provider-imds": "^4.3.7", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -793,16 +795,16 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.45", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.45.tgz", - "integrity": "sha512-MZQv4SNjByk1iOKmrqmzcUF/uCB05wjvEHyXKxmGQTUANTIVayX6HPUF0bzkWLvtnkH7sAn9kUCfkXbSpj9sDA==", + "version": "3.972.53", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.53.tgz", + "integrity": "sha512-+71sluhkgPqdhbbD3UDwUpj24GCkng9HQx6z7qoBFb8dwkF4ktpOcVKDeHpgg8PvBgLYwAnUYLTEGRC/PniCiQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.15", - "@aws-sdk/nested-clients": "^3.997.13", - "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.5", - "@smithy/types": "^4.14.2", + "@aws-sdk/core": "^3.974.21", + "@aws-sdk/nested-clients": "^3.997.21", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -810,21 +812,21 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.46", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.46.tgz", - "integrity": "sha512-cS4w0jzDRb1jOlkiJS3y80OxddHzkky/MN9k3NYs5jganNKVLjF0lpvjlwS118oGMr3cdAfOlVdo8gLurTSE7w==", + "version": "3.972.56", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.56.tgz", + "integrity": "sha512-iI+4o0dvQQ4NHel4FMDiFy5q2gaU/ryLK3niOsoPccAt9WLFRkV4XTYPWRr9XvmBUqEzXG73S4p/8gm0Lu/W3A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.41", - "@aws-sdk/credential-provider-http": "^3.972.43", - "@aws-sdk/credential-provider-ini": "^3.972.45", - "@aws-sdk/credential-provider-process": "^3.972.41", - "@aws-sdk/credential-provider-sso": "^3.972.45", - "@aws-sdk/credential-provider-web-identity": "^3.972.45", - "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.5", - "@smithy/credential-provider-imds": "^4.3.5", - "@smithy/types": "^4.14.2", + "@aws-sdk/credential-provider-env": "^3.972.47", + "@aws-sdk/credential-provider-http": "^3.972.49", + "@aws-sdk/credential-provider-ini": "^3.972.54", + "@aws-sdk/credential-provider-process": "^3.972.47", + "@aws-sdk/credential-provider-sso": "^3.972.53", + "@aws-sdk/credential-provider-web-identity": "^3.972.53", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/credential-provider-imds": "^4.3.7", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -832,15 +834,15 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.41.tgz", - "integrity": "sha512-7I/n1zkysouLOWvkEhjNEP4vMnD2v4kzzr3/3QBdrripEpn7ap1/I5DF3Hou1SUqkKWo1f3oPGMyFAA1FAMvsQ==", + "version": "3.972.47", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.47.tgz", + "integrity": "sha512-tAizPm9IFo/PHn06c+LQJlzfY2AGOlyF0CUljFejrU6LcZBjnk8pmbZK3/xoIDdnIzjEdbClfvY3mXfr818ZEg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.15", - "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.5", - "@smithy/types": "^4.14.2", + "@aws-sdk/core": "^3.974.21", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -848,17 +850,17 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.45", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.45.tgz", - "integrity": "sha512-oHgbz/eFD8IKiksqDsz9ZMU4A59BpQq4QwJedBnGD80ZqYcHPPHZBwjBnxLVkB7iRVVHWpDclR8yWdD2PkQIUA==", + "version": "3.972.53", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.53.tgz", + "integrity": "sha512-pUXE3fu4tfEDV8BksIgf4dXvuIH10FhwHMl/wu8rBD5T1sMpryQWFVitH3kdPS90wlgrGYJQ/meQTSPacyZfeg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.15", - "@aws-sdk/nested-clients": "^3.997.13", - "@aws-sdk/token-providers": "3.1056.0", - "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.5", - "@smithy/types": "^4.14.2", + "@aws-sdk/core": "^3.974.21", + "@aws-sdk/nested-clients": "^3.997.21", + "@aws-sdk/token-providers": "3.1069.0", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -866,47 +868,16 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.45", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.45.tgz", - "integrity": "sha512-CDhzKdb2onv5bpnjn/acgdNmJOQthPDLsPizU7rZflsEcgMMp8Mlri+U5hdxf8ldvZJpvM3vLU6D56vfJm5AMQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.15", - "@aws-sdk/nested-clients": "^3.997.13", - "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.5", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.972.17", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.17.tgz", - "integrity": "sha512-lbDmWuHenc+kiwCNrxz4MyN6nkxCWyTXPIWuspJN0ibziu+8CXci7vI1bK9MAkwy8cwJOEXNu0gBM5S0uTGRIg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.15", - "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.5", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.972.14", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.14.tgz", - "integrity": "sha512-3TNFEVGO4sWZj9TEXOCZLzGEctXHnaO4fk2EQ8KVaboTbwHmEPEQrm17Xb9koImUIXEw0sgi2xtHjg7LuTS3rA==", + "version": "3.972.53", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.53.tgz", + "integrity": "sha512-JmMGlhVvSj8uSG9CpeDkJAXT35H89tc6v84iMgEIE75q4yp1MKVVKvopv6Gg28HJIR7hMNkojRF8H2m5W44wyg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.5", - "@smithy/types": "^4.14.2", + "@aws-sdk/core": "^3.974.21", + "@aws-sdk/nested-clients": "^3.997.21", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -914,33 +885,12 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.974.23", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.23.tgz", - "integrity": "sha512-4nPKARo2lfKvQGUt2fPA5NlS/mEohckdxpuC9ecbjVfj7B7NFFYHeTg+Bf5BEQwdn3yRfUIzFiEkPp8Yuaw3wA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@aws-crypto/crc32c": "5.2.0", - "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "^3.974.15", - "@aws-sdk/crc64-nvme": "^3.972.9", - "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.5", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.972.11", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.11.tgz", - "integrity": "sha512-hkfspNUP4criAH6ton6BGKgnm5dZx+7bUOy1YqlTfejDeUPAM23D81q/IX+hdlS3KUsfwGz5ADTqZWKBEUpf4A==", + "version": "3.974.31", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.31.tgz", + "integrity": "sha512-Yzj6NRYVZdBaCp7o1BwHGyeDBfixdeToLIAMprshIITEdl9wKVSiidVOfeaiH8FyeC1hBmBfDZFvs/aH1Y3xpw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.9", - "@smithy/types": "^4.14.2", + "@aws-sdk/checksums": "^3.1000.6", "tslib": "^2.6.2" }, "engines": { @@ -948,30 +898,16 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.44", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.44.tgz", - "integrity": "sha512-8HQsRg1NpX8vR4vNl1E8pyLnqZroq9VSL2vZQVSgBqp6wv6365LzYD08/c9FFh/9FTg7YRc7aTtEmXF0ir/pqg==", + "version": "3.972.52", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.52.tgz", + "integrity": "sha512-rerjP08onRqkBh0AcCqip6GkKvESapmLoTgi1xysZ4C6a1xMrIMtTBcEbUb6EY71oeajnigeUD4KwZjtIO+aWQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.15", - "@aws-sdk/signature-v4-multi-region": "^3.996.30", - "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.5", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.972.11", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.11.tgz", - "integrity": "sha512-7PQvGNhtveKlvVqNahqWx5yrwxP7ecwAoB1dYBf8eKwfo2tzzCbNnW+q2nO3N066ktQaB4iBQbDRWtizm+amoQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.9", - "@smithy/types": "^4.14.2", + "@aws-sdk/core": "^3.974.21", + "@aws-sdk/signature-v4-multi-region": "^3.996.35", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -979,20 +915,20 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.13.tgz", - "integrity": "sha512-2pA6eyb5nSo/ZD2cayhOTEMoGQYgspq0RI05GDLkzQ3ajZ6isS6waV6E92Am/hz4LIlLUTrbwPLurJ/fuiHvkg==", + "version": "3.997.21", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.21.tgz", + "integrity": "sha512-eC7Vl7Qom/BGhZjG9GEqPwdQ/fk45hg1t5LP4EUxG5d1fdshLbaxCiwh/tszUzDX/4mW40mu2QsbeJJRPBbqUw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.15", - "@aws-sdk/signature-v4-multi-region": "^3.996.30", - "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.5", - "@smithy/fetch-http-handler": "^5.4.5", - "@smithy/node-http-handler": "^4.7.5", - "@smithy/types": "^4.14.2", + "@aws-sdk/core": "^3.974.21", + "@aws-sdk/signature-v4-multi-region": "^3.996.35", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/fetch-http-handler": "^5.4.6", + "@smithy/node-http-handler": "^4.7.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -1000,14 +936,14 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.30", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.30.tgz", - "integrity": "sha512-HULDLMVzkmTSEv6//7kx2kRevp/VYUpm8hJNNFbmhxDn0fUiGTxVcM9yg31TukvTq8nyOBDUN2gH0o5IRbKjdw==", + "version": "3.996.35", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.35.tgz", + "integrity": "sha512-6L/VWs+Wch2stHemCGTmUNqKLMzURxQDK5boNG3Jn3kAOp71meDUuS5sbObpEvFxHDq0uWeSLFDNSYsjNt+Dlg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.9", - "@smithy/signature-v4": "^5.4.5", - "@smithy/types": "^4.14.2", + "@aws-sdk/types": "^3.973.13", + "@smithy/signature-v4": "^5.4.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -1015,16 +951,16 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.1056.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1056.0.tgz", - "integrity": "sha512-81duvlltQlsfn5K+o8zILcystBRdbT1G2JJYVCML5NZHBz4CL/zf+sAemCtBh/uh6RQUMyInGeZLQ7/8igZhbA==", + "version": "3.1069.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1069.0.tgz", + "integrity": "sha512-ks4X+kngC3PA5howV7Qu1TgG4bfC4jPykKdvw3nmBSXR9yZxRJouBholFSNQ5kY3L+Fgwyw+LCjzQmNi+KR91g==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.15", - "@aws-sdk/nested-clients": "^3.997.13", - "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.5", - "@smithy/types": "^4.14.2", + "@aws-sdk/core": "^3.974.21", + "@aws-sdk/nested-clients": "^3.997.21", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -1032,12 +968,12 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.973.9", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.9.tgz", - "integrity": "sha512-kuBfgQVdcz5Bmapc4A13YbpVw/pXkesfhetcFYwbntqas8sF41OHyd4o28+/TG2ZQdHBsv90Lsu5y6oitvYCdg==", + "version": "3.973.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.13.tgz", + "integrity": "sha512-pEHZqRkAlHfnfAU9tK+WpKv/gBNjGJrHMgA3A0iYRGyswBS2t0pfez+lWlwktb3Bqa0ovh7w/QJTFwp3fDxLNg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.2", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -1045,9 +981,9 @@ } }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.965.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", - "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", + "version": "3.965.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.8.tgz", + "integrity": "sha512-uUbMs1cBZPafD0ohUj6EwNf0fPZ534NvBxHox4hjX+0Rxq5paSYUem7+hi833pYrzrcnBATKIYpR02MDXT5M9g==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -1057,12 +993,12 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.26", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.26.tgz", - "integrity": "sha512-cDbrqvDS73whl6YAPSPq0U6whzG6UWI9PuWh0wrUuGoZexhWEqhdunbukV7iBoaWnFV1AODutM5hOD6rtn439g==", + "version": "3.972.30", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.30.tgz", + "integrity": "sha512-StElZPEoBquWwNqw1AcfpzEyZqJvFxouG+mpDNYlcH6ZOrqd2CuIryv+8LV8gNHZUOyKyJF3Dq9vxaXEmDR9TQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.2", + "@smithy/types": "^4.14.3", "fast-xml-parser": "5.7.3", "tslib": "^2.6.2" }, @@ -1772,9 +1708,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", "license": "MIT", "optional": true, "dependencies": { @@ -1862,9 +1798,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", - "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3699,9 +3635,9 @@ "license": "MIT" }, "node_modules/@multiversx/sdk-nestjs-auth": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-nestjs-auth/-/sdk-nestjs-auth-7.0.1.tgz", - "integrity": "sha512-cN/Zt1L44hhtr7gtkVVW7HqPf5awytpab4n7l41169IqSpwBjpQan4sVUwCAVJfTHsG39GOsx411+KLBykooxQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-nestjs-auth/-/sdk-nestjs-auth-7.1.0.tgz", + "integrity": "sha512-aK6F6JsLQcvdHZzaW79Lpm4NErRoyF/7Gz0mUrTEXcwPV8+zHlFWBDMxCRx6ay+RCCFAneBOBsrZDKn1nX0LfA==", "license": "MIT", "dependencies": { "@multiversx/sdk-core": "^15.4.0", @@ -3716,9 +3652,9 @@ } }, "node_modules/@multiversx/sdk-nestjs-cache": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-nestjs-cache/-/sdk-nestjs-cache-7.0.1.tgz", - "integrity": "sha512-CxhKoCiOLhKje4KoHvVkomnLf7awZgZekSteEuByEx4V5yu+Z1SSiU88aVkODsEymIWiK1V4cFe5w+QgVN3FWg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-nestjs-cache/-/sdk-nestjs-cache-7.1.0.tgz", + "integrity": "sha512-+kl5m6yLa8GC5NuiUOCBv6MgluhANvmcD/bSVtT2EM6tHREuK06DicVTZFOw2GqJhzKCQaU7j6lO2QNBVTDgsA==", "license": "MIT", "dependencies": { "lru-cache": "^11.3.5", @@ -3741,9 +3677,9 @@ "license": "MIT" }, "node_modules/@multiversx/sdk-nestjs-common": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-nestjs-common/-/sdk-nestjs-common-7.0.1.tgz", - "integrity": "sha512-Ptdo6r6/IlvqMEtCRsWssiGyh1YiYyDiep5LZaG+/yXLysUUiOkfVK83V2qqS2Ms3gT0tFiRWcenltUjUbIzOQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-nestjs-common/-/sdk-nestjs-common-7.1.0.tgz", + "integrity": "sha512-p4+8LUxpiDlW6hZ81YRJi4IGjEYt6d4Nq4DUXwydsVA57f89X8SLl7DFxhPgnfTTV2GY/JbUYqD8/ImE7TAuYQ==", "license": "MIT", "dependencies": { "@multiversx/sdk-core": "^15.4.0", @@ -3760,9 +3696,9 @@ } }, "node_modules/@multiversx/sdk-nestjs-elastic": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-nestjs-elastic/-/sdk-nestjs-elastic-7.0.1.tgz", - "integrity": "sha512-4sFCBl5iFG/WBN7alEIgzOh9/WotPBj+XI3e0WbafIAOmrq6RMFd0rO1ZxwloNgtlWBWtznfmL+T9JPUcQoINw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-nestjs-elastic/-/sdk-nestjs-elastic-7.1.0.tgz", + "integrity": "sha512-CERcvydz7w9fNVlsqx0fEK29/4HxBqRs2xta8nbItB9ER+nrTNCNT8wIofEwN7S25l2i8CqLYhQnTrIXKWZddQ==", "license": "MIT", "peerDependencies": { "@multiversx/sdk-nestjs-http": "^7.0.0", @@ -3770,9 +3706,9 @@ } }, "node_modules/@multiversx/sdk-nestjs-http": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-nestjs-http/-/sdk-nestjs-http-7.0.1.tgz", - "integrity": "sha512-8u9s6dU90YeJqE62PIRhBWe4YM6C03a/5odxU7icXhHHVXKm+f3Hu9FTiQrQXXhYpiaUQSTNwkWjkaNw2no+ZQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-nestjs-http/-/sdk-nestjs-http-7.1.0.tgz", + "integrity": "sha512-rFXw/Zz1IP81JdiP0IEXQSm2go9IT2nHqxxcgdKfw8IDO6eRYUvjt1KdugPRItBWaEkMM7cd3rlxjWS7ni1yCA==", "license": "MIT", "dependencies": { "@multiversx/sdk-core": "^15.4.0", @@ -3788,9 +3724,9 @@ } }, "node_modules/@multiversx/sdk-nestjs-monitoring": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-nestjs-monitoring/-/sdk-nestjs-monitoring-7.0.1.tgz", - "integrity": "sha512-drh4RAvon1FRpXEgTWBVAO2WY2NjmkWGQ6lUAu9m4K+2omKsxmudegK9sWmzjdibpFo4U4+xDqmdD6sJAptF2g==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-nestjs-monitoring/-/sdk-nestjs-monitoring-7.1.0.tgz", + "integrity": "sha512-yZT2+k7inL2Dh1X6SVo0jwW0zXDXFxAxkOwxH79Yi0Z2C/VzkVWJScT+DmHWbSzK/W6A1p4yiDsghts7zZ7w0g==", "license": "MIT", "dependencies": { "prom-client": "^15.1.3", @@ -3833,9 +3769,9 @@ } }, "node_modules/@multiversx/sdk-nestjs-rabbitmq": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-nestjs-rabbitmq/-/sdk-nestjs-rabbitmq-7.0.1.tgz", - "integrity": "sha512-p7FNaCZHvE4hMAmoBCm2IEtWI2G/LjT1Ufxp1tF9Dcg0VWck2VqgXa6NFJErzYaHjUYZ53r7KTJtr40qhtCwXw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-nestjs-rabbitmq/-/sdk-nestjs-rabbitmq-7.1.0.tgz", + "integrity": "sha512-BqEqe7Z1f7kjPBz4XDqOzQo7P4/eyUyjqRB9PARK2WraG5ivWfjK4yIrNkn5tWqbtZafw+hzpFnuobx8LebGNg==", "license": "MIT", "dependencies": { "@golevelup/nestjs-rabbitmq": "9.0.0", @@ -3881,9 +3817,9 @@ } }, "node_modules/@multiversx/sdk-nestjs-redis": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-nestjs-redis/-/sdk-nestjs-redis-7.0.1.tgz", - "integrity": "sha512-ejni+2Qb1VyHvOcJBKkP8lp8oau6JFKe9LVC8kLQOFAFfv4Q2Cg3U8pXD9lhPwgh/4xds2KYontSmbRuRhkDpQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-nestjs-redis/-/sdk-nestjs-redis-7.1.0.tgz", + "integrity": "sha512-B3OtGpZ3bvm3ClYyl6H4pi5Qhi/aUBJ9KjMsVwZy+XapxBM82tNLx2nDgWxTZieIefT4FLyjCBUzg8CwMCtZDQ==", "license": "MIT", "dependencies": { "ioredis": "^5.10.1" @@ -3953,15 +3889,15 @@ } }, "node_modules/@nestjs/cli": { - "version": "11.0.21", - "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.21.tgz", - "integrity": "sha512-F8mV0Sj/zVEouzR3NxBuJy08YHTUOmC5Xdcx3qIIaJWzrm8Vw86CHkhkaPBJ5ewRMHPDCShPmhsfwhpCcjts3A==", + "version": "11.0.23", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.23.tgz", + "integrity": "sha512-2V0Bf5jz0KXhUZk3eJi9GljIyqH04otwsE/mYLbqJR+X0iiYx+6bkNJ2Qz28uHNFj1cpHgimf9xDzHkqarie0g==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "19.2.24", - "@angular-devkit/schematics": "19.2.24", - "@angular-devkit/schematics-cli": "19.2.24", + "@angular-devkit/core": "19.2.27", + "@angular-devkit/schematics": "19.2.27", + "@angular-devkit/schematics-cli": "19.2.27", "@inquirer/prompts": "7.10.1", "@nestjs/schematics": "^11.0.1", "ansis": "4.2.0", @@ -3975,7 +3911,7 @@ "tsconfig-paths": "4.2.0", "tsconfig-paths-webpack-plugin": "4.2.0", "typescript": "5.9.3", - "webpack": "5.106.0", + "webpack": "5.106.2", "webpack-node-externals": "3.0.0" }, "bin": { @@ -4039,29 +3975,6 @@ "node": ">=4.0" } }, - "node_modules/@nestjs/cli/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@nestjs/cli/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/@nestjs/cli/node_modules/schema-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", @@ -4097,9 +4010,9 @@ } }, "node_modules/@nestjs/cli/node_modules/webpack": { - "version": "5.106.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.0.tgz", - "integrity": "sha512-Pkx5joZ9RrdgO5LBkyX1L2ZAJeK/Taz3vqZ9CbcP0wS5LEMx5QkKsEwLl29QJfihZ+DKRBFldzy1O30pJ1MDpA==", + "version": "5.106.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.2.tgz", + "integrity": "sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==", "dev": true, "license": "MIT", "dependencies": { @@ -4119,9 +4032,8 @@ "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.3.1", - "mime-types": "^2.1.27", + "mime-db": "^1.54.0", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", @@ -4146,9 +4058,9 @@ } }, "node_modules/@nestjs/common": { - "version": "11.1.24", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.24.tgz", - "integrity": "sha512-9zHxaDDM+oXW9As6UsP5yYB+UqczBmpeSCIFWdPEtEukMnZhxODG1BBjaUcdBB8Sc1uzojSJSJlp3yFp853t1g==", + "version": "11.1.27", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.27.tgz", + "integrity": "sha512-kEGSzqM2lWr4whh4Ubflw+oPZSEzxvRMu9WL+LveZploJWTjec5bBlCiRVlVzTPg2kIwBiLwWSvCCW7Wnin1gg==", "license": "MIT", "dependencies": { "file-type": "21.3.4", @@ -4192,13 +4104,11 @@ } }, "node_modules/@nestjs/core": { - "version": "11.1.24", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.24.tgz", - "integrity": "sha512-K4bzT+lEdd0Hhcsw3jtk56QAW6s6skK3ViN7hIROSN0kUf4ROwWEAKopJID6yhPQxB45kDtP2wEcjzE8171J3g==", - "hasInstallScript": true, + "version": "11.1.27", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.27.tgz", + "integrity": "sha512-K6DX7hcqmZdeXkv7tsPakKBRCgqL19a4mtbX4FluY0hWtFdtPKp6lbe+lb8gWPfvLdbOWr/CPScn7BSjBX+Ecg==", "license": "MIT", "dependencies": { - "@nuxt/opencollective": "0.4.1", "fast-safe-stringify": "2.1.1", "iterare": "1.2.1", "path-to-regexp": "8.4.2", @@ -4311,9 +4221,9 @@ } }, "node_modules/@nestjs/microservices": { - "version": "11.1.24", - "resolved": "https://registry.npmjs.org/@nestjs/microservices/-/microservices-11.1.24.tgz", - "integrity": "sha512-ALu/7qk3obFlw7KVSPRz+BjuyWPLmv9isknhLG8UYXkjx3aPhJGp52i3qiTqucM1jKtoBgPa3+SK4e9fVvglGA==", + "version": "11.1.27", + "resolved": "https://registry.npmjs.org/@nestjs/microservices/-/microservices-11.1.27.tgz", + "integrity": "sha512-wVUuNNFxILFtG6CvzvdcF/2/CtwWTKNR8AXsHzH7+JKoJY1xAXqvw8b6LArUKyRpOfsiKMLCC/KKvvA4Enh/xg==", "license": "MIT", "dependencies": { "iterare": "1.2.1", @@ -4369,9 +4279,9 @@ } }, "node_modules/@nestjs/platform-express": { - "version": "11.1.24", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.24.tgz", - "integrity": "sha512-CeMKbRBm05aOBiWhIHWO2xDeHbxynBF9ySQv3gRjObz2N5+uJnYriAYkHvVqvC4JIydmMPmT5VdICFNlNz3qyA==", + "version": "11.1.27", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.27.tgz", + "integrity": "sha512-0ZFhz6H6EdGh4xQVbUNwjoAwBuz73P7FvUAl67h9CTdMqQlJDaQYJApBv8pKfVZ1fGjMCbl0m9DcC6pXaZPWSQ==", "license": "MIT", "dependencies": { "cors": "2.8.6", @@ -4390,9 +4300,9 @@ } }, "node_modules/@nestjs/platform-socket.io": { - "version": "11.1.24", - "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.1.24.tgz", - "integrity": "sha512-ImdR9G8W5Y2Hhcptdci+tNaG6JV/dzDguFTgtXOL5ie/gD9O9ARw8Cd9RzF2+oteyzQ+1sPK/+wgVOPOyYGVCA==", + "version": "11.1.27", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.1.27.tgz", + "integrity": "sha512-xgpLzaIDGOCC6xOAtHnRAz8sqieFgGxxu3MN5ID026Jt6oeL3efp29N5QHhPr7UlqBfy/Jd02uj0POkZq6Au3Q==", "license": "MIT", "dependencies": { "socket.io": "4.8.3", @@ -4444,6 +4354,63 @@ } } }, + "node_modules/@nestjs/schematics/node_modules/@angular-devkit/core": { + "version": "19.2.24", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.24.tgz", + "integrity": "sha512-Kd49warf6U/EyWe5BszF/eebN3zQ3bk7tgfEljAw8q/rX95UUtriJubWvp6pgzHfzBA4jwq8f+QiNZB8eBEXPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.4", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@nestjs/schematics/node_modules/@angular-devkit/schematics": { + "version": "19.2.24", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.24.tgz", + "integrity": "sha512-lnw+ZM1Io+cJAkReC0NPDjqObL8NtKzKIkdgEEKC8CUmkhurYhedbicN8Y8NYHgG1uLd2GozW3+/QqPRZaN+Lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.24", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.17", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@nestjs/schematics/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/@nestjs/swagger": { "version": "11.4.4", "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.4.4.tgz", @@ -4477,10 +4444,22 @@ } } }, + "node_modules/@nestjs/swagger/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/@nestjs/testing": { - "version": "11.1.24", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.24.tgz", - "integrity": "sha512-+4M4UAnhtprBQN0J2uI6IP0wDqhy9aH8XCMu5SO8oCi0oB04YXA4a4PAEkxmsPn7gHW4dj1u4GFteNQOWgvTJw==", + "version": "11.1.27", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.27.tgz", + "integrity": "sha512-I35po13UHZZeGenLWJ3QYwh77RsLau5RcFKWBZ4waVHeARpwjtC7v7n7lGh98swLQdGmZgTnbvKaZ0B5dsUIKA==", "dev": true, "license": "MIT", "dependencies": { @@ -4506,9 +4485,9 @@ } }, "node_modules/@nestjs/typeorm": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.1.tgz", - "integrity": "sha512-8rw/nKT0S+L+MkzgE9F2/mox7mAgsPlwfzmW9gsESN1lmQtIrVEfiiBwC2O8+guS1jBfQehJIdcdUj2OAp4VUQ==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.2.tgz", + "integrity": "sha512-KQsmOKqIbfq+I1fe88rDq8QNfTU3C4b8MA2qOImhvIbIDOStcC+m+z2itf7vnWGQK0LZBFjWlYdRBID12qgZeg==", "license": "MIT", "peerDependencies": { "@nestjs/common": "^10.0.0 || ^11.0.0", @@ -4519,9 +4498,9 @@ } }, "node_modules/@nestjs/websockets": { - "version": "11.1.24", - "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.1.24.tgz", - "integrity": "sha512-37Z/QYzZ4nPHcGyGGjhjoKVOcpSPMhmRQj5DS1l0RKlRYgq8S0cmgaZ6kQ8PI3259PdchLx41oQibXh22iEUiA==", + "version": "11.1.27", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.1.27.tgz", + "integrity": "sha512-X3OgJt9KgYTvt9D7sNz9SOj3A1daAHy7DZrYhM1pky8Fh+erlKQH5IQ/tKm+GaJKA5M0srBUr1CMqjak/qNxOw==", "license": "MIT", "dependencies": { "iterare": "1.2.1", @@ -4566,9 +4545,9 @@ "license": "MIT" }, "node_modules/@nodable/entities": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.1.tgz", - "integrity": "sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg==", "funding": [ { "type": "github", @@ -4612,22 +4591,6 @@ "node": ">= 8" } }, - "node_modules/@nuxt/opencollective": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@nuxt/opencollective/-/opencollective-0.4.1.tgz", - "integrity": "sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==", - "license": "MIT", - "dependencies": { - "consola": "^3.2.3" - }, - "bin": { - "opencollective": "bin/opencollective.js" - }, - "engines": { - "node": "^14.18.0 || >=16.10.0", - "npm": ">=5.10.0" - } - }, "node_modules/@opentelemetry/api": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.8.0.tgz", @@ -4845,13 +4808,13 @@ } }, "node_modules/@smithy/core": { - "version": "3.24.5", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.5.tgz", - "integrity": "sha512-Kt8phUg45M15EjhYAbZ+fFikYneijLu9Liugz8ZsYz2i8j0hzGv27LWKpEHYRfvj+LyCOSijpcR/2i8RouV+cA==", + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.25.0.tgz", + "integrity": "sha512-TTD6el7tvKyafkXBf7XO3jLOE+qVxOTrLjp/fEGiV3BMfUHK/LfdYlQO9YgZvzxC7kqA3H/IhJXNqQgnbgjb7A==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.14.2", + "@smithy/types": "^4.15.0", "tslib": "^2.6.2" }, "engines": { @@ -4859,13 +4822,13 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.5.tgz", - "integrity": "sha512-yiF8xHpdkaTfzLVqFzsP6WvNghEK+qZzLYWFD13L2SsFhbXwBGlxdocKF95qjr7s5lE5NRage+EJFK4mAsx88Q==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.0.tgz", + "integrity": "sha512-pPQmNdEvMJttv9z2kdYxoui83p/nr32zjMf0aMfmzmGmFEgKXUfy0vXiNg0fx4R5XLQzmJBLM9Wg0guEq2/q8A==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.5", - "@smithy/types": "^4.14.2", + "@smithy/core": "^3.25.0", + "@smithy/types": "^4.15.0", "tslib": "^2.6.2" }, "engines": { @@ -4873,13 +4836,13 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.5.tgz", - "integrity": "sha512-SK3VMeH0fibgdTg2QeB+O4p7Yy/2E5HBOHJeC58FshkDdeuX8lOgO7PfjYfLyPLP1ch55j91cQqKBzDS0mRjSQ==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.5.0.tgz", + "integrity": "sha512-OG8kBYAgX7lf32+xLzgirvuLffn1KNoszaSiButt45i2cRa5irk8LQXLYQ5Smij1SBTN4KMNcBsRwRrLPfIGyA==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.5", - "@smithy/types": "^4.14.2", + "@smithy/core": "^3.25.0", + "@smithy/types": "^4.15.0", "tslib": "^2.6.2" }, "engines": { @@ -4899,13 +4862,13 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.5.tgz", - "integrity": "sha512-3dA9TQ+ybRSZ/m0wnbZhiBy4Dezjgq1Ib/ZZrYTpJDBgpoLLU/SDzZc/g0x0MNAdOJe1wPcM+x2PBRmoOur+Sw==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.8.0.tgz", + "integrity": "sha512-Mq7TNt/VhlEWiYRLQGpzUWeUxh899UGpjKh7Ru0WVIDIjnE+cTRAn0NYlFQ6bWfsQnKnpCbWJj86HzmcG0qEdg==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.5", - "@smithy/types": "^4.14.2", + "@smithy/core": "^3.25.0", + "@smithy/types": "^4.15.0", "tslib": "^2.6.2" }, "engines": { @@ -4913,13 +4876,13 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.4.5.tgz", - "integrity": "sha512-QBJKWGqIknH0dc9LWpfH1mkdokAx6iXYN3UcQ3eY6uIEyScuoQAhfl94ge7ozUy9WgFUdE8xsvwBjaYBbWmPNA==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.5.0.tgz", + "integrity": "sha512-vW6UdK7e7gV2wU/tXRsPq4pMQMusb8VymdVOyIFNA1FtyRmEClRFkYDtYI8UcO/HM0wK3qqjvvQs3HOlbgMbdg==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.5", - "@smithy/types": "^4.14.2", + "@smithy/core": "^3.25.0", + "@smithy/types": "^4.15.0", "tslib": "^2.6.2" }, "engines": { @@ -4927,9 +4890,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.14.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz", - "integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.15.0.tgz", + "integrity": "sha512-Z5TAOxygoFvybJV3igo5SloFflSokHx2hu1eFA+DxDTcn+FtKxUSui+rbTRG1pAafMA888Z3MVvCWUuvCrTXjg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -5395,12 +5358,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.12.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz", - "integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==", + "version": "24.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", + "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/node-fetch": { @@ -5582,17 +5545,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.0.tgz", - "integrity": "sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.1.tgz", + "integrity": "sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.60.0", - "@typescript-eslint/type-utils": "8.60.0", - "@typescript-eslint/utils": "8.60.0", - "@typescript-eslint/visitor-keys": "8.60.0", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/type-utils": "8.61.1", + "@typescript-eslint/utils": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -5605,22 +5568,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.60.0", + "@typescript-eslint/parser": "^8.61.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.0.tgz", - "integrity": "sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.1.tgz", + "integrity": "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.60.0", - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/typescript-estree": "8.60.0", - "@typescript-eslint/visitor-keys": "8.60.0", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", "debug": "^4.4.3" }, "engines": { @@ -5636,14 +5599,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.0.tgz", - "integrity": "sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.1.tgz", + "integrity": "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.60.0", - "@typescript-eslint/types": "^8.60.0", + "@typescript-eslint/tsconfig-utils": "^8.61.1", + "@typescript-eslint/types": "^8.61.1", "debug": "^4.4.3" }, "engines": { @@ -5658,14 +5621,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.0.tgz", - "integrity": "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.1.tgz", + "integrity": "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/visitor-keys": "8.60.0" + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5676,9 +5639,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.0.tgz", - "integrity": "sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.1.tgz", + "integrity": "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==", "dev": true, "license": "MIT", "engines": { @@ -5693,15 +5656,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.0.tgz", - "integrity": "sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.1.tgz", + "integrity": "sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/typescript-estree": "8.60.0", - "@typescript-eslint/utils": "8.60.0", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/utils": "8.61.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -5718,9 +5681,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz", - "integrity": "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.1.tgz", + "integrity": "sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==", "dev": true, "license": "MIT", "engines": { @@ -5732,16 +5695,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.0.tgz", - "integrity": "sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.1.tgz", + "integrity": "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.60.0", - "@typescript-eslint/tsconfig-utils": "8.60.0", - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/visitor-keys": "8.60.0", + "@typescript-eslint/project-service": "8.61.1", + "@typescript-eslint/tsconfig-utils": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -5760,16 +5723,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.0.tgz", - "integrity": "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.1.tgz", + "integrity": "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.60.0", - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/typescript-estree": "8.60.0" + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5784,13 +5747,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.0.tgz", - "integrity": "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.1.tgz", + "integrity": "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/types": "8.61.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -6015,9 +5978,9 @@ } }, "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -6264,6 +6227,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/anynum": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.0.tgz", + "integrity": "sha512-xjR9/zBVnUOP6ztMIIgShjsxui80nQUQH+5xJnvrYLs+90bF25/KJqaAi8mk+B4RDtX1Nspi6fmp4YTEts8SfA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/app-root-path": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", @@ -6370,9 +6345,9 @@ } }, "node_modules/axios": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", - "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.0.tgz", + "integrity": "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.16.0", @@ -6553,9 +6528,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.32", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz", - "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==", + "version": "2.10.37", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz", + "integrity": "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==", "dev": true, "license": "Apache-2.0", "bin": { @@ -6653,20 +6628,20 @@ } }, "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^1.0.5", + "content-type": "^2.0.0", "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" }, "engines": { "node": ">=18" @@ -6676,6 +6651,19 @@ "url": "https://opencollective.com/express" } }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/bowser": { "version": "2.14.1", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", @@ -6903,9 +6891,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001793", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", - "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", "dev": true, "funding": [ { @@ -7366,15 +7354,6 @@ "typedarray": "^0.0.6" } }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, "node_modules/content-disposition": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", @@ -8022,9 +8001,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.364", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.364.tgz", - "integrity": "sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==", + "version": "1.5.375", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.375.tgz", + "integrity": "sha512-ZWP5eB4BVPW/ZYo9252hQZHZ5XavtsTgpbhcmMmRwymavC5AsLWQWBPaKMeNd2LW0KGby5HPXvj7+sr4ta5j/Q==", "dev": true, "license": "ISC" }, @@ -8063,9 +8042,9 @@ } }, "node_modules/engine.io": { - "version": "6.6.8", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.8.tgz", - "integrity": "sha512-2agL3ueZhqxoVrfmntO8yuVj+uNSlIOnhykYHk3Cq0ShYPdUjjUiSJrQvXjq01I9jAuI0Zl2YO8Evv5Mqytm5g==", + "version": "6.6.9", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.9.tgz", + "integrity": "sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg==", "license": "MIT", "dependencies": { "@types/cors": "^2.8.12", @@ -8077,25 +8056,46 @@ "cors": "~2.8.5", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", - "ws": "~8.20.1" + "ws": "~8.21.0" }, "engines": { "node": ">=10.2.0" } }, "node_modules/engine.io-client": { - "version": "6.6.5", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.5.tgz", - "integrity": "sha512-QCwxUDULPlXv8F6tqMMKx5dNkTe6OaBYRMPYeXKBlyOoKvAmE0ac6pW7fFhSscJ/5SI7666/U/B+MElbsrJlIg==", + "version": "6.6.6", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.6.tgz", + "integrity": "sha512-iY6QdftLQ9pyiPoX082bpf/u1UewnOaJrtJIF9T0++QB34lZrj0uP+Q/bj8AlUsAxqhnkTV2BS8SBZSxOmoV5Q==", "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", - "ws": "~8.20.1", + "ws": "~8.21.0", "xmlhttprequest-ssl": "~2.1.1" } }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/engine.io-parser": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", @@ -8148,10 +8148,31 @@ "node": ">= 0.6" } }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/enhanced-resolve": { - "version": "5.22.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.22.1.tgz", - "integrity": "sha512-6QEuw3zoX1SJQc7b87aBXke/no+mG2bTBgw29gWMQonLmpEkWoCAVkl+M49e48AZlWzxiDzDZzYdp6kobcyLww==", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.0.tgz", + "integrity": "sha512-SkE2t82KlkkxQRVMVLAGKxLfORGQfrkx5dkj+vlgXRVNEdPc4eZcR+J/Fvj8C+yKSFH5L0q3NFlyufOVQnCcYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8305,18 +8326,21 @@ } }, "node_modules/eslint": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.0.tgz", - "integrity": "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.5.0.tgz", + "integrity": "sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ==", "dev": true, "license": "MIT", + "workspaces": [ + "packages/*" + ], "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.6.0", "@eslint/core": "^1.2.1", - "@eslint/plugin-kit": "^0.7.1", + "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -9173,16 +9197,16 @@ } }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -9442,9 +9466,9 @@ "license": "ISC" }, "node_modules/graphql": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.0.tgz", - "integrity": "sha512-BBvQ/406p+4CZbTpCbVPSxfzrZrbnuWSP1ELYgyS6B+hNeKzgrdB4JczCa5VZUBQrDa9hUngm0KnexY6pJRN5Q==", + "version": "16.14.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", + "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" @@ -9471,15 +9495,15 @@ } }, "node_modules/graphql-request/node_modules/form-data": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", - "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.5.tgz", + "integrity": "sha512-j23EibVLnp4zNXGW7LjryXYa2X6U/M96yoOX+ybZxwkYajdxRNEqYY3zhh7y0i6kfISKS2jr+EJq1YTUDEv5+w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", + "hasown": "^2.0.4", "mime-types": "^2.1.35" }, "engines": { @@ -9906,9 +9930,9 @@ "license": "ISC" }, "node_modules/ioredis": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.11.0.tgz", - "integrity": "sha512-EZBErytyVovD8f6pDfG3Kb37N6Y3lmDA9NNj+4+IP13CzzHGeX+OyeRM2Um13khRzoBSzzL+5lVnCX8V2RLeMg==", + "version": "5.11.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.11.1.tgz", + "integrity": "sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A==", "license": "MIT", "dependencies": { "@ioredis/commands": "1.10.0", @@ -10913,9 +10937,19 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -11159,9 +11193,9 @@ } }, "node_modules/libphonenumber-js": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.13.3.tgz", - "integrity": "sha512-xMkdAMqcyG7iN2WZZmGIfWbYxW4orRkny+0/AXIbwL0xll2zkDX0Vzo/BXFa6+7mh2UvJl9MbcTtHk0YXkFtBA==", + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.13.6.tgz", + "integrity": "sha512-NdB6O6QvlGMCoG003m0YIKG2+Xw7DjmCZhmc1RH+K6HncADUbRf8TZeLegxBBN1VFyPHcNpPTKpIhYLXzJVy1Q==", "license": "MIT" }, "node_modules/limiter": { @@ -11835,9 +11869,9 @@ } }, "node_modules/mysql2": { - "version": "3.22.4", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.22.4.tgz", - "integrity": "sha512-CtXYlmL7ZamiYKbmqkamQHWJROUHSfm+f3kByzGfknw7kW51mcB2ouMUqYq1XfYxbXmnWo6RhPydx6OCqdgcmQ==", + "version": "3.22.5", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.22.5.tgz", + "integrity": "sha512-95uZ2TrPWAZdwpB3vvvDbmEMcNG8yIeNCyu6GUcr/QnWEE/wXm7+mhOCsdQfWQDTV7qYT/PDUZ4U4UPP4AsXqQ==", "license": "MIT", "dependencies": { "aws-ssl-profiles": "^1.1.2", @@ -12021,9 +12055,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.46", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", - "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", "dev": true, "license": "MIT", "engines": { @@ -12628,9 +12662,9 @@ } }, "node_modules/pprof-format": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/pprof-format/-/pprof-format-2.2.1.tgz", - "integrity": "sha512-p4tVN7iK19ccDqQv8heyobzUmbHyds4N2FI6aBMcXz6y99MglTWDxIyhFkNaLeEXs6IFUEzT0zya0icbSLLY0g==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/pprof-format/-/pprof-format-2.2.2.tgz", + "integrity": "sha512-hd90rHVDhNOhgHTmazVzDSVwTLOBjpZQ26AO/0j46sAFZ9uSWY0DcK2zJcwnuo2R6EzufBbOoWlPazA5nSyHcg==", "license": "MIT" }, "node_modules/prelude-ls": { @@ -12644,9 +12678,9 @@ } }, "node_modules/prettier": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", - "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz", + "integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==", "dev": true, "license": "MIT", "bin": { @@ -12745,9 +12779,9 @@ "license": "MIT" }, "node_modules/protobufjs": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.1.tgz", - "integrity": "sha512-4K0myLaWL5EteuSAro91EGFgcfVgxb64Jx+7oDAY6GOkXD4M69yuSEljNcInGVCA5sOPxmZ/EqDLj2x0Q0+Ygg==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", + "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -12757,7 +12791,6 @@ "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", @@ -13420,9 +13453,9 @@ "license": "Apache-2.0" }, "node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -13597,14 +13630,14 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -13733,13 +13766,34 @@ } }, "node_modules/socket.io-adapter": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.7.tgz", - "integrity": "sha512-e0LyK91f3cUxTmv95/KzoLg47+zF+s/sbxRGDNsyG4dmIP8ZSX8ax6byOxfJXeNNtS/8AZlfD+uP7gBeR7DLlg==", + "version": "2.5.8", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.8.tgz", + "integrity": "sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw==", "license": "MIT", "dependencies": { "debug": "~4.4.1", - "ws": "~8.20.1" + "ws": "~8.21.0" + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/socket.io-client": { @@ -14078,16 +14132,19 @@ } }, "node_modules/strnum": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", - "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.0.tgz", + "integrity": "sha512-sHrVyWWdq28RbhjuJdZsA1SnGRJV6NiXbk6AXBxDOsgAcA+lmpUZCYjOdLBxkXMwis6RRe7dlZt4VlIWFVzkmg==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "anynum": "^1.0.0" + } }, "node_modules/strtok3": { "version": "10.3.5", @@ -14572,9 +14629,9 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { @@ -15233,9 +15290,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, "node_modules/universalify": { @@ -15375,13 +15432,12 @@ } }, "node_modules/watchpack": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", - "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", "dev": true, "license": "MIT", "dependencies": { - "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" }, "engines": { @@ -15574,9 +15630,9 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.21", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.21.tgz", - "integrity": "sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==", + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", From e62a498d9da4d13f233776de0c58097adc050a7b Mon Sep 17 00:00:00 2001 From: GuticaStefan Date: Wed, 17 Jun 2026 15:35:58 +0300 Subject: [PATCH 09/15] remove searchAfter from tags return type --- src/endpoints/nfttags/entities/tag.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/endpoints/nfttags/entities/tag.ts b/src/endpoints/nfttags/entities/tag.ts index bf0ff0b23..b2732bb11 100644 --- a/src/endpoints/nfttags/entities/tag.ts +++ b/src/endpoints/nfttags/entities/tag.ts @@ -10,7 +10,4 @@ export class Tag { @ApiProperty({ type: Number, nullable: true, example: 46135 }) count: number | undefined = undefined; - - @ApiProperty({ type: String, required: false }) - searchAfter?: string; } From 1ffd4a71c5206ef463af1e56c5e3cd31f5f2390b Mon Sep 17 00:00:00 2001 From: GuticaStefan Date: Wed, 17 Jun 2026 15:44:18 +0300 Subject: [PATCH 10/15] fix tag fields --- src/common/indexer/entities/tag.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/indexer/entities/tag.ts b/src/common/indexer/entities/tag.ts index b7e5a91a7..ba5894622 100644 --- a/src/common/indexer/entities/tag.ts +++ b/src/common/indexer/entities/tag.ts @@ -1,6 +1,6 @@ import { ElasticSortable } from "./elastic.sortable"; -export interface Tag extends ElasticSortable { +export interface Tag { count: number; tag: string; } From 8e54113b57d0edead17078e8026c94d6e1672fe0 Mon Sep 17 00:00:00 2001 From: GuticaStefan Date: Wed, 17 Jun 2026 15:44:37 +0300 Subject: [PATCH 11/15] remove unused import --- src/common/indexer/entities/tag.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/common/indexer/entities/tag.ts b/src/common/indexer/entities/tag.ts index ba5894622..c56f9077f 100644 --- a/src/common/indexer/entities/tag.ts +++ b/src/common/indexer/entities/tag.ts @@ -1,5 +1,3 @@ -import { ElasticSortable } from "./elastic.sortable"; - export interface Tag { count: number; tag: string; From 300dbec5dfc27b50b7bc473811a82e49e348a904 Mon Sep 17 00:00:00 2001 From: GuticaStefan Date: Fri, 19 Jun 2026 11:43:48 +0300 Subject: [PATCH 12/15] keep consistent swagger searchafter description --- src/endpoints/applications/application.controller.ts | 2 +- src/endpoints/miniblocks/mini.block.controller.ts | 2 +- src/endpoints/transactions/transaction.controller.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/endpoints/applications/application.controller.ts b/src/endpoints/applications/application.controller.ts index 085062acb..5a4b6f14b 100644 --- a/src/endpoints/applications/application.controller.ts +++ b/src/endpoints/applications/application.controller.ts @@ -19,7 +19,7 @@ export class ApplicationController { @ApiOkResponse({ type: [Application] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) - @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiQuery({ name: 'before', description: 'Before timestamp or timestampMs', required: false }) @ApiQuery({ name: 'after', description: 'After timestamp or timestampMs', required: false }) @ApiQuery({ name: 'withTxCount', description: 'Include transaction count', required: false, type: Boolean }) diff --git a/src/endpoints/miniblocks/mini.block.controller.ts b/src/endpoints/miniblocks/mini.block.controller.ts index 864b26b00..af4d6d34a 100644 --- a/src/endpoints/miniblocks/mini.block.controller.ts +++ b/src/endpoints/miniblocks/mini.block.controller.ts @@ -18,7 +18,7 @@ export class MiniBlockController { @ApiOkResponse({ type: [MiniBlockDetailed] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) - @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiQuery({ name: 'hashes', description: 'Filter by a comma-separated list of miniblocks hashes', required: false }) @ApiQuery({ name: 'type', description: 'Sorting criteria by type', required: false, enum: MiniBlockType }) async getMiniBlocks( diff --git a/src/endpoints/transactions/transaction.controller.ts b/src/endpoints/transactions/transaction.controller.ts index 1d460ed0b..bce14f12b 100644 --- a/src/endpoints/transactions/transaction.controller.ts +++ b/src/endpoints/transactions/transaction.controller.ts @@ -42,7 +42,7 @@ export class TransactionController { @ApiQuery({ name: 'fields', description: 'List of fields to filter by', required: false, isArray: true, style: 'form', explode: false }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) - @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiQuery({ name: 'condition', description: 'Condition for elastic search queries', required: false, deprecated: true }) @ApiQuery({ name: 'withScResults', description: 'Return results for transactions. When "withScResults" parameter is applied, complexity estimation is 200', required: false, type: Boolean }) @ApiQuery({ name: 'withOperations', description: 'Return operations for transactions. When "withOperations" parameter is applied, complexity estimation is 200', required: false, type: Boolean }) From 9db59539a3d8429d886b8c9415f0a40244a7a7c2 Mon Sep 17 00:00:00 2001 From: GuticaStefan Date: Fri, 19 Jun 2026 11:44:10 +0300 Subject: [PATCH 13/15] add validation for rounds filter --- src/endpoints/rounds/entities/round.filter.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/endpoints/rounds/entities/round.filter.ts b/src/endpoints/rounds/entities/round.filter.ts index 51c64a15b..08c0187d4 100644 --- a/src/endpoints/rounds/entities/round.filter.ts +++ b/src/endpoints/rounds/entities/round.filter.ts @@ -1,10 +1,14 @@ import { QueryConditionOptions } from "@multiversx/sdk-nestjs-elastic"; +import { BadRequestException } from "@nestjs/common"; import { QueryPagination } from "src/common/entities/query.pagination"; export class RoundFilter extends QueryPagination { constructor(init?: Partial) { super(); Object.assign(this, init); + if (this.searchAfter !== undefined && this.from !== 0) { + throw new BadRequestException("'from' must be 0 when 'searchAfter' is provided."); + } } condition: QueryConditionOptions | undefined = QueryConditionOptions.must; From cc72095d2c2e69c89e66fbf381dd2e843daee37e Mon Sep 17 00:00:00 2001 From: GuticaStefan Date: Fri, 19 Jun 2026 12:01:44 +0300 Subject: [PATCH 14/15] add round filter and query pagination unit tests --- src/test/unit/utils/query.pagination.spec.ts | 48 +++++++++++++++ src/test/unit/utils/round.filter.spec.ts | 63 ++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 src/test/unit/utils/query.pagination.spec.ts create mode 100644 src/test/unit/utils/round.filter.spec.ts diff --git a/src/test/unit/utils/query.pagination.spec.ts b/src/test/unit/utils/query.pagination.spec.ts new file mode 100644 index 000000000..2d5b0a334 --- /dev/null +++ b/src/test/unit/utils/query.pagination.spec.ts @@ -0,0 +1,48 @@ +import { BadRequestException } from "@nestjs/common"; +import { QueryPagination } from "src/common/entities/query.pagination"; + +describe('QueryPagination', () => { + + it('should initialize with default values', () => { + const pagination = new QueryPagination(); + + expect(pagination.from).toBe(0); + expect(pagination.size).toBe(25); + expect(pagination.before).toBeUndefined(); + expect(pagination.after).toBeUndefined(); + expect(pagination.searchAfter).toBeUndefined(); + }); + + it('should allow overriding default values (without searchAfter)', () => { + const pagination = new QueryPagination({ from: 10, size: 50 }); + + expect(pagination.from).toBe(10); + expect(pagination.size).toBe(50); + }); + + it('should be valid when searchAfter is provided and from is 0', () => { + const pagination = new QueryPagination({ searchAfter: 'cursor-xyz', from: 0 }); + + expect(pagination.searchAfter).toBe('cursor-xyz'); + expect(pagination.from).toBe(0); + }); + + it('should be valid when searchAfter is provided and from is omitted (defaults to 0)', () => { + const pagination = new QueryPagination({ searchAfter: 'cursor-xyz' }); + + expect(pagination.searchAfter).toBe('cursor-xyz'); + expect(pagination.from).toBe(0); // Checks that the default value kicks in safely + }); + + it('should throw BadRequestException if searchAfter is provided and from is not 0', () => { + // Asserting that it throws the correct exception type + expect(() => { + new QueryPagination({ searchAfter: 'cursor-xyz', from: 5 }); + }).toThrow(BadRequestException); + + // Optional: Asserting the exact error message + expect(() => { + new QueryPagination({ searchAfter: 'cursor-xyz', from: 5 }); + }).toThrow("'from' must be 0 when 'searchAfter' is provided."); + }); +}); diff --git a/src/test/unit/utils/round.filter.spec.ts b/src/test/unit/utils/round.filter.spec.ts new file mode 100644 index 000000000..3ebe54de4 --- /dev/null +++ b/src/test/unit/utils/round.filter.spec.ts @@ -0,0 +1,63 @@ +import { BadRequestException } from "@nestjs/common"; +import { QueryConditionOptions } from "@multiversx/sdk-nestjs-elastic"; +import { RoundFilter } from "src/endpoints/rounds/entities/round.filter"; + +describe('RoundFilter', () => { + + it('should initialize with default values including inherited pagination properties', () => { + const filter = new RoundFilter(); + + // Inherited from QueryPagination + expect(filter.from).toBe(0); + expect(filter.size).toBe(25); + expect(filter.searchAfter).toBeUndefined(); + + // RoundFilter specific defaults + expect(filter.condition).toBe(QueryConditionOptions.must); + expect(filter.validator).toBeUndefined(); + expect(filter.shard).toBeUndefined(); + expect(filter.epoch).toBeUndefined(); + }); + + it('should correctly assign custom filter and pagination properties', () => { + const filter = new RoundFilter({ + from: 10, + size: 50, + validator: 'erd1testvalidatoraddress...', + shard: 1, + epoch: 500, + condition: QueryConditionOptions.should + }); + + expect(filter.from).toBe(10); + expect(filter.size).toBe(50); + expect(filter.validator).toBe('erd1testvalidatoraddress...'); + expect(filter.shard).toBe(1); + expect(filter.epoch).toBe(500); + expect(filter.condition).toBe(QueryConditionOptions.should); + }); + + it('should be valid when searchAfter is provided and from is 0', () => { + const filter = new RoundFilter({ searchAfter: 'shard-cursor-abc', from: 0 }); + + expect(filter.searchAfter).toBe('shard-cursor-abc'); + expect(filter.from).toBe(0); + }); + + it('should be valid when searchAfter is provided and from is omitted', () => { + const filter = new RoundFilter({ searchAfter: 'shard-cursor-abc' }); + + expect(filter.searchAfter).toBe('shard-cursor-abc'); + expect(filter.from).toBe(0); // Falls back to default 0 + }); + + it('should throw BadRequestException if searchAfter is provided and from is not 0', () => { + expect(() => { + new RoundFilter({ searchAfter: 'shard-cursor-abc', from: 42 }); + }).toThrow(BadRequestException); + + expect(() => { + new RoundFilter({ searchAfter: 'shard-cursor-abc', from: 42 }); + }).toThrow("'from' must be 0 when 'searchAfter' is provided."); + }); +}); From 0a1335c6e90a5f1190853b892d8f62c4919af207 Mon Sep 17 00:00:00 2001 From: GuticaStefan Date: Tue, 23 Jun 2026 11:24:53 +0300 Subject: [PATCH 15/15] sort accounts by address keyword --- src/common/indexer/elastic/elastic.indexer.service.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/indexer/elastic/elastic.indexer.service.ts b/src/common/indexer/elastic/elastic.indexer.service.ts index 03fb64c00..daecaf9c1 100644 --- a/src/common/indexer/elastic/elastic.indexer.service.ts +++ b/src/common/indexer/elastic/elastic.indexer.service.ts @@ -487,7 +487,7 @@ export class ElasticIndexerService implements IndexerInterface { case AccountSort.balance: elasticQuery = elasticQuery.withSort([ { name: 'balanceNum', order: sortOrder }, - { name: 'address', order: sortOrder }, + { name: 'address.keyword', order: sortOrder }, { name: 'timestamp', order: sortOrder }, { name: 'timestampMs', order: sortOrder, missing: 0 }, ]); @@ -496,14 +496,14 @@ export class ElasticIndexerService implements IndexerInterface { if (this.apiConfigService.getAccountExtraDetailsTransfersLast24hUrl()) { elasticQuery = elasticQuery.withSort([ { name: 'api_transfersLast24h', order: sortOrder }, - { name: 'address', order: sortOrder }, + { name: 'address.keyword', order: sortOrder }, ]); } else { elasticQuery = elasticQuery .withSort([ { name: 'timestamp', order: sortOrder }, { name: 'timestampMs', order: sortOrder, missing: 0 }, - { name: 'address', order: sortOrder }, + { name: 'address.keyword', order: sortOrder }, ]) .withMustExistCondition('currentOwner'); } @@ -513,7 +513,7 @@ export class ElasticIndexerService implements IndexerInterface { elasticQuery = elasticQuery.withSort([ { name: 'timestamp', order: sortOrder }, { name: 'timestampMs', order: sortOrder, missing: 0 }, - { name: 'address', order: sortOrder }, + { name: 'address.keyword', order: sortOrder }, ]); break; }