|
| 1 | +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. |
| 2 | + |
| 3 | +import { KernelError } from './error'; |
| 4 | +import { FinalRequestOptions } from '../internal/request-options'; |
| 5 | +import { defaultParseResponse } from '../internal/parse'; |
| 6 | +import { type Kernel } from '../client'; |
| 7 | +import { APIPromise } from './api-promise'; |
| 8 | +import { type APIResponseProps } from '../internal/parse'; |
| 9 | +import { maybeObj } from '../internal/utils/values'; |
| 10 | + |
| 11 | +export type PageRequestOptions = Pick<FinalRequestOptions, 'query' | 'headers' | 'body' | 'path' | 'method'>; |
| 12 | + |
| 13 | +export abstract class AbstractPage<Item> implements AsyncIterable<Item> { |
| 14 | + #client: Kernel; |
| 15 | + protected options: FinalRequestOptions; |
| 16 | + |
| 17 | + protected response: Response; |
| 18 | + protected body: unknown; |
| 19 | + |
| 20 | + constructor(client: Kernel, response: Response, body: unknown, options: FinalRequestOptions) { |
| 21 | + this.#client = client; |
| 22 | + this.options = options; |
| 23 | + this.response = response; |
| 24 | + this.body = body; |
| 25 | + } |
| 26 | + |
| 27 | + abstract nextPageRequestOptions(): PageRequestOptions | null; |
| 28 | + |
| 29 | + abstract getPaginatedItems(): Item[]; |
| 30 | + |
| 31 | + hasNextPage(): boolean { |
| 32 | + const items = this.getPaginatedItems(); |
| 33 | + if (!items.length) return false; |
| 34 | + return this.nextPageRequestOptions() != null; |
| 35 | + } |
| 36 | + |
| 37 | + async getNextPage(): Promise<this> { |
| 38 | + const nextOptions = this.nextPageRequestOptions(); |
| 39 | + if (!nextOptions) { |
| 40 | + throw new KernelError( |
| 41 | + 'No next page expected; please check `.hasNextPage()` before calling `.getNextPage()`.', |
| 42 | + ); |
| 43 | + } |
| 44 | + |
| 45 | + return await this.#client.requestAPIList(this.constructor as any, nextOptions); |
| 46 | + } |
| 47 | + |
| 48 | + async *iterPages(): AsyncGenerator<this> { |
| 49 | + let page: this = this; |
| 50 | + yield page; |
| 51 | + while (page.hasNextPage()) { |
| 52 | + page = await page.getNextPage(); |
| 53 | + yield page; |
| 54 | + } |
| 55 | + } |
| 56 | + |
| 57 | + async *[Symbol.asyncIterator](): AsyncGenerator<Item> { |
| 58 | + for await (const page of this.iterPages()) { |
| 59 | + for (const item of page.getPaginatedItems()) { |
| 60 | + yield item; |
| 61 | + } |
| 62 | + } |
| 63 | + } |
| 64 | +} |
| 65 | + |
| 66 | +/** |
| 67 | + * This subclass of Promise will resolve to an instantiated Page once the request completes. |
| 68 | + * |
| 69 | + * It also implements AsyncIterable to allow auto-paginating iteration on an unawaited list call, eg: |
| 70 | + * |
| 71 | + * for await (const item of client.items.list()) { |
| 72 | + * console.log(item) |
| 73 | + * } |
| 74 | + */ |
| 75 | +export class PagePromise< |
| 76 | + PageClass extends AbstractPage<Item>, |
| 77 | + Item = ReturnType<PageClass['getPaginatedItems']>[number], |
| 78 | + > |
| 79 | + extends APIPromise<PageClass> |
| 80 | + implements AsyncIterable<Item> |
| 81 | +{ |
| 82 | + constructor( |
| 83 | + client: Kernel, |
| 84 | + request: Promise<APIResponseProps>, |
| 85 | + Page: new (...args: ConstructorParameters<typeof AbstractPage>) => PageClass, |
| 86 | + ) { |
| 87 | + super( |
| 88 | + client, |
| 89 | + request, |
| 90 | + async (client, props) => |
| 91 | + new Page(client, props.response, await defaultParseResponse(client, props), props.options), |
| 92 | + ); |
| 93 | + } |
| 94 | + |
| 95 | + /** |
| 96 | + * Allow auto-paginating iteration on an unawaited list call, eg: |
| 97 | + * |
| 98 | + * for await (const item of client.items.list()) { |
| 99 | + * console.log(item) |
| 100 | + * } |
| 101 | + */ |
| 102 | + async *[Symbol.asyncIterator](): AsyncGenerator<Item> { |
| 103 | + const page = await this; |
| 104 | + for await (const item of page) { |
| 105 | + yield item; |
| 106 | + } |
| 107 | + } |
| 108 | +} |
| 109 | + |
| 110 | +export type OffsetPaginationResponse<Item> = Item[]; |
| 111 | + |
| 112 | +export interface OffsetPaginationParams { |
| 113 | + offset?: number; |
| 114 | + |
| 115 | + limit?: number; |
| 116 | +} |
| 117 | + |
| 118 | +export class OffsetPagination<Item> extends AbstractPage<Item> { |
| 119 | + items: Array<Item>; |
| 120 | + |
| 121 | + constructor( |
| 122 | + client: Kernel, |
| 123 | + response: Response, |
| 124 | + body: OffsetPaginationResponse<Item>, |
| 125 | + options: FinalRequestOptions, |
| 126 | + ) { |
| 127 | + super(client, response, body, options); |
| 128 | + |
| 129 | + this.items = body || []; |
| 130 | + } |
| 131 | + |
| 132 | + getPaginatedItems(): Item[] { |
| 133 | + return this.items ?? []; |
| 134 | + } |
| 135 | + |
| 136 | + nextPageRequestOptions(): PageRequestOptions | null { |
| 137 | + const offset = (this.options.query as OffsetPaginationParams).offset ?? 0; |
| 138 | + if (!offset) { |
| 139 | + return null; |
| 140 | + } |
| 141 | + |
| 142 | + const length = this.getPaginatedItems().length; |
| 143 | + const currentCount = offset + length; |
| 144 | + |
| 145 | + return { |
| 146 | + ...this.options, |
| 147 | + query: { |
| 148 | + ...maybeObj(this.options.query), |
| 149 | + offset: currentCount, |
| 150 | + }, |
| 151 | + }; |
| 152 | + } |
| 153 | +} |
0 commit comments