UNPKG

@arkiv-network/sdk

Version:

TypeScript client library for Arkiv's blockchains interactions

94 lines (89 loc) • 3.89 kB
import type { ArkivClient } from "../../clients/baseClient" import { runQuery } from "../../query/engine" import type { Expression } from "../../query/expression" import { checkedOrderTerm, type OrderCheck, type OrderTerm, renderOrderBy } from "../../query/order" import { type FullEntity, projectSelection, type ResultOf, type SelectArg, type SelectionOf, toRow, } from "../../query/selection" export type QueryOptions< TSelection extends SelectArg = SelectArg, TOrder extends OrderTerm = OrderTerm, > = { /** What to return — the same selection `select()` takes. Defaults to everything. */ select?: TSelection | undefined /** * The sort, an `asc(...)` or a `desc(...)`, sent as an `ORDER BY` clause after the query. There * is no schema here, so a key on a user attribute names its type, as in `desc("score", "dec")`. */ orderBy?: (TOrder & OrderCheck<TOrder, undefined>) | undefined /** Page size, up to the node maximum of 100. */ limit?: number | undefined /** * How many matches to skip before the page. Defaults to none. Each request reads the latest * block, so writes between two requests shift the matches, and a row can repeat or be missed. * Sort by a key that does not change, such as `asc("$key")`, so that pages line up at least while * nothing is written. */ offset?: number | undefined /** Whether to also count every entity the query matches, returned as `count`. */ count?: boolean | undefined } /** * What one result of a `query()` reads as for selection `S`: the same row `select(S)` returns * without a schema — every field when everything is selected, and every field optional when the * selection is only known at runtime. */ export type QueryRow<S> = ResultOf<SelectionOf<S>, undefined> export type QueryReturnType<TResult = FullEntity> = { entities: TResult[] /** The block the page was read at. */ blockNumber: bigint /** How many entities the query matches, if `count` was set. */ count: bigint | undefined } /** * Runs a single query and returns one page, with no builder in between. * * The selection means what it does in `select()`, and each result is the same flat row a query * without a schema returns — typed from the selection, with every attribute an `ArkivValue` or * `undefined`. * * Takes either an {@link Expression} or a raw query string. The string form is an escape hatch for * a query built elsewhere — it goes to the node exactly as written, with none of the name, type or * operator checks the expression combinators apply. An `orderBy` is checked either way, and goes * after the string as its `ORDER BY` clause, so a raw string must not end in one of its own. * * @throws If the selection asks for nothing, or names an attribute outside the name grammar. * @throws If `limit` is not an integer from 1 to 100, or `offset` is not a non-negative integer. * @throws {InvalidPredicateError} If `orderBy` is not a key `asc` or `desc` would build, or names a * user attribute without its type. * @throws {QueryError} If the node rejects the query. */ export async function query<const S extends SelectArg = "*", const K extends OrderTerm = OrderTerm>( client: ArkivClient, query: Expression | string, options: QueryOptions<S, K> = {}, ): Promise<QueryReturnType<QueryRow<S>>> { const projection = projectSelection(options.select) const filters = query.toString() const order = options.orderBy === undefined ? undefined : renderOrderBy(checkedOrderTerm(options.orderBy), undefined) const page = await runQuery(client, { query: order === undefined ? filters : `${filters} ${order}`, select: projection.select, limit: options.limit, offset: options.offset, count: options.count, }) return { ...page, entities: page.entities.map((entity) => toRow(entity, projection) as QueryRow<S>), } }