本页面的翻译由 LLM 生成。如果你发现翻译有误,请在 GitHub 上提交 issue 告诉我们。
筛选与分页
学习筛选、分页、排序等进阶查询模式。
Keeper 为各数据类型提供不同筛选器。
StringFilter 支持相等和模式匹配。
input StringFilter { eq: String # Exact match like: String # Pattern match}示例:精确匹配
query SearchToken { oracle { tokens(where: { name: { eq: "Bitcoin" } }) { id symbol name } }}示例:模式匹配
query SearchByPattern { oracle { tokens(where: { name: { like: "%coin%" } }) { symbol name } }}JavaScript 用法:
// Exact matchconst { data } = await client.query({ query: gql` query SearchToken($name: String!) { oracle { tokens(where: { name: $name }) { symbol name } } } `, variables: { name: "Bitcoin" }})
// Pattern searchconst searchResults = await client.query({ query: gql` query SearchTokens($pattern: String!) { oracle { tokens(where: { name: $pattern }) { symbol name } } } `, variables: { pattern: "%BTC%" }})IntFilter 支持比较运算。
input IntFilter { eq: Int # Equal to gt: Int # Greater than gte: Int # Greater than or equal lt: Int # Less than lte: Int # Less than or equal}示例:费用金额范围
query GetLargeFees { fee { feeTransactions( filter: { minAmount: 1000000 # >= 1,000,000 maxAmount: 10000000 # <= 10,000,000 } ) { id totalFeeCharged traderAddress } }}JavaScript 用法:
const { data } = await client.query({ query: gql` query GetFeesInRange($min: Int!, $max: Int!) { fee { feeTransactions( filter: { minAmount: $min maxAmount: $max } ) { id totalFeeCharged } } } `, variables: { min: 1000000, max: 10000000 }})FloatFilter 用于浮点比较。
input FloatFilter { eq: Float # Equal to gt: Float # Greater than gte: Float # Greater than or equal lt: Float # Less than lte: Float # Less than or equal}示例:按杠杆筛选
// Note: Direct float filters not used in current schema,// but filter objects use similar patternsconst highLeverageTrades = await client.query({ query: gql` query GetHighLeverageTrades($trader: String!) { perp { trades(where: { trader: $trader }) { id leverage isOpen } } } `, variables: { trader: "nibi1abc..." }})
// Filter client-sideconst filtered = highLeverageTrades.data.perp.trades.filter( trade => trade.leverage > 10)TimeFilter 用于日期时间范围查询。
input TimeFilter { eq: Time # Exact time gt: Time # After gte: Time # After or at lt: Time # Before lte: Time # Before or at}示例:按日期范围筛选
query GetFeesLastMonth { fee { feeTransactions( filter: { fromDate: "2024-10-01T00:00:00Z" toDate: "2024-10-31T23:59:59Z" } limit: 100 ) { id totalFeeCharged blockTime } }}JavaScript 用法:
// Get fees for last 30 daysconst thirtyDaysAgo = new Date()thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)
const { data } = await client.query({ query: gql` query GetRecentFees($fromDate: Time!, $toDate: Time!) { fee { feeTransactions( filter: { fromDate: $fromDate toDate: $toDate } limit: 100 ) { id totalFeeCharged blockTime } } } `, variables: { fromDate: thirtyDaysAgo.toISOString(), toDate: new Date().toISOString() }})辅助函数:
function getDateRange(days) { const toDate = new Date() const fromDate = new Date() fromDate.setDate(fromDate.getDate() - days)
return { fromDate: fromDate.toISOString(), toDate: toDate.toISOString() }}
// Usageconst last7Days = getDateRange(7)const last30Days = getDateRange(30)const last90Days = getDateRange(90)按枚举值筛选。
示例:费用类型筛选
query GetOpeningFees($trader: String!) { fee { feeTransactions( filter: { traderAddress: $trader feeType: OPENING } limit: 50 ) { id feeType totalFeeCharged } }}示例:交易类型筛选
// Get only limit ordersconst { data } = await client.query({ query: gql` query GetLimitOrders($trader: String!) { perp { tradeHistory(where: { trader: $trader }, limit: 100) { id tradeChangeType trade { id tradeType } } } } `, variables: { trader: "nibi1abc..." }})
// Filter for limit_order_created eventsconst limitOrders = data.perp.tradeHistory.filter( h => h.tradeChangeType === 'limit_order_created')使用 limit 和 offset 分页。
query GetTradesPaginated($trader: String!, $limit: Int!, $offset: Int!) { perp { trades( where: { trader: $trader } limit: $limit offset: $offset ) { id isOpen leverage } }}JavaScript 实现:
async function fetchPage(trader, page = 0, pageSize = 20) { const { data } = await client.query({ query: GET_TRADES_PAGINATED, variables: { trader, limit: pageSize, offset: page * pageSize } })
return data.perp.trades}
// Get first pageconst firstPage = await fetchPage("nibi1abc...", 0, 20)
// Get second pageconst secondPage = await fetchPage("nibi1abc...", 1, 20)
// Get third pageconst thirdPage = await fetchPage("nibi1abc...", 2, 20)获取全部结果
Section titled “获取全部结果”跨多个页面获取全部结果。
async function fetchAllTrades(trader, pageSize = 100) { let allTrades = [] let page = 0 let hasMore = true
while (hasMore) { const { data } = await client.query({ query: gql` query GetTrades($trader: String!, $limit: Int!, $offset: Int!) { perp { trades( where: { trader: $trader } limit: $limit offset: $offset ) { id isOpen leverage } } } `, variables: { trader, limit: pageSize, offset: page * pageSize } })
const trades = data.perp.trades allTrades = allTrades.concat(trades)
hasMore = trades.length === pageSize page++
console.log(`Fetched page ${page}, total: ${allTrades.length}`) }
return allTrades}
// Usageconst allTrades = await fetchAllTrades("nibi1abc...")console.log(`Total trades: ${allTrades.length}`)游标分页替代方案
Section titled “游标分页替代方案”Keeper 使用偏移量分页,但客户端可实现游标逻辑。
async function fetchTradesAfterBlock(trader, afterBlock = 0, limit = 100) { const { data } = await client.query({ query: gql` query GetTrades($trader: String!, $limit: Int!) { perp { trades( where: { trader: $trader } limit: $limit order_desc: false ) { id openBlock { block } } } } `, variables: { trader, limit } })
// Filter trades after cursor block const filtered = data.perp.trades.filter( trade => trade.openBlock.block > afterBlock )
return filtered}
// Get first batchconst firstBatch = await fetchTradesAfterBlock("nibi1abc...", 0)const lastBlock = firstBatch[firstBatch.length - 1].openBlock.block
// Get next batchconst nextBatch = await fetchTradesAfterBlock("nibi1abc...", lastBlock)在界面实现无限滚动。
class TradesPaginator { constructor(client, trader, pageSize = 20) { this.client = client this.trader = trader this.pageSize = pageSize this.currentPage = 0 this.allTrades = [] this.hasMore = true }
async loadMore() { if (!this.hasMore) { return [] }
const { data } = await this.client.query({ query: gql` query GetTrades($trader: String!, $limit: Int!, $offset: Int!) { perp { trades( where: { trader: $trader } limit: $limit offset: $offset order_desc: true ) { id isOpen leverage } } } `, variables: { trader: this.trader, limit: this.pageSize, offset: this.currentPage * this.pageSize } })
const trades = data.perp.trades this.allTrades = this.allTrades.concat(trades) this.hasMore = trades.length === this.pageSize this.currentPage++
return trades }
reset() { this.currentPage = 0 this.allTrades = [] this.hasMore = true }
getAll() { return this.allTrades }}
// Usageconst paginator = new TradesPaginator(client, "nibi1abc...", 20)
// Load first pageconst firstPage = await paginator.loadMore()
// Load more on scrollwindow.addEventListener('scroll', async () => { if (isNearBottom() && paginator.hasMore) { const nextPage = await paginator.loadMore() appendToUI(nextPage) }})多数查询支持 order_by 和 order_desc。
query GetTradesOrdered($trader: String!) { perp { trades( where: { trader: $trader } order_by: sequence order_desc: true # Descending (newest first) ) { id openBlock { block_ts } } }}各领域排序选项:
合约交易:
sequence:按序号排序。
交易历史:
sequence:按序号。trade_id:按交易 ID。
LP 存款:
depositor:按存款人地址。vault:按金库地址。
LP 存款历史:
depositor:按存款人。sequence:按序号。vault:按金库。
LP 提现申请:
depositor:按存款人。unlock_epoch:按解锁周期。vault:按金库。
预言机代币:
id:按代币 ID。name:按名称。permission_group:按权限组。
费用交易:
- 无显式排序参数,按 ID 排序。
最新优先:
const { data } = await client.query({ query: gql` query GetRecentTrades($trader: String!) { perp { trades( where: { trader: $trader } order_desc: true limit: 20 ) { id openBlock { block_ts } } } } `, variables: { trader: "nibi1abc..." }})最早优先:
const { data } = await client.query({ query: gql` query GetOldestTrades($trader: String!) { perp { trades( where: { trader: $trader } order_desc: false limit: 20 ) { id openBlock { block_ts } } } } `, variables: { trader: "nibi1abc..." }})按代币名称:
const { data } = await client.query({ query: gql` query GetTokensAlphabetically { oracle { tokens( order_by: name order_desc: false ) { symbol name } } } `})组合多个筛选条件进行精确查询。
const { data } = await client.query({ query: gql` query GetFilteredFees( $trader: String! $feeType: FeeType! $fromDate: Time! $toDate: Time! $minAmount: Int! ) { fee { feeTransactions( filter: { traderAddress: $trader feeType: $feeType fromDate: $fromDate toDate: $toDate minAmount: $minAmount } limit: 100 ) { id totalFeeCharged blockTime } } } `, variables: { trader: "nibi1abc...", feeType: "OPENING", fromDate: "2024-01-01T00:00:00Z", toDate: "2024-12-31T23:59:59Z", minAmount: 1000000 }})客户端二次筛选
Section titled “客户端二次筛选”服务端不支持的筛选可在客户端执行。
// Fetch all tradesconst { data } = await client.query({ query: gql` query GetAllTrades($trader: String!) { perp { trades(where: { trader: $trader }, limit: 1000) { id leverage isLong isOpen collateralAmount state { pnlPct } perpBorrowing { baseToken { symbol } } } } } `, variables: { trader: "nibi1abc..." }})
// Filter for high-leverage profitable longsconst filtered = data.perp.trades.filter(trade => trade.isLong && trade.leverage > 10 && trade.state.pnlPct > 0.1 && // 10% profit trade.isOpen)
// Group by tokenconst byToken = filtered.reduce((acc, trade) => { const symbol = trade.perpBorrowing.baseToken.symbol if (!acc[symbol]) acc[symbol] = [] acc[symbol].push(trade) return acc}, {})
console.log('Profitable high-leverage longs by token:', byToken)构建搜索功能。
async function searchTrades(trader, searchTerm) { // Fetch all trades const { data } = await client.query({ query: gql` query GetTrades($trader: String!) { perp { trades(where: { trader: $trader }) { id isLong leverage perpBorrowing { baseToken { symbol name } marketId } } } } `, variables: { trader } })
// Search by token symbol or name const searchLower = searchTerm.toLowerCase() return data.perp.trades.filter(trade => { const symbol = trade.perpBorrowing.baseToken.symbol.toLowerCase() const name = trade.perpBorrowing.baseToken.name.toLowerCase() return symbol.includes(searchLower) || name.includes(searchLower) })}
// Usageconst btcTrades = await searchTrades("nibi1abc...", "bitcoin")const ethTrades = await searchTrades("nibi1abc...", "eth")1. 使用适当分页大小
Section titled “1. 使用适当分页大小”// Good: Reasonable page sizeconst PAGE_SIZE = 50
// Bad: Too large, slow queriesconst PAGE_SIZE = 10000
// Bad: Too small, too many requestsconst PAGE_SIZE = 5建议分页大小:
- 交易:20 至 100。
- 费用交易:50 至 100。
- 历史:50 至 200。
- 代币:100 至 500。
2. 缓存分页结果
Section titled “2. 缓存分页结果”class CachedPaginator { constructor(client, query, variables) { this.client = client this.query = query this.variables = variables this.cache = new Map() }
async getPage(page, pageSize = 20) { const cacheKey = `${page}-${pageSize}`
if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey) }
const { data } = await this.client.query({ query: this.query, variables: { ...this.variables, limit: pageSize, offset: page * pageSize } })
this.cache.set(cacheKey, data) return data }
clearCache() { this.cache.clear() }}3. 处理空结果
Section titled “3. 处理空结果”async function fetchTradesWithEmptyCheck(trader, page = 0) { const { data } = await client.query({ query: GET_TRADES, variables: { trader, limit: 20, offset: page * 20 } })
const trades = data.perp.trades
if (trades.length === 0) { if (page === 0) { console.log('No trades found for this trader') } else { console.log('No more trades to load') } }
return trades}4. 对筛选变更防抖
Section titled “4. 对筛选变更防抖”import { debounce } from 'lodash'
const debouncedSearch = debounce(async (searchTerm) => { const results = await searchTrades(trader, searchTerm) updateUI(results)}, 300) // Wait 300ms after user stops typing
// Usage in Reactfunction SearchInput() { const handleChange = (e) => { debouncedSearch(e.target.value) }
return <input onChange={handleChange} />}5. 显示加载状态
Section titled “5. 显示加载状态”async function loadTradesWithUI(trader, page) { try { showLoadingSpinner()
const trades = await fetchPage(trader, page)
if (trades.length === 0) { showEmptyState() } else { renderTrades(trades) } } catch (error) { showErrorState(error) } finally { hideLoadingSpinner() }}6. 验证筛选输入
Section titled “6. 验证筛选输入”function validateDateRange(fromDate, toDate) { const from = new Date(fromDate) const to = new Date(toDate)
if (isNaN(from.getTime()) || isNaN(to.getTime())) { throw new Error('Invalid date format') }
if (from > to) { throw new Error('From date must be before to date') }
const maxRange = 90 * 24 * 60 * 60 * 1000 // 90 days if (to - from > maxRange) { throw new Error('Date range too large (max 90 days)') }
return { fromDate, toDate }}
// Usagetry { const { fromDate, toDate } = validateDateRange( userInputFrom, userInputTo )
const { data } = await client.query({ query: GET_FEES, variables: { fromDate, toDate } })} catch (error) { showError(error.message)}