diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 78e7f27..ddfa3e3 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.11.0"
+ ".": "0.11.1"
}
diff --git a/.stats.yml b/.stats.yml
index 6ac19ba..7fb3d31 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -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
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9b9bb96..dca3d03 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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)
diff --git a/README.md b/README.md
index 98f3d75..fa69c0c 100644
--- a/README.md
+++ b/README.md
@@ -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)
diff --git a/api.md b/api.md
index 4c61dfb..86d0347 100644
--- a/api.md
+++ b/api.md
@@ -23,7 +23,7 @@ Methods:
- client.deployments.create({ ...params }) -> DeploymentCreateResponse
- client.deployments.retrieve(id) -> DeploymentRetrieveResponse
-- client.deployments.list({ ...params }) -> DeploymentListResponse
+- client.deployments.list({ ...params }) -> DeploymentListResponsesOffsetPagination
- client.deployments.follow(id, { ...params }) -> DeploymentFollowResponse
# Apps
diff --git a/package.json b/package.json
index 75126b4..bae1017 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh
index ab5947d..38abeb4 100755
--- a/scripts/utils/upload-artifact.sh
+++ b/scripts/utils/upload-artifact.sh
@@ -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)
diff --git a/src/client.ts b/src/client.ts
index 130658c..77a53c3 100644
--- a/src/client.ts
+++ b/src/client.ts
@@ -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';
@@ -24,6 +26,7 @@ import {
DeploymentFollowResponse,
DeploymentListParams,
DeploymentListResponse,
+ DeploymentListResponsesOffsetPagination,
DeploymentRetrieveResponse,
DeploymentStateEvent,
Deployments,
@@ -551,6 +554,25 @@ export class Kernel {
return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
}
+ getAPIList- = Pagination.AbstractPage
- >(
+ path: string,
+ Page: new (...args: any[]) => PageClass,
+ opts?: RequestOptions,
+ ): Pagination.PagePromise {
+ return this.requestAPIList(Page, { method: 'get', path, ...opts });
+ }
+
+ requestAPIList<
+ Item = unknown,
+ PageClass extends Pagination.AbstractPage
- = Pagination.AbstractPage
- ,
+ >(
+ Page: new (...args: ConstructorParameters) => PageClass,
+ options: FinalRequestOptions,
+ ): Pagination.PagePromise {
+ const request = this.makeRequest(options, null, undefined);
+ return new Pagination.PagePromise(this as any as Kernel, request, Page);
+ }
+
async fetchWithTimeout(
url: RequestInfo,
init: RequestInit | undefined,
@@ -809,6 +831,12 @@ 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,
@@ -816,6 +844,7 @@ export declare namespace Kernel {
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,
diff --git a/src/core/pagination.ts b/src/core/pagination.ts
new file mode 100644
index 0000000..c3266cd
--- /dev/null
+++ b/src/core/pagination.ts
@@ -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;
+
+export abstract class AbstractPage
- implements AsyncIterable
- {
+ #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 {
+ 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 {
+ let page: this = this;
+ yield page;
+ while (page.hasNextPage()) {
+ page = await page.getNextPage();
+ yield page;
+ }
+ }
+
+ async *[Symbol.asyncIterator](): AsyncGenerator
- {
+ 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 = ReturnType[number],
+ >
+ extends APIPromise
+ implements AsyncIterable
-
+{
+ constructor(
+ client: Kernel,
+ request: Promise,
+ Page: new (...args: ConstructorParameters) => 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
- {
+ const page = await this;
+ for await (const item of page) {
+ yield item;
+ }
+ }
+}
+
+export type OffsetPaginationResponse
- = Item[];
+
+export interface OffsetPaginationParams {
+ offset?: number;
+
+ limit?: number;
+}
+
+export class OffsetPagination
- extends AbstractPage
- {
+ items: Array
- ;
+
+ has_more: boolean | null;
+
+ next_offset: number | null;
+
+ constructor(
+ client: Kernel,
+ response: Response,
+ body: OffsetPaginationResponse
- ,
+ 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,
+ },
+ };
+ }
+}
diff --git a/src/index.ts b/src/index.ts
index ac8023c..72d9bc0 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -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,
diff --git a/src/internal/utils/values.ts b/src/internal/utils/values.ts
index 667646d..1fc2af6 100644
--- a/src/internal/utils/values.ts
+++ b/src/internal/utils/values.ts
@@ -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);
diff --git a/src/pagination.ts b/src/pagination.ts
new file mode 100644
index 0000000..90bf015
--- /dev/null
+++ b/src/pagination.ts
@@ -0,0 +1,2 @@
+/** @deprecated Import from ./core/pagination instead */
+export * from './core/pagination';
diff --git a/src/resources/deployments.ts b/src/resources/deployments.ts
index 6cb5672..b9dfe5a 100644
--- a/src/resources/deployments.ts
+++ b/src/resources/deployments.ts
@@ -3,6 +3,7 @@
import { APIResource } from '../core/resource';
import * as Shared from './shared';
import { APIPromise } from '../core/api-promise';
+import { OffsetPagination, type OffsetPaginationParams, PagePromise } from '../core/pagination';
import { Stream } from '../core/streaming';
import { type Uploadable } from '../core/uploads';
import { buildHeaders } from '../internal/headers';
@@ -43,14 +44,20 @@ export class Deployments extends APIResource {
*
* @example
* ```ts
- * const deployments = await client.deployments.list();
+ * // Automatically fetches more pages as needed.
+ * for await (const deploymentListResponse of client.deployments.list()) {
+ * // ...
+ * }
* ```
*/
list(
query: DeploymentListParams | null | undefined = {},
options?: RequestOptions,
- ): APIPromise {
- return this._client.get('/deployments', { query, ...options });
+ ): PagePromise {
+ return this._client.getAPIList('/deployments', OffsetPagination, {
+ query,
+ ...options,
+ });
}
/**
@@ -77,6 +84,8 @@ export class Deployments extends APIResource {
}
}
+export type DeploymentListResponsesOffsetPagination = OffsetPagination;
+
/**
* An event representing the current state of a deployment.
*/
@@ -234,53 +243,49 @@ export interface DeploymentRetrieveResponse {
updated_at?: string | null;
}
-export type DeploymentListResponse = Array;
-
-export namespace DeploymentListResponse {
+/**
+ * Deployment record information.
+ */
+export interface DeploymentListResponse {
/**
- * Deployment record information.
+ * Unique identifier for the deployment
*/
- export interface DeploymentListResponseItem {
- /**
- * Unique identifier for the deployment
- */
- id: string;
+ id: string;
- /**
- * Timestamp when the deployment was created
- */
- created_at: string;
+ /**
+ * Timestamp when the deployment was created
+ */
+ created_at: string;
- /**
- * Deployment region code
- */
- region: 'aws.us-east-1a';
+ /**
+ * Deployment region code
+ */
+ region: 'aws.us-east-1a';
- /**
- * Current status of the deployment
- */
- status: 'queued' | 'in_progress' | 'running' | 'failed' | 'stopped';
+ /**
+ * Current status of the deployment
+ */
+ status: 'queued' | 'in_progress' | 'running' | 'failed' | 'stopped';
- /**
- * Relative path to the application entrypoint
- */
- entrypoint_rel_path?: string;
+ /**
+ * Relative path to the application entrypoint
+ */
+ entrypoint_rel_path?: string;
- /**
- * Environment variables configured for this deployment
- */
- env_vars?: { [key: string]: string };
+ /**
+ * Environment variables configured for this deployment
+ */
+ env_vars?: { [key: string]: string };
- /**
- * Status reason
- */
- status_reason?: string;
+ /**
+ * Status reason
+ */
+ status_reason?: string;
- /**
- * Timestamp when the deployment was last updated
- */
- updated_at?: string | null;
- }
+ /**
+ * Timestamp when the deployment was last updated
+ */
+ updated_at?: string | null;
}
/**
@@ -373,7 +378,7 @@ export interface DeploymentCreateParams {
version?: string;
}
-export interface DeploymentListParams {
+export interface DeploymentListParams extends OffsetPaginationParams {
/**
* Filter results by application name.
*/
@@ -394,6 +399,7 @@ export declare namespace Deployments {
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,
diff --git a/src/resources/index.ts b/src/resources/index.ts
index 6b0e380..6482d4f 100644
--- a/src/resources/index.ts
+++ b/src/resources/index.ts
@@ -22,6 +22,7 @@ export {
type DeploymentCreateParams,
type DeploymentListParams,
type DeploymentFollowParams,
+ type DeploymentListResponsesOffsetPagination,
} from './deployments';
export {
Invocations,
diff --git a/src/version.ts b/src/version.ts
index 9085e9d..945825f 100644
--- a/src/version.ts
+++ b/src/version.ts
@@ -1 +1 @@
-export const VERSION = '0.11.0'; // x-release-please-version
+export const VERSION = '0.11.1'; // x-release-please-version
diff --git a/tests/api-resources/deployments.test.ts b/tests/api-resources/deployments.test.ts
index 289fe0c..80f430f 100644
--- a/tests/api-resources/deployments.test.ts
+++ b/tests/api-resources/deployments.test.ts
@@ -63,7 +63,10 @@ describe('resource deployments', () => {
test.skip('list: request options and params are passed correctly', async () => {
// ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error
await expect(
- client.deployments.list({ app_name: 'app_name' }, { path: '/_stainless_unknown_path' }),
+ client.deployments.list(
+ { app_name: 'app_name', limit: 1, offset: 0 },
+ { path: '/_stainless_unknown_path' },
+ ),
).rejects.toThrow(Kernel.NotFoundError);
});