Skip to content

Commit b8fa501

Browse files
feat(api): add pagination to the deployments endpoint
1 parent 36d6b29 commit b8fa501

File tree

10 files changed

+277
-55
lines changed

10 files changed

+277
-55
lines changed

.stats.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 46
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-e98d46c55826cdf541a9ee0df04ce92806ac6d4d92957ae79f897270b7d85b23.yml
3-
openapi_spec_hash: 8a1af54fc0a4417165b8a52e6354b685
4-
config_hash: 043ddc54629c6d8b889123770cb4769f
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-33f1feaba7bde46bfa36d2fefb5c3bc9512967945bccf78045ad3f64aafc4eb0.yml
3+
openapi_spec_hash: 52a448889d41216d1ca30e8a57115b14
4+
config_hash: 1f28d5c3c063f418ebd2799df1e4e781

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,37 @@ On timeout, an `APIConnectionTimeoutError` is thrown.
168168

169169
Note that requests which time out will be [retried twice by default](#retries).
170170

171+
## Auto-pagination
172+
173+
List methods in the Kernel API are paginated.
174+
You can use the `for await … of` syntax to iterate through items across all pages:
175+
176+
```ts
177+
async function fetchAllDeploymentListResponses(params) {
178+
const allDeploymentListResponses = [];
179+
// Automatically fetches more pages as needed.
180+
for await (const deploymentListResponse of client.deployments.list({ app_name: 'YOUR_APP', limit: 2 })) {
181+
allDeploymentListResponses.push(deploymentListResponse);
182+
}
183+
return allDeploymentListResponses;
184+
}
185+
```
186+
187+
Alternatively, you can request a single page at a time:
188+
189+
```ts
190+
let page = await client.deployments.list({ app_name: 'YOUR_APP', limit: 2 });
191+
for (const deploymentListResponse of page.items) {
192+
console.log(deploymentListResponse);
193+
}
194+
195+
// Convenience methods are provided for manually paginating:
196+
while (page.hasNextPage()) {
197+
page = await page.getNextPage();
198+
// ...
199+
}
200+
```
201+
171202
## Advanced Usage
172203

173204
### Accessing raw Response data (e.g., headers)

api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Methods:
2323

2424
- <code title="post /deployments">client.deployments.<a href="./src/resources/deployments.ts">create</a>({ ...params }) -> DeploymentCreateResponse</code>
2525
- <code title="get /deployments/{id}">client.deployments.<a href="./src/resources/deployments.ts">retrieve</a>(id) -> DeploymentRetrieveResponse</code>
26-
- <code title="get /deployments">client.deployments.<a href="./src/resources/deployments.ts">list</a>({ ...params }) -> DeploymentListResponse</code>
26+
- <code title="get /deployments">client.deployments.<a href="./src/resources/deployments.ts">list</a>({ ...params }) -> DeploymentListResponsesOffsetPagination</code>
2727
- <code title="get /deployments/{id}/events">client.deployments.<a href="./src/resources/deployments.ts">follow</a>(id, { ...params }) -> DeploymentFollowResponse</code>
2828

2929
# Apps

src/client.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import * as Shims from './internal/shims';
1313
import * as Opts from './internal/request-options';
1414
import { VERSION } from './version';
1515
import * as Errors from './core/error';
16+
import * as Pagination from './core/pagination';
17+
import { AbstractPage, type OffsetPaginationParams, OffsetPaginationResponse } from './core/pagination';
1618
import * as Uploads from './core/uploads';
1719
import * as API from './resources/index';
1820
import { APIPromise } from './core/api-promise';
@@ -24,6 +26,7 @@ import {
2426
DeploymentFollowResponse,
2527
DeploymentListParams,
2628
DeploymentListResponse,
29+
DeploymentListResponsesOffsetPagination,
2730
DeploymentRetrieveResponse,
2831
DeploymentStateEvent,
2932
Deployments,
@@ -551,6 +554,25 @@ export class Kernel {
551554
return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
552555
}
553556

557+
getAPIList<Item, PageClass extends Pagination.AbstractPage<Item> = Pagination.AbstractPage<Item>>(
558+
path: string,
559+
Page: new (...args: any[]) => PageClass,
560+
opts?: RequestOptions,
561+
): Pagination.PagePromise<PageClass, Item> {
562+
return this.requestAPIList(Page, { method: 'get', path, ...opts });
563+
}
564+
565+
requestAPIList<
566+
Item = unknown,
567+
PageClass extends Pagination.AbstractPage<Item> = Pagination.AbstractPage<Item>,
568+
>(
569+
Page: new (...args: ConstructorParameters<typeof Pagination.AbstractPage>) => PageClass,
570+
options: FinalRequestOptions,
571+
): Pagination.PagePromise<PageClass, Item> {
572+
const request = this.makeRequest(options, null, undefined);
573+
return new Pagination.PagePromise<PageClass, Item>(this as any as Kernel, request, Page);
574+
}
575+
554576
async fetchWithTimeout(
555577
url: RequestInfo,
556578
init: RequestInit | undefined,
@@ -809,13 +831,20 @@ Kernel.Profiles = Profiles;
809831
export declare namespace Kernel {
810832
export type RequestOptions = Opts.RequestOptions;
811833

834+
export import OffsetPagination = Pagination.OffsetPagination;
835+
export {
836+
type OffsetPaginationParams as OffsetPaginationParams,
837+
type OffsetPaginationResponse as OffsetPaginationResponse,
838+
};
839+
812840
export {
813841
Deployments as Deployments,
814842
type DeploymentStateEvent as DeploymentStateEvent,
815843
type DeploymentCreateResponse as DeploymentCreateResponse,
816844
type DeploymentRetrieveResponse as DeploymentRetrieveResponse,
817845
type DeploymentListResponse as DeploymentListResponse,
818846
type DeploymentFollowResponse as DeploymentFollowResponse,
847+
type DeploymentListResponsesOffsetPagination as DeploymentListResponsesOffsetPagination,
819848
type DeploymentCreateParams as DeploymentCreateParams,
820849
type DeploymentListParams as DeploymentListParams,
821850
type DeploymentFollowParams as DeploymentFollowParams,

src/core/pagination.ts

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export { Kernel as default } from './client';
55
export { type Uploadable, toFile } from './core/uploads';
66
export { APIPromise } from './core/api-promise';
77
export { Kernel, type ClientOptions } from './client';
8+
export { PagePromise } from './core/pagination';
89
export {
910
KernelError,
1011
APIError,

src/pagination.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/** @deprecated Import from ./core/pagination instead */
2+
export * from './core/pagination';

0 commit comments

Comments
 (0)