This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / lib / api / pagination.ts
3.7 kB 122 lines
1import type { XRPCQueries } from "@atcute/lexicons/ambient"; 2import type { 3 InferInput, 4 InferOutput, 5 ObjectSchema, 6 XRPCLexBodyParam 7} from "@atcute/lexicons/validations"; 8import { ok, type BobbinContext, type XrpcRequestInit } from "./client"; 9 10export type QueryOutput<TName extends keyof XRPCQueries> = 11 XRPCQueries[TName]["output"] extends XRPCLexBodyParam 12 ? InferOutput<XRPCQueries[TName]["output"]["schema"]> 13 : never; 14 15export type QueryParams<TName extends keyof XRPCQueries> = 16 XRPCQueries[TName]["params"] extends ObjectSchema 17 ? InferInput<XRPCQueries[TName]["params"]> 18 : Record<string, never>; 19 20type CursorPage = { items: readonly unknown[]; cursor?: string }; 21 22// registered queries whose output is an { items, cursor? } page. 23export type PaginatedQuery = { 24 [K in keyof XRPCQueries]: QueryOutput<K> extends CursorPage ? K : never; 25}[keyof XRPCQueries]; 26 27export type PageItem<TName extends PaginatedQuery> = 28 QueryOutput<TName> extends { items: readonly (infer I)[] } ? I : never; 29 30export type PageParams<TName extends PaginatedQuery> = Omit<QueryParams<TName>, "cursor">; 31 32export interface PaginateOptions extends XrpcRequestInit { 33 maxPages?: number; 34} 35 36const requestPage = <TName extends PaginatedQuery>( 37 ctx: BobbinContext, 38 name: TName, 39 params: PageParams<TName> & { cursor?: string }, 40 init?: XrpcRequestInit 41): Promise<QueryOutput<TName>> => 42 ok( 43 ctx.xrpc.get(name, { 44 params, 45 signal: init?.signal, 46 headers: init?.headers 47 } as never) 48 ) as Promise<QueryOutput<TName>>; 49export const fetchPage = <TName extends PaginatedQuery>( 50 ctx: BobbinContext, 51 name: TName, 52 params: PageParams<TName> & { cursor?: string }, 53 init?: XrpcRequestInit 54): Promise<QueryOutput<TName>> => requestPage(ctx, name, params, init); 55 56export async function* pages<TName extends PaginatedQuery>( 57 ctx: BobbinContext, 58 name: TName, 59 params: PageParams<TName>, 60 options: PaginateOptions = {} 61): AsyncGenerator<QueryOutput<TName>, void, unknown> { 62 let cursor: string | undefined; 63 let seen = 0; 64 do { 65 const data = await requestPage( 66 ctx, 67 name, 68 { ...params, cursor } as PageParams<TName> & { cursor?: string }, 69 options 70 ); 71 yield data; 72 const next = (data as CursorPage).cursor; 73 if (next !== undefined && next === cursor) break; 74 cursor = next; 75 if (options.maxPages && ++seen >= options.maxPages) break; 76 } while (cursor); 77} 78 79export async function* items<TName extends PaginatedQuery>( 80 ctx: BobbinContext, 81 name: TName, 82 params: PageParams<TName>, 83 options: PaginateOptions = {} 84): AsyncGenerator<PageItem<TName>, void, unknown> { 85 for await (const data of pages(ctx, name, params, options)) { 86 for (const item of (data as CursorPage).items) yield item as PageItem<TName>; 87 } 88} 89 90export interface CollectOptions extends PaginateOptions { 91 max?: number; 92} 93 94export const collect = async <TName extends PaginatedQuery>( 95 ctx: BobbinContext, 96 name: TName, 97 params: PageParams<TName>, 98 options: CollectOptions = {} 99): Promise<PageItem<TName>[]> => { 100 const out: PageItem<TName>[] = []; 101 for await (const item of items(ctx, name, params, options)) { 102 out.push(item); 103 if (options.max && out.length >= options.max) break; 104 } 105 return out; 106}; 107 108export async function* paginateBy<T>( 109 load: (cursor: string | undefined) => Promise<{ items: readonly T[]; cursor?: string | null }>, 110 options: { maxPages?: number } = {} 111): AsyncGenerator<T, void, unknown> { 112 let cursor: string | undefined; 113 let seen = 0; 114 do { 115 const page = await load(cursor); 116 for (const item of page.items) yield item; 117 const next = page.cursor ?? undefined; 118 if (next !== undefined && next === cursor) break; 119 cursor = next; 120 if (options.maxPages && ++seen >= options.maxPages) break; 121 } while (cursor); 122}