Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.11.0"
".": "0.11.1"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 46
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-e98d46c55826cdf541a9ee0df04ce92806ac6d4d92957ae79f897270b7d85b23.yml
openapi_spec_hash: 8a1af54fc0a4417165b8a52e6354b685
config_hash: 043ddc54629c6d8b889123770cb4769f
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-cb38560915edce03abce2ae3ef5bc745489dbe9b6f80c2b4ff42edf8c2ff276d.yml
openapi_spec_hash: a869194d6c864ba28d79ec0105439c3e
config_hash: ed56f95781ec9b2e73c97e1a66606071
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## 0.11.1 (2025-09-08)

Full Changelog: [v0.11.0...v0.11.1](https://github.com/onkernel/kernel-node-sdk/compare/v0.11.0...v0.11.1)

### Features

* **api:** add pagination to the deployments endpoint ([b8fa501](https://github.com/onkernel/kernel-node-sdk/commit/b8fa5012dd3b5944e128a5ff629aeea19159362a))
* **api:** pagination properties added to response (has_more, next_offset) ([49c574e](https://github.com/onkernel/kernel-node-sdk/commit/49c574eeba8413d01cc4528282a59068dfc2150d))
* **api:** update API spec with pagination headers ([d1169c0](https://github.com/onkernel/kernel-node-sdk/commit/d1169c09fb1222114db878a133ca593479d257fc))


### Bug Fixes

* coerce nullable values to undefined ([41cb0ae](https://github.com/onkernel/kernel-node-sdk/commit/41cb0aeb163e00f1e3e2fe41acd3f0c379b64976))


### Chores

* ci build action ([790397f](https://github.com/onkernel/kernel-node-sdk/commit/790397fdd2f074f49e70a1eebfa129750174bcb3))

## 0.11.0 (2025-09-04)

Full Changelog: [v0.10.0...v0.11.0](https://github.com/onkernel/kernel-node-sdk/compare/v0.10.0...v0.11.0)
Expand Down
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,37 @@ On timeout, an `APIConnectionTimeoutError` is thrown.

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

## Auto-pagination

List methods in the Kernel API are paginated.
You can use the `for await … of` syntax to iterate through items across all pages:

```ts
async function fetchAllDeploymentListResponses(params) {
const allDeploymentListResponses = [];
// Automatically fetches more pages as needed.
for await (const deploymentListResponse of client.deployments.list({ app_name: 'YOUR_APP', limit: 2 })) {
allDeploymentListResponses.push(deploymentListResponse);
}
return allDeploymentListResponses;
}
```

Alternatively, you can request a single page at a time:

```ts
let page = await client.deployments.list({ app_name: 'YOUR_APP', limit: 2 });
for (const deploymentListResponse of page.items) {
console.log(deploymentListResponse);
}

// Convenience methods are provided for manually paginating:
while (page.hasNextPage()) {
page = await page.getNextPage();
// ...
}
```

## Advanced Usage

### Accessing raw Response data (e.g., headers)
Expand Down
2 changes: 1 addition & 1 deletion api.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Methods:

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

# Apps
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onkernel/sdk",
"version": "0.11.0",
"version": "0.11.1",
"description": "The official TypeScript library for the Kernel API",
"author": "Kernel <>",
"types": "dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion scripts/utils/upload-artifact.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ if [[ "$SIGNED_URL" == "null" ]]; then
exit 1
fi

UPLOAD_RESPONSE=$(tar -cz "${BUILD_PATH:-dist}" | curl -v -X PUT \
UPLOAD_RESPONSE=$(tar "${BASE_PATH:+-C$BASE_PATH}" -cz "${ARTIFACT_PATH:-dist}" | curl -v -X PUT \
-H "Content-Type: application/gzip" \
--data-binary @- "$SIGNED_URL" 2>&1)

Expand Down
29 changes: 29 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import * as Shims from './internal/shims';
import * as Opts from './internal/request-options';
import { VERSION } from './version';
import * as Errors from './core/error';
import * as Pagination from './core/pagination';
import { AbstractPage, type OffsetPaginationParams, OffsetPaginationResponse } from './core/pagination';
import * as Uploads from './core/uploads';
import * as API from './resources/index';
import { APIPromise } from './core/api-promise';
Expand All @@ -24,6 +26,7 @@ import {
DeploymentFollowResponse,
DeploymentListParams,
DeploymentListResponse,
DeploymentListResponsesOffsetPagination,
DeploymentRetrieveResponse,
DeploymentStateEvent,
Deployments,
Expand Down Expand Up @@ -551,6 +554,25 @@ export class Kernel {
return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
}

getAPIList<Item, PageClass extends Pagination.AbstractPage<Item> = Pagination.AbstractPage<Item>>(
path: string,
Page: new (...args: any[]) => PageClass,
opts?: RequestOptions,
): Pagination.PagePromise<PageClass, Item> {
return this.requestAPIList(Page, { method: 'get', path, ...opts });
}

requestAPIList<
Item = unknown,
PageClass extends Pagination.AbstractPage<Item> = Pagination.AbstractPage<Item>,
>(
Page: new (...args: ConstructorParameters<typeof Pagination.AbstractPage>) => PageClass,
options: FinalRequestOptions,
): Pagination.PagePromise<PageClass, Item> {
const request = this.makeRequest(options, null, undefined);
return new Pagination.PagePromise<PageClass, Item>(this as any as Kernel, request, Page);
}

async fetchWithTimeout(
url: RequestInfo,
init: RequestInit | undefined,
Expand Down Expand Up @@ -809,13 +831,20 @@ Kernel.Profiles = Profiles;
export declare namespace Kernel {
export type RequestOptions = Opts.RequestOptions;

export import OffsetPagination = Pagination.OffsetPagination;
export {
type OffsetPaginationParams as OffsetPaginationParams,
type OffsetPaginationResponse as OffsetPaginationResponse,
};

export {
Deployments as Deployments,
type DeploymentStateEvent as DeploymentStateEvent,
type DeploymentCreateResponse as DeploymentCreateResponse,
type DeploymentRetrieveResponse as DeploymentRetrieveResponse,
type DeploymentListResponse as DeploymentListResponse,
type DeploymentFollowResponse as DeploymentFollowResponse,
type DeploymentListResponsesOffsetPagination as DeploymentListResponsesOffsetPagination,
type DeploymentCreateParams as DeploymentCreateParams,
type DeploymentListParams as DeploymentListParams,
type DeploymentFollowParams as DeploymentFollowParams,
Expand Down
167 changes: 167 additions & 0 deletions src/core/pagination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { KernelError } from './error';
import { FinalRequestOptions } from '../internal/request-options';
import { defaultParseResponse } from '../internal/parse';
import { type Kernel } from '../client';
import { APIPromise } from './api-promise';
import { type APIResponseProps } from '../internal/parse';
import { maybeCoerceBoolean, maybeCoerceInteger, maybeObj } from '../internal/utils/values';

export type PageRequestOptions = Pick<FinalRequestOptions, 'query' | 'headers' | 'body' | 'path' | 'method'>;

export abstract class AbstractPage<Item> implements AsyncIterable<Item> {
#client: Kernel;
protected options: FinalRequestOptions;

protected response: Response;
protected body: unknown;

constructor(client: Kernel, response: Response, body: unknown, options: FinalRequestOptions) {
this.#client = client;
this.options = options;
this.response = response;
this.body = body;
}

abstract nextPageRequestOptions(): PageRequestOptions | null;

abstract getPaginatedItems(): Item[];

hasNextPage(): boolean {
const items = this.getPaginatedItems();
if (!items.length) return false;
return this.nextPageRequestOptions() != null;
}

async getNextPage(): Promise<this> {
const nextOptions = this.nextPageRequestOptions();
if (!nextOptions) {
throw new KernelError(
'No next page expected; please check `.hasNextPage()` before calling `.getNextPage()`.',
);
}

return await this.#client.requestAPIList(this.constructor as any, nextOptions);
}

async *iterPages(): AsyncGenerator<this> {
let page: this = this;
yield page;
while (page.hasNextPage()) {
page = await page.getNextPage();
yield page;
}
}

async *[Symbol.asyncIterator](): AsyncGenerator<Item> {
for await (const page of this.iterPages()) {
for (const item of page.getPaginatedItems()) {
yield item;
}
}
}
}

/**
* This subclass of Promise will resolve to an instantiated Page once the request completes.
*
* It also implements AsyncIterable to allow auto-paginating iteration on an unawaited list call, eg:
*
* for await (const item of client.items.list()) {
* console.log(item)
* }
*/
export class PagePromise<
PageClass extends AbstractPage<Item>,
Item = ReturnType<PageClass['getPaginatedItems']>[number],
>
extends APIPromise<PageClass>
implements AsyncIterable<Item>
{
constructor(
client: Kernel,
request: Promise<APIResponseProps>,
Page: new (...args: ConstructorParameters<typeof AbstractPage>) => PageClass,
) {
super(
client,
request,
async (client, props) =>
new Page(client, props.response, await defaultParseResponse(client, props), props.options),
);
}

/**
* Allow auto-paginating iteration on an unawaited list call, eg:
*
* for await (const item of client.items.list()) {
* console.log(item)
* }
*/
async *[Symbol.asyncIterator](): AsyncGenerator<Item> {
const page = await this;
for await (const item of page) {
yield item;
}
}
}

export type OffsetPaginationResponse<Item> = Item[];

export interface OffsetPaginationParams {
offset?: number;

limit?: number;
}

export class OffsetPagination<Item> extends AbstractPage<Item> {
items: Array<Item>;

has_more: boolean | null;

next_offset: number | null;

constructor(
client: Kernel,
response: Response,
body: OffsetPaginationResponse<Item>,
options: FinalRequestOptions,
) {
super(client, response, body, options);

this.items = body || [];
this.has_more = maybeCoerceBoolean(this.response.headers.get('x-has-more')) ?? null;
this.next_offset = maybeCoerceInteger(this.response.headers.get('x-next-offset')) ?? null;
}

getPaginatedItems(): Item[] {
return this.items ?? [];
}

override hasNextPage(): boolean {
if (this.has_more === false) {
return false;
}

return super.hasNextPage();
}

nextPageRequestOptions(): PageRequestOptions | null {
const offset = this.next_offset;
if (!offset) {
return null;
}

const length = this.getPaginatedItems().length;
const currentCount = offset + length;

return {
...this.options,
query: {
...maybeObj(this.options.query),
offset: currentCount,
},
};
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { Kernel as default } from './client';
export { type Uploadable, toFile } from './core/uploads';
export { APIPromise } from './core/api-promise';
export { Kernel, type ClientOptions } from './client';
export { PagePromise } from './core/pagination';
export {
KernelError,
APIError,
Expand Down
6 changes: 3 additions & 3 deletions src/internal/utils/values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,21 +76,21 @@ export const coerceBoolean = (value: unknown): boolean => {
};

export const maybeCoerceInteger = (value: unknown): number | undefined => {
if (value === undefined) {
if (value == null) {
return undefined;
}
return coerceInteger(value);
};

export const maybeCoerceFloat = (value: unknown): number | undefined => {
if (value === undefined) {
if (value == null) {
return undefined;
}
return coerceFloat(value);
};

export const maybeCoerceBoolean = (value: unknown): boolean | undefined => {
if (value === undefined) {
if (value == null) {
return undefined;
}
return coerceBoolean(value);
Expand Down
2 changes: 2 additions & 0 deletions src/pagination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** @deprecated Import from ./core/pagination instead */
export * from './core/pagination';
Loading