Query module
The query module facilitates interactions with Joystream GraphQL APIs exposed by:
It allows executing queries against those APIs in a fully type-safe way, without the need for any additional complex setup.
This is possible thanks to GenQL which generates a TypeScript GraphQL client from a GraphQL schema.
Create query API
- Query node
- Orion
- Storage squid
import { QueryNodeApi } from "@joystream/sdk-core/query/queryNode";
const qnApi = new QueryNodeApi("https://mainnet.joystream.dev/query/graphql");
import { OrionApi } from "@joystream/sdk-core/query/orion";
const orionApi = new OrionApi("https://mainnet.joystream.dev/orion/graphql");
import { StorageSquidApi } from '@joystream/sdk-core/query/storageSquid';
const storageSquidApi = new StorageSquidApi("https://mainnet.joystream.dev/storage/squid/graphql");
Configuration
All QueryApi
s (ie. QueryNodeApi
, OrionApi
, StorageSquidApi
), accept an optional configuration object
as a second argument to their constructors (after url):
export type Config = {
// Maximum size of an array of inputs to a single query
// (for example, max. chunk size of ids in `query.ENTITY.byIds`)
// Default: 1000
inputBatchSize: number
// Maximum number of results to fetch in a single query
// Default: 1000
resultsPerQueryLimit: number
// Maximum number of requests that can be sent concurrently to GraphQL server
// Default: 20
concurrentRequestsLimit: number
// Additional GenQL client options
clientOptions?: ClientOptions
}
Execute queries
Get entity by id
Queries an entity by its ID.
Syntax
qApi.query.ENTITY_NAME.byId(ID) // Selects all scalar fields qApi.query.ENTITY_NAME.byId(ID, SELECTION) // Selects specified fields
Examples
- Query node
- Orion
- Storage squid
import { SnippetParams } from '../../snippet'
export default async function ({ qnApi, log }: SnippetParams) {
// @snippet-begin
// Get all scalar fields of member by id=336
const member = await qnApi.query.Membership.byId('336')
log(member)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ qnApi, log }: SnippetParams) {
// @snippet-begin
// Get proposal by id=9, along with some of its details
const proposal = await qnApi.query.Proposal.byId('9', {
__scalar: true, // Retrieve all scalar fields of Proposal
details: {
__typename: true,
on_CreateWorkingGroupLeadOpeningProposalDetails: {
group: {
name: true,
},
},
// ...handle other types if needed
},
})
log(proposal)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ qnApi, log }: SnippetParams) {
// @snippet-begin
// Get election by id, along with candidating members and their vote power
const election = await qnApi.query.ElectionRound.byId('00000014', {
__scalar: true, // Retrieve all scalar fields of ElectionRound
candidates: {
member: { id: true, handle: true },
votePower: true,
},
})
log(election)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ orionApi, log }: SnippetParams) {
// @snippet-begin
// Get all scalar fields of channel by id=1
const channel = await orionApi.query.Channel.byId('1')
log(channel)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ orionApi, log }: SnippetParams) {
// @snippet-begin
// Get channel by id, along with those of its videos which are longer than 1 hour
const channelWithVideos = await orionApi.query.Channel.byId('7692', {
__scalar: true, // Get all scalar fields of Channel
videos: {
__args: { where: { duration_gt: 3600 } },
__scalar: true, // Get all scalar fields of Video
},
})
log(channelWithVideos)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ orionApi, log }: SnippetParams) {
// @snippet-begin
// Get specific fields of a video by id=1
const video = await orionApi.query.Video.byId('1', {
id: true,
title: true,
duration: true,
category: {
name: true,
},
channel: {
title: true,
},
})
log(video)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ storageSquidApi, log }: SnippetParams) {
// @snippet-begin
// Get all scalar fields of storage bucket by id=0
const storageBucket = await storageSquidApi.query.StorageBucket.byId('0')
log(storageBucket)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ storageSquidApi, log }: SnippetParams) {
// @snippet-begin
// Get storage bag of channel 1, along with some information about its data objects
const storageBag = await storageSquidApi.query.StorageBag.byId(
'dynamic:channel:1',
{
__scalar: true,
objects: {
id: true,
ipfsHash: true,
size: true,
},
}
)
log(storageBag)
// @snippet-end
}
Get first result
Retrieves first entity matching provided conditions.
Syntax
qApi.query.ENTITY_NAME.first({ where: WHERE_ARGS, select: SELECTION, // Optional, by default all scalar fields are selected orderBy: ORDER_BY_LIST // Optional })
Examples
- Query node
- Orion
- Storage squid
import { SnippetParams } from '../../snippet'
export default async function ({ qnApi, log }: SnippetParams) {
// @snippet-begin
// Get all scalar fields of a member by their handle
const member = await qnApi.query.Membership.first({
where: { handle_eq: 'lezek' },
})
log(member)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ qnApi, log }: SnippetParams) {
// @snippet-begin
// Get id, handle and totalChannelsCreated of a member with highest number of channels created
const member = await qnApi.query.Membership.first({
select: { id: true, handle: true, totalChannelsCreated: true },
orderBy: ['totalChannelsCreated_DESC'],
})
log(member)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ orionApi, log }: SnippetParams) {
// @snippet-begin
// Get id and title of a video by their Youtube video ID
const video = await orionApi.query.Video.first({
select: { id: true, title: true },
where: { ytVideoId_eq: 'GlIQQX5s2bw' },
})
log(video)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ orionApi, log }: SnippetParams) {
// @snippet-begin
// Get a highest revenue channel which title contains the word "Joystream"
const channel = await orionApi.query.Channel.first({
where: { title_containsInsensitive: 'Joystream' },
orderBy: ['cumulativeRevenue_DESC'],
})
log(channel)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ storageSquidApi, log }: SnippetParams) {
// @snippet-begin
// Get all scalar fields of a bucket by the node endpoint
const storageBucket = await storageSquidApi.query.StorageBucket.first({
where: {
operatorMetadata: {
nodeEndpoint_eq: 'https://storage.freakstatic.com/storage/',
},
},
})
log(storageBucket)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ storageSquidApi, log }: SnippetParams) {
// @snippet-begin
// Get id of storage data object by hash
const { id } = await storageSquidApi.query.StorageDataObject.first({
select: { id: true },
where: { ipfsHash_eq: 'gW9Z69CKtJvkvDxNY5BNoe2Wb4KXJneDSjm1iZD9Um8ich' },
})
log(id)
// @snippet-end
}
Get multiple entities by ids
Retrieves multiple entities by their ids.
Will execute multiple queries in case the list of ids is very large to avoid hitting the 2 MB request size limit.
The exact number of entities retrieved in a single query can be controlled with config.inputBatchSize
.
Syntax
qApi.query.ENTITY_NAME.byIds(IDS) // Selects all scalar fields qApi.query.ENTITY_NAME.byIds(IDS, SELECTION) // Selects specified fields
Examples
- Query node
- Orion
- Storage squid
import { SnippetParams } from '../../snippet'
export default async function ({ qnApi, log }: SnippetParams) {
// @snippet-begin
// Get all scalar fields of a few different members:
const members = await qnApi.query.Membership.byIds(['4129', '3234', '957'])
log(members)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ qnApi, log }: SnippetParams) {
// @snippet-begin
// Get a few proposals along with some of their details
const proposals = await qnApi.query.Proposal.byIds(['9', '10', '11'], {
__scalar: true,
details: {
on_CreateWorkingGroupLeadOpeningProposalDetails: {
group: {
name: true,
},
},
// ...handle other types if needed
},
})
log(proposals)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ orionApi, log }: SnippetParams) {
// @snippet-begin
// Get all scalar fields of a few channels
const channels = await orionApi.query.Channel.byIds(['1', '7692', '7698'])
log(channels)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ orionApi, log }: SnippetParams) {
// @snippet-begin
// Query a few channels, along with those of their videos which are longer than 1 hour
const channelWithVideos = await orionApi.query.Channel.byIds(
['1', '7692', '7698'],
{
__scalar: true,
videos: {
__args: { where: { duration_gt: 3600 } },
__scalar: true,
},
}
)
log(channelWithVideos)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ orionApi, log }: SnippetParams) {
// @snippet-begin
// Get specific fields of a few videos
const videos = await orionApi.query.Video.byIds(['1', '5', '905'], {
id: true,
title: true,
duration: true,
category: {
name: true,
},
channel: {
title: true,
},
})
log(videos)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ storageSquidApi, log }: SnippetParams) {
// @snippet-begin
// Get all scalar fields of a few storage buckets
const storageBuckets = await storageSquidApi.query.StorageBucket.byIds([
'0',
'1',
'2',
])
log(storageBuckets)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ storageSquidApi, log }: SnippetParams) {
// @snippet-begin
// Get storage bags of a few channels, along with some information about their data objects
const storageBags = await storageSquidApi.query.StorageBag.byIds(
['dynamic:channel:1', 'dynamic:channel:7692', 'dynamic:channel:7698'],
{
__scalar: true,
objects: {
id: true,
ipfsHash: true,
size: true,
},
}
)
log(storageBags)
// @snippet-end
}
Get multiple entities from a list
Oftentimes you may have a list of values and would like to query some associated entities based on those values.
A specific example may be a list of ids, in which case you can use byIds
method.
But those values may not always ids, they can also be, for example:
- membership handles,
- ids of an associated entity (e.g. bag ids of storage data objects),
If the list you have is very large, you may want to make use of the features that byIds
method provides,
like auto-chunking and query parallelization.
Fortunately this is possible with byMany
method.
Syntax
qApi.query.ENTITY_NAME.byMany({ input: VALUES, // List of values to query from where: WHERE_FUNCTION, // Takes a chunk of values and returns the where conditions select: SELECTION, // Optional, by default all scalar fields are selected })
Examples
- Query node
- Orion
- Storage squid
import { SnippetParams } from '../../snippet'
export default async function ({ qnApi, log }: SnippetParams) {
// @snippet-begin
// Get all scalar fields of a few different members by their handles:
const members = await qnApi.query.Membership.byMany({
input: ['leet_joy', 'Jenny', 'Codefikeyz'],
where: (handles) => ({ handle_in: handles }),
})
log(members)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ qnApi, log }: SnippetParams) {
// @snippet-begin
// Get specific fields of a few different members by their handles:
const members = await qnApi.query.Membership.byMany({
input: ['leet_joy', 'Jenny', 'Codefikeyz'],
where: (handles) => ({ handle_in: handles }),
select: {
id: true,
handle: true,
metadata: {
name: true,
about: true,
},
},
})
log(members)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ orionApi, log }: SnippetParams) {
// @snippet-begin
// Get all scalar fields of a few channels by a list of title keywords
const keywords = ['Bitcoin', 'Ethereum', 'Dogecoin']
const channels = await orionApi.query.Channel.byMany({
input: keywords,
where: (keywords) => ({
OR: keywords.map((k) => ({ title_containsInsensitive: k })),
}),
})
log(channels)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ orionApi, log }: SnippetParams) {
// @snippet-begin
// Query specific fields of videos by their ytVideoIds
const videos = await orionApi.query.Video.byMany({
input: ['GlIQQX5s2bw', 'rSiuFHKnhcA', 'WYb7884hM6o'],
where: (ytVideoIds) => ({ ytVideoId_in: ytVideoIds }),
select: {
id: true,
ytVideoId: true,
title: true,
description: true,
channel: {
id: true,
title: true,
},
},
})
log(videos)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ storageSquidApi, log }: SnippetParams) {
// @snippet-begin
// Query scalar fields of storage buckets by their node endpoints
const endpoints = [
'https://storage.js.8k.pm/storage/',
'https://storage.freakstatic.com/storage/',
'https://storage.0x2bc.com/storage/',
]
const buckets = await storageSquidApi.query.StorageBucket.byMany({
input: endpoints,
where: (endpoints) => ({
operatorMetadata: { nodeEndpoint_in: endpoints },
}),
})
log(buckets)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ storageSquidApi, log }: SnippetParams) {
// @snippet-begin
// Query ids and sizes of data objects by a list of bagIds
const objects = await storageSquidApi.query.StorageDataObject.byMany({
input: [
'dynamic:channel:1',
'dynamic:channel:7692',
'dynamic:channel:7698',
],
where: (bagIds) => ({ storageBag: { id_in: bagIds } }),
select: { id: true, size: true },
})
log(objects)
// @snippet-end
}
Pagination
Pagination queries are useful for fetching larger quantities of data or loading more data on demand.
Joystream services use Subsquid GraphQL servers which provide different kinds of pagination.
- Relay-style pagination using
Connection
queries (works in newer versions of Subsquid) is used by:- OrionApi
- StorageSquidApi
- Offset pagination using
limit
andoffset
(works in older version of Subsquid) is used by:- QueryNodeApi
The query module of Joystream SDK provides a simple interface for running queries with pagination.
Syntax
qApi.query.ENTITY_NAME.paginate({ select: SELECTION, orderBy: ORDER_BY_LIST, where: WHERE_ARGS, pageSize: PAGE_SIZE, // Optional, config.resultsPerQueryLimit will be used by default })
The paginate
method returns a Pagination
object which matches the following interface:
interface Pagination<Entity> {
// True if next page is available, false otherwise.
hasNextPage: boolean
// Fetches next page of entities
nextPage(): Promise<Entity[]>
// Fetches all pages and combines them into a single result.
// This may take a while and consume a lot of memory, so use with caution!
fetchAll(): Promise<Entity[]>
// Fetches a specific number of entities, running multiple queries if needed.
fetch(items: number): Promise<Entity[]>
}
Examples
- Query node
- Orion
- Storage squid
import { SnippetParams } from '../../snippet'
export default async function ({ qnApi, log }: SnippetParams) {
// @snippet-begin
// Get ids and handles of ALL members,
// fetching no more than 1000 members in a single query
const members = await qnApi.query.Membership.paginate({
orderBy: ['createdAt_ASC'],
select: { id: true, handle: true },
pageSize: 1000,
}).fetchAll()
log(members)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ qnApi, log }: SnippetParams) {
// @snippet-begin
// Fetch data about historical elections and log each page of 10 results separately
const electionPagination = qnApi.query.ElectionRound.paginate({
select: {
id: true,
isFinished: true,
candidates: {
member: {
id: true,
handle: true,
},
votePower: true,
},
},
where: { isFinished_eq: true },
orderBy: ['createdAt_DESC'],
pageSize: 10,
})
let i = 1
while (electionPagination.hasNextPage) {
const page = await electionPagination.nextPage()
log(`Page ${i}`)
log(page)
++i
}
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ orionApi, log }: SnippetParams) {
// @snippet-begin
// Get ids and titles of ALL channels,
// fetching no more than 1000 channels in a single query
const channels = await orionApi.query.Channel.paginate({
orderBy: ['createdAt_ASC'],
select: { id: true, title: true },
pageSize: 1000,
}).fetchAll()
log(channels)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ orionApi, log }: SnippetParams) {
// @snippet-begin
// Fetch data about creator tokens and log each page of 100 entries separately
const crtPagination = orionApi.query.CreatorToken.paginate({
orderBy: ['createdAt_ASC'],
select: {
id: true,
symbol: true,
lastPrice: true,
},
pageSize: 100,
})
let i = 1
while (crtPagination.hasNextPage) {
const page = await crtPagination.nextPage()
log(`Page ${i}`)
log(page)
++i
}
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ storageSquidApi, log }: SnippetParams) {
// @snippet-begin
// Get ids of ALL storage bags,
// fetching no more than 1000 bags in a single query
const bags = await storageSquidApi.query.StorageBag.paginate({
orderBy: ['id_ASC'],
select: { id: true },
pageSize: 1000,
}).fetchAll()
log(bags)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ storageSquidApi, log }: SnippetParams) {
// @snippet-begin
// Fetch information about the largest data objects
// and log each page of 100 results separately
// (limit the number of pages to 10)
const objectsPagination = storageSquidApi.query.StorageDataObject.paginate({
orderBy: ['size_DESC'],
select: {
id: true,
size: true,
type: {
__typename: true,
},
},
pageSize: 100,
})
let i = 1
while (i <= 10 && objectsPagination.hasNextPage) {
const page = await objectsPagination.nextPage()
log(`Page ${i}`)
log(page)
++i
}
// @snippet-end
}
Custom queries
If you have more specific needs, you can access the underlying GenQL client directly and take advantage of its type-safe interface to execute any GraphQL query you wish.
Examples
- Query node
- Orion
- Storage squid
import { SnippetParams } from '../../snippet'
export default async function ({ qnApi, log }: SnippetParams) {
// @snippet-begin
const result = await qnApi.client.query({
postsByText: {
__args: {
text: 'Joystream',
limit: 10,
},
item: {
__typename: true,
on_ForumPost: {
id: true,
text: true,
},
},
},
})
log(result)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ orionApi, log }: SnippetParams) {
// @snippet-begin
const result = await orionApi.client.query({
tokensWithPriceChange: {
__args: {
periodDays: 30,
limit: 10,
minVolume: '10000000000', // in HAPI
},
creatorToken: {
id: true,
symbol: true,
},
pricePercentageChange: true,
},
})
log(result)
// @snippet-end
}
import { SnippetParams } from '../../snippet'
export default async function ({ storageSquidApi, log }: SnippetParams) {
// @snippet-begin
const result = await storageSquidApi.client.query({
squidStatus: {
height: true,
},
})
log(result)
// @snippet-end
}
Read the GenQL documentation to find out more about how to use the GenQL client.