Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123import type { XRPCQueries } from "@atcute/lexicons/ambient";import type { InferInput, InferOutput, ObjectSchema, XRPCLexBodyParam} from "@atcute/lexicons/validations";import { ok, type BobbinContext, type XrpcRequestInit } from "./client";
export type QueryOutput<TName extends keyof XRPCQueries> = XRPCQueries[TName]["output"] extends XRPCLexBodyParam ? InferOutput<XRPCQueries[TName]["output"]["schema"]> : never;
export type QueryParams<TName extends keyof XRPCQueries> = XRPCQueries[TName]["params"] extends ObjectSchema ? InferInput<XRPCQueries[TName]["params"]> : Record<string, never>;
type CursorPage = { items: readonly unknown[]; cursor?: string };
// registered queries whose output is an { items, cursor? } page.export type PaginatedQuery = { [K in keyof XRPCQueries]: QueryOutput<K> extends CursorPage ? K : never;}[keyof XRPCQueries];
export type PageItem<TName extends PaginatedQuery> = QueryOutput<TName> extends { items: readonly (infer I)[] } ? I : never;
export type PageParams<TName extends PaginatedQuery> = Omit<QueryParams<TName>, "cursor">;
export interface PaginateOptions extends XrpcRequestInit { maxPages?: number;}
const requestPage = <TName extends PaginatedQuery>( ctx: BobbinContext, name: TName, params: PageParams<TName> & { cursor?: string }, init?: XrpcRequestInit): Promise<QueryOutput<TName>> => ok( ctx.xrpc.get(name, { params, signal: init?.signal, headers: init?.headers } as never) ) as Promise<QueryOutput<TName>>;export const fetchPage = <TName extends PaginatedQuery>( ctx: BobbinContext, name: TName, params: PageParams<TName> & { cursor?: string }, init?: XrpcRequestInit): Promise<QueryOutput<TName>> => requestPage(ctx, name, params, init);
export async function* pages<TName extends PaginatedQuery>( ctx: BobbinContext, name: TName, params: PageParams<TName>, options: PaginateOptions = {}): AsyncGenerator<QueryOutput<TName>, void, unknown> { let cursor: string | undefined; let seen = 0; do { const data = await requestPage( ctx, name, { ...params, cursor } as PageParams<TName> & { cursor?: string }, options ); yield data; const next = (data as CursorPage).cursor; if (next !== undefined && next === cursor) break; cursor = next; if (options.maxPages && ++seen >= options.maxPages) break; } while (cursor);}
export async function* items<TName extends PaginatedQuery>( ctx: BobbinContext, name: TName, params: PageParams<TName>, options: PaginateOptions = {}): AsyncGenerator<PageItem<TName>, void, unknown> { for await (const data of pages(ctx, name, params, options)) { for (const item of (data as CursorPage).items) yield item as PageItem<TName>; }}
export interface CollectOptions extends PaginateOptions { max?: number;}
export const collect = async <TName extends PaginatedQuery>( ctx: BobbinContext, name: TName, params: PageParams<TName>, options: CollectOptions = {}): Promise<PageItem<TName>[]> => { const out: PageItem<TName>[] = []; for await (const item of items(ctx, name, params, options)) { out.push(item); if (options.max && out.length >= options.max) break; } return out;};
export async function* paginateBy<T>( load: (cursor: string | undefined) => Promise<{ items: readonly T[]; cursor?: string | null }>, options: { maxPages?: number } = {}): AsyncGenerator<T, void, unknown> { let cursor: string | undefined; let seen = 0; do { const page = await load(cursor); for (const item of page.items) yield item; const next = page.cursor ?? undefined; if (next !== undefined && next === cursor) break; cursor = next; if (options.maxPages && ++seen >= options.maxPages) break; } while (cursor);}