このページの翻訳は LLM によって生成されています。誤りに気づいた場合は GitHub で issue を開いてお知らせください。
フィルターとページネーション
絞り込み、ページ分割、並べ替えを使った高度なクエリを学びます。
フィルターの型
Section titled “フィルターの型”データ型に応じたフィルターに対応します。
文字列フィルター
Section titled “文字列フィルター”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%" }})整数フィルター
Section titled “整数フィルター”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 }})浮動小数点フィルター
Section titled “浮動小数点フィルター”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)時刻フィルター
Section titled “時刻フィルター”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)列挙型フィルター
Section titled “列挙型フィルター”列挙値で絞り込みます。
例:手数料種類
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')ページネーション
Section titled “ページネーション”基本のページ分割
Section titled “基本のページ分割”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)複数ページから全件取得します。
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はoffset方式ですが、カーソル式の処理も実装できます。
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)無限スクロール
Section titled “無限スクロール”UIに無限スクロールを実装します。
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) }})並べ替えの方向
Section titled “並べ替えの方向”多くのクエリは 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順です。
並べ替えの例
Section titled “並べ替えの例”新しい順:
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 } } } `})応用パターン
Section titled “応用パターン”フィルターの組合せ
Section titled “フィルターの組合せ”複数フィルターで正確に絞り込みます。
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)}