diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 097e558..b35fe33 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,10 @@ on: - 'integrated/**' - 'stl-preview-head/**' - 'stl-preview-base/**' + pull_request: + branches-ignore: + - 'stl-preview-head/**' + - 'stl-preview-base/**' jobs: lint: diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f1c1e58..bcd0522 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.5.0" + ".": "0.6.0" } diff --git a/.stats.yml b/.stats.yml index d654666..ba1c7c9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 11 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-4502c65bef0843a6ae96d23bba075433af6bab49b55b544b1522f63e7881c00c.yml -openapi_spec_hash: 3e67b77bbc8cd6155b8f66f3271f2643 -config_hash: c6bab7ac8da570a5abbcfb19db119b6b +configured_endpoints: 15 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-5d4e11bc46eeecee7363d56a9dfe946acee997d5b352c2b0a50c20e742c54d2d.yml +openapi_spec_hash: 333e53ad9c706296b9afdb8ff73bec8f +config_hash: 0fdf285ddd8dee229fd84ea57df9080f diff --git a/CHANGELOG.md b/CHANGELOG.md index 60707d6..6ba64d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,40 @@ # Changelog +## 0.6.0 (2025-06-18) + +Full Changelog: [v0.5.0...v0.6.0](https://github.com/onkernel/kernel-node-sdk/compare/v0.5.0...v0.6.0) + +### Features + +* **api:** update via SDK Studio ([ce675af](https://github.com/onkernel/kernel-node-sdk/commit/ce675afa43f72708fd26bd0ab9d37b43f2e4b645)) +* **api:** update via SDK Studio ([b4fbd8c](https://github.com/onkernel/kernel-node-sdk/commit/b4fbd8cd5287894a1e31c3369e6268676b5cfb93)) +* **api:** update via SDK Studio ([c6890ba](https://github.com/onkernel/kernel-node-sdk/commit/c6890ba4c2ccebc4be3388107de407ef833555d6)) +* **api:** update via SDK Studio ([842ec55](https://github.com/onkernel/kernel-node-sdk/commit/842ec5503a95b8a7bd43405cb0af36411610d14a)) +* **api:** update via SDK Studio ([b0652c7](https://github.com/onkernel/kernel-node-sdk/commit/b0652c76a0d9b8ff39c8d97d0087a305ed1bb0b7)) +* **api:** update via SDK Studio ([76a1ea8](https://github.com/onkernel/kernel-node-sdk/commit/76a1ea8d2d978b7872ff122f6b4457f361695e22)) +* **api:** update via SDK Studio ([371e317](https://github.com/onkernel/kernel-node-sdk/commit/371e3178a73439915a459dc0b7e968aeaf9f7e8f)) +* **api:** update via SDK Studio ([6e4f782](https://github.com/onkernel/kernel-node-sdk/commit/6e4f782824e8ec1956d31ddaff7073a0015a3014)) +* **api:** update via SDK Studio ([121219a](https://github.com/onkernel/kernel-node-sdk/commit/121219a8e7dab92398d7373afb84be06a9d15217)) +* **api:** update via SDK Studio ([567602f](https://github.com/onkernel/kernel-node-sdk/commit/567602fb83e9135938abd7cd080f864fc787f288)) +* **api:** update via SDK Studio ([ef99764](https://github.com/onkernel/kernel-node-sdk/commit/ef99764a42bc6243b028162eb1fcc88ae36eed41)) +* **api:** update via SDK Studio ([e4ad78d](https://github.com/onkernel/kernel-node-sdk/commit/e4ad78d21af5a7d009f55cea09da358780ca8d2d)) +* **api:** update via SDK Studio ([171423f](https://github.com/onkernel/kernel-node-sdk/commit/171423fb6af8fde206a1e32a1e8611f4b1d325be)) +* **client:** add support for endpoint-specific base URLs ([7b4d7a2](https://github.com/onkernel/kernel-node-sdk/commit/7b4d7a2da7ba44fa0e0648545eccb5e175cf8255)) + + +### Bug Fixes + +* publish script — handle NPM errors correctly ([16c70f4](https://github.com/onkernel/kernel-node-sdk/commit/16c70f444b06763b186c7138e16476d03b9638fe)) + + +### Chores + +* avoid type error in certain environments ([ec7be8d](https://github.com/onkernel/kernel-node-sdk/commit/ec7be8d0c90029c4fb5911afee057dcd73f6caa1)) +* **ci:** enable for pull requests ([0dc0e13](https://github.com/onkernel/kernel-node-sdk/commit/0dc0e130a319209e0debcb41df6a7e4e473a013b)) +* **client:** refactor imports ([801fb38](https://github.com/onkernel/kernel-node-sdk/commit/801fb38b66af7598d9bbc12d6964031f44f7a6ea)) +* **internal:** add pure annotations, make base APIResource abstract ([71cd518](https://github.com/onkernel/kernel-node-sdk/commit/71cd5185af173938e8bf933045de56dec7c04802)) +* **readme:** update badges ([48f24e9](https://github.com/onkernel/kernel-node-sdk/commit/48f24e9591eeb869dfeb38b6f52f28d6edfcd10e)) + ## 0.5.0 (2025-06-04) Full Changelog: [v0.4.0...v0.5.0](https://github.com/onkernel/kernel-node-sdk/compare/v0.4.0...v0.5.0) diff --git a/README.md b/README.md index 22327b8..4217f0f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Kernel TypeScript API Library -[![NPM version](https://img.shields.io/npm/v/@onkernel/sdk.svg)](https://npmjs.org/package/@onkernel/sdk) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/@onkernel/sdk) +[![NPM version]()](https://npmjs.org/package/@onkernel/sdk) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/@onkernel/sdk) This library provides convenient access to the Kernel REST API from server-side TypeScript or JavaScript. diff --git a/api.md b/api.md index 6cef980..2f61707 100644 --- a/api.md +++ b/api.md @@ -1,3 +1,27 @@ +# Shared + +Types: + +- ErrorDetail +- ErrorEvent +- ErrorModel +- LogEvent + +# Deployments + +Types: + +- DeploymentStateEvent +- DeploymentCreateResponse +- DeploymentRetrieveResponse +- DeploymentFollowResponse + +Methods: + +- client.deployments.create({ ...params }) -> DeploymentCreateResponse +- client.deployments.retrieve(id) -> DeploymentRetrieveResponse +- client.deployments.follow(id) -> DeploymentFollowResponse + # Apps Types: @@ -20,19 +44,22 @@ Methods: - client.apps.deployments.create({ ...params }) -> DeploymentCreateResponse - client.apps.deployments.follow(id) -> DeploymentFollowResponse -## Invocations +# Invocations Types: -- InvocationCreateResponse -- InvocationRetrieveResponse -- InvocationUpdateResponse +- InvocationStateEvent +- InvocationCreateResponse +- InvocationRetrieveResponse +- InvocationUpdateResponse +- InvocationFollowResponse Methods: -- client.apps.invocations.create({ ...params }) -> InvocationCreateResponse -- client.apps.invocations.retrieve(id) -> InvocationRetrieveResponse -- client.apps.invocations.update(id, { ...params }) -> InvocationUpdateResponse +- client.invocations.create({ ...params }) -> InvocationCreateResponse +- client.invocations.retrieve(id) -> InvocationRetrieveResponse +- client.invocations.update(id, { ...params }) -> InvocationUpdateResponse +- client.invocations.follow(id) -> InvocationFollowResponse # Browsers diff --git a/bin/publish-npm b/bin/publish-npm index 2505dec..fa2243d 100644 --- a/bin/publish-npm +++ b/bin/publish-npm @@ -7,15 +7,35 @@ npm config set '//registry.npmjs.org/:_authToken' "$NPM_TOKEN" yarn build cd dist +# Get package name and version from package.json +PACKAGE_NAME="$(jq -r -e '.name' ./package.json)" +VERSION="$(jq -r -e '.version' ./package.json)" + # Get latest version from npm # -# If the package doesn't exist, yarn will return -# {"type":"error","data":"Received invalid response from npm."} -# where .data.version doesn't exist so LAST_VERSION will be an empty string. -LAST_VERSION="$(yarn info --json 2> /dev/null | jq -r '.data.version')" - -# Get current version from package.json -VERSION="$(node -p "require('./package.json').version")" +# If the package doesn't exist, npm will return: +# { +# "error": { +# "code": "E404", +# "summary": "Unpublished on 2025-06-05T09:54:53.528Z", +# "detail": "'the_package' is not in this registry..." +# } +# } +NPM_INFO="$(npm view "$PACKAGE_NAME" version --json 2>/dev/null || true)" + +# Check if we got an E404 error +if echo "$NPM_INFO" | jq -e '.error.code == "E404"' > /dev/null 2>&1; then + # Package doesn't exist yet, no last version + LAST_VERSION="" +elif echo "$NPM_INFO" | jq -e '.error' > /dev/null 2>&1; then + # Report other errors + echo "ERROR: npm returned unexpected data:" + echo "$NPM_INFO" + exit 1 +else + # Success - get the version + LAST_VERSION=$(echo "$NPM_INFO" | jq -r '.') # strip quotes +fi # Check if current version is pre-release (e.g. alpha / beta / rc) CURRENT_IS_PRERELEASE=false diff --git a/package.json b/package.json index 6b06ec5..05bd20a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@onkernel/sdk", - "version": "0.5.0", + "version": "0.6.0", "description": "The official TypeScript library for the Kernel API", "author": "Kernel <>", "types": "dist/index.d.ts", @@ -42,7 +42,7 @@ "publint": "^0.2.12", "ts-jest": "^29.1.0", "ts-node": "^10.5.0", - "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.4/tsc-multi-1.1.4.tgz", + "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz", "tsconfig-paths": "^4.0.0", "typescript": "5.8.3" }, diff --git a/scripts/build b/scripts/build index b2ad914..a008cb0 100755 --- a/scripts/build +++ b/scripts/build @@ -31,7 +31,7 @@ fi node scripts/utils/make-dist-package-json.cjs > dist/package.json # build to .js/.mjs/.d.ts files -npm exec tsc-multi +./node_modules/.bin/tsc-multi # we need to patch index.js so that `new module.exports()` works for cjs backwards # compat. No way to get that from index.ts because it would cause compile errors # when building .mjs diff --git a/src/client.ts b/src/client.ts index 21d63a6..d27d6c9 100644 --- a/src/client.ts +++ b/src/client.ts @@ -5,7 +5,6 @@ import type { HTTPMethod, PromiseOrValue, MergedRequestInit, FinalizedRequestIni import { uuid4 } from './internal/utils/uuid'; import { validatePositiveInteger, isAbsoluteURL, safeJSON } from './internal/utils/values'; import { sleep } from './internal/utils/sleep'; -import { type Logger, type LogLevel, parseLogLevel } from './internal/utils/log'; export type { Logger, LogLevel } from './internal/utils/log'; import { castToError, isAbortError } from './internal/errors'; import type { APIResponseProps } from './internal/parse'; @@ -17,9 +16,6 @@ import * as Errors from './core/error'; import * as Uploads from './core/uploads'; import * as API from './resources/index'; import { APIPromise } from './core/api-promise'; -import { type Fetch } from './internal/builtin-types'; -import { HeadersLike, NullableHeaders, buildHeaders } from './internal/headers'; -import { FinalRequestOptions, RequestOptions } from './internal/request-options'; import { BrowserCreateParams, BrowserCreateResponse, @@ -29,11 +25,38 @@ import { BrowserRetrieveResponse, Browsers, } from './resources/browsers'; -import { readEnv } from './internal/utils/env'; -import { formatRequestDetails, loggerFor } from './internal/utils/log'; -import { isEmptyObj } from './internal/utils/values'; +import { + DeploymentCreateParams, + DeploymentCreateResponse, + DeploymentFollowResponse, + DeploymentRetrieveResponse, + DeploymentStateEvent, + Deployments, +} from './resources/deployments'; import { KernelApp } from './core/app-framework'; +import { + InvocationCreateParams, + InvocationCreateResponse, + InvocationFollowResponse, + InvocationRetrieveResponse, + InvocationStateEvent, + InvocationUpdateParams, + InvocationUpdateResponse, + Invocations, +} from './resources/invocations'; import { AppListParams, AppListResponse, Apps } from './resources/apps/apps'; +import { type Fetch } from './internal/builtin-types'; +import { HeadersLike, NullableHeaders, buildHeaders } from './internal/headers'; +import { FinalRequestOptions, RequestOptions } from './internal/request-options'; +import { readEnv } from './internal/utils/env'; +import { + type LogLevel, + type Logger, + formatRequestDetails, + loggerFor, + parseLogLevel, +} from './internal/utils/log'; +import { isEmptyObj } from './internal/utils/values'; const environments = { production: 'https://api.onkernel.com/', @@ -216,6 +239,13 @@ export class Kernel { }); } + /** + * Check whether the base URL is set to its default. + */ + #baseURLOverridden(): boolean { + return this.baseURL !== environments[this._options.environment || 'production']; + } + protected defaultQuery(): Record | undefined { return this._options.defaultQuery; } @@ -265,11 +295,16 @@ export class Kernel { return Errors.APIError.generate(status, error, message, headers); } - buildURL(path: string, query: Record | null | undefined): string { + buildURL( + path: string, + query: Record | null | undefined, + defaultBaseURL?: string | undefined, + ): string { + const baseURL = (!this.#baseURLOverridden() && defaultBaseURL) || this.baseURL; const url = isAbsoluteURL(path) ? new URL(path) - : new URL(this.baseURL + (this.baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path)); + : new URL(baseURL + (baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path)); const defaultQuery = this.defaultQuery(); if (!isEmptyObj(defaultQuery)) { @@ -610,9 +645,9 @@ export class Kernel { { retryCount = 0 }: { retryCount?: number } = {}, ): { req: FinalizedRequestInit; url: string; timeout: number } { const options = { ...inputOptions }; - const { method, path, query } = options; + const { method, path, query, defaultBaseURL } = options; - const url = this.buildURL(path!, query as Record); + const url = this.buildURL(path!, query as Record, defaultBaseURL); if ('timeout' in options) validatePositiveInteger('timeout', options.timeout); options.timeout = options.timeout ?? this.timeout; const { bodyHeaders, body } = this.buildBody({ options }); @@ -735,16 +770,40 @@ export class Kernel { static toFile = Uploads.toFile; + deployments: API.Deployments = new API.Deployments(this); apps: API.Apps = new API.Apps(this); + invocations: API.Invocations = new API.Invocations(this); browsers: API.Browsers = new API.Browsers(this); } +Kernel.Deployments = Deployments; Kernel.Apps = Apps; +Kernel.Invocations = Invocations; Kernel.Browsers = Browsers; export declare namespace Kernel { export type RequestOptions = Opts.RequestOptions; + export { + Deployments as Deployments, + type DeploymentStateEvent as DeploymentStateEvent, + type DeploymentCreateResponse as DeploymentCreateResponse, + type DeploymentRetrieveResponse as DeploymentRetrieveResponse, + type DeploymentFollowResponse as DeploymentFollowResponse, + type DeploymentCreateParams as DeploymentCreateParams, + }; + export { Apps as Apps, type AppListResponse as AppListResponse, type AppListParams as AppListParams }; + export { + Invocations as Invocations, + type InvocationStateEvent as InvocationStateEvent, + type InvocationCreateResponse as InvocationCreateResponse, + type InvocationRetrieveResponse as InvocationRetrieveResponse, + type InvocationUpdateResponse as InvocationUpdateResponse, + type InvocationFollowResponse as InvocationFollowResponse, + type InvocationCreateParams as InvocationCreateParams, + type InvocationUpdateParams as InvocationUpdateParams, + }; + export { Browsers as Browsers, type BrowserPersistence as BrowserPersistence, @@ -754,4 +813,9 @@ export declare namespace Kernel { type BrowserCreateParams as BrowserCreateParams, type BrowserDeleteParams as BrowserDeleteParams, }; + + export type ErrorDetail = API.ErrorDetail; + export type ErrorEvent = API.ErrorEvent; + export type ErrorModel = API.ErrorModel; + export type LogEvent = API.LogEvent; } diff --git a/src/core/resource.ts b/src/core/resource.ts index b8cfadd..f1dd803 100644 --- a/src/core/resource.ts +++ b/src/core/resource.ts @@ -2,7 +2,7 @@ import type { Kernel } from '../client'; -export class APIResource { +export abstract class APIResource { protected _client: Kernel; constructor(client: Kernel) { diff --git a/src/internal/headers.ts b/src/internal/headers.ts index 5cc03ce..c724a9d 100644 --- a/src/internal/headers.ts +++ b/src/internal/headers.ts @@ -1,5 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import { isReadonlyArray } from './utils/values'; + type HeaderValue = string | undefined | null; export type HeadersLike = | Headers @@ -9,7 +11,7 @@ export type HeadersLike = | null | NullableHeaders; -const brand_privateNullableHeaders = Symbol('brand.privateNullableHeaders'); +const brand_privateNullableHeaders = /* @__PURE__ */ Symbol('brand.privateNullableHeaders'); /** * @internal @@ -25,8 +27,6 @@ export type NullableHeaders = { nulls: Set; }; -const isArray = Array.isArray as (val: unknown) => val is readonly unknown[]; - function* iterateHeaders(headers: HeadersLike): IterableIterator { if (!headers) return; @@ -43,7 +43,7 @@ function* iterateHeaders(headers: HeadersLike): IterableIterator; if (headers instanceof Headers) { iter = headers.entries(); - } else if (isArray(headers)) { + } else if (isReadonlyArray(headers)) { iter = headers; } else { shouldClear = true; @@ -52,7 +52,7 @@ function* iterateHeaders(headers: HeadersLike): IterableIterator>(); +const supportsFormDataMap = /** @__PURE__ */ new WeakMap>(); /** * node-fetch doesn't support the global FormData object in recent node versions. Instead of sending @@ -138,7 +138,7 @@ export const createForm = async >( // We check for Blob not File because Bun.File doesn't inherit from File, // but they both inherit from Blob and have a `name` property at runtime. -const isNamedBlob = (value: object) => value instanceof Blob && 'name' in value; +const isNamedBlob = (value: unknown) => value instanceof Blob && 'name' in value; const isUploadable = (value: unknown) => typeof value === 'object' && diff --git a/src/internal/utils/log.ts b/src/internal/utils/log.ts index 4865262..44d8f65 100644 --- a/src/internal/utils/log.ts +++ b/src/internal/utils/log.ts @@ -58,7 +58,7 @@ const noopLogger = { debug: noop, }; -let cachedLoggers = new WeakMap(); +let cachedLoggers = /** @__PURE__ */ new WeakMap(); export function loggerFor(client: Kernel): Logger { const logger = client.logger; diff --git a/src/internal/utils/path.ts b/src/internal/utils/path.ts index e9bcaf5..8abf278 100644 --- a/src/internal/utils/path.ts +++ b/src/internal/utils/path.ts @@ -60,4 +60,4 @@ export const createPathTagFunction = (pathEncoder = encodeURIPath) => /** * URI-encodes path params and ensures no unsafe /./ or /../ path segments are introduced. */ -export const path = createPathTagFunction(encodeURIPath); +export const path = /* @__PURE__ */ createPathTagFunction(encodeURIPath); diff --git a/src/internal/utils/values.ts b/src/internal/utils/values.ts index 0953aea..667646d 100644 --- a/src/internal/utils/values.ts +++ b/src/internal/utils/values.ts @@ -9,6 +9,9 @@ export const isAbsoluteURL = (url: string): boolean => { return startsWithSchemeRegexp.test(url); }; +export let isArray = (val: unknown): val is unknown[] => ((isArray = Array.isArray), isArray(val)); +export let isReadonlyArray = isArray as (val: unknown) => val is readonly unknown[]; + /** Returns an object if the given value isn't an object, otherwise returns as-is */ export function maybeObj(x: unknown): object { if (typeof x !== 'object') { diff --git a/src/resources/apps/apps.ts b/src/resources/apps/apps.ts index 2b982f6..7f8831e 100644 --- a/src/resources/apps/apps.ts +++ b/src/resources/apps/apps.ts @@ -8,21 +8,11 @@ import { DeploymentFollowResponse, Deployments, } from './deployments'; -import * as InvocationsAPI from './invocations'; -import { - InvocationCreateParams, - InvocationCreateResponse, - InvocationRetrieveResponse, - InvocationUpdateParams, - InvocationUpdateResponse, - Invocations, -} from './invocations'; import { APIPromise } from '../../core/api-promise'; import { RequestOptions } from '../../internal/request-options'; export class Apps extends APIResource { deployments: DeploymentsAPI.Deployments = new DeploymentsAPI.Deployments(this._client); - invocations: InvocationsAPI.Invocations = new InvocationsAPI.Invocations(this._client); /** * List applications. Optionally filter by app name and/or version label. @@ -57,7 +47,7 @@ export namespace AppListResponse { /** * Deployment region code */ - region: string; + region: 'aws.us-east-1a'; /** * Version label for the application @@ -84,7 +74,6 @@ export interface AppListParams { } Apps.Deployments = Deployments; -Apps.Invocations = Invocations; export declare namespace Apps { export { type AppListResponse as AppListResponse, type AppListParams as AppListParams }; @@ -95,13 +84,4 @@ export declare namespace Apps { type DeploymentFollowResponse as DeploymentFollowResponse, type DeploymentCreateParams as DeploymentCreateParams, }; - - export { - Invocations as Invocations, - type InvocationCreateResponse as InvocationCreateResponse, - type InvocationRetrieveResponse as InvocationRetrieveResponse, - type InvocationUpdateResponse as InvocationUpdateResponse, - type InvocationCreateParams as InvocationCreateParams, - type InvocationUpdateParams as InvocationUpdateParams, - }; } diff --git a/src/resources/apps/deployments.ts b/src/resources/apps/deployments.ts index a5cf463..1581c8e 100644 --- a/src/resources/apps/deployments.ts +++ b/src/resources/apps/deployments.ts @@ -1,6 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../../core/resource'; +import * as Shared from '../shared'; import { APIPromise } from '../../core/api-promise'; import { Stream } from '../../core/streaming'; import { type Uploadable } from '../../core/uploads'; @@ -96,7 +97,7 @@ export namespace DeploymentCreateResponse { export type DeploymentFollowResponse = | DeploymentFollowResponse.StateEvent | DeploymentFollowResponse.StateUpdateEvent - | DeploymentFollowResponse.LogEvent; + | Shared.LogEvent; export namespace DeploymentFollowResponse { /** @@ -138,26 +139,6 @@ export namespace DeploymentFollowResponse { */ timestamp?: string; } - - /** - * A log entry from the application. - */ - export interface LogEvent { - /** - * Event type identifier (always "log"). - */ - event: 'log'; - - /** - * Log message text. - */ - message: string; - - /** - * Time the log entry was produced. - */ - timestamp?: string; - } } export interface DeploymentCreateParams { diff --git a/src/resources/apps/index.ts b/src/resources/apps/index.ts index 29751c0..a32f41e 100644 --- a/src/resources/apps/index.ts +++ b/src/resources/apps/index.ts @@ -7,11 +7,3 @@ export { type DeploymentFollowResponse, type DeploymentCreateParams, } from './deployments'; -export { - Invocations, - type InvocationCreateResponse, - type InvocationRetrieveResponse, - type InvocationUpdateResponse, - type InvocationCreateParams, - type InvocationUpdateParams, -} from './invocations'; diff --git a/src/resources/deployments.ts b/src/resources/deployments.ts new file mode 100644 index 0000000..bc247fc --- /dev/null +++ b/src/resources/deployments.ts @@ -0,0 +1,326 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../core/resource'; +import * as Shared from './shared'; +import { APIPromise } from '../core/api-promise'; +import { Stream } from '../core/streaming'; +import { type Uploadable } from '../core/uploads'; +import { buildHeaders } from '../internal/headers'; +import { RequestOptions } from '../internal/request-options'; +import { multipartFormRequestOptions } from '../internal/uploads'; +import { path } from '../internal/utils/path'; + +export class Deployments extends APIResource { + /** + * Create a new deployment. + * + * @example + * ```ts + * const deployment = await client.deployments.create({ + * entrypoint_rel_path: 'src/app.py', + * file: fs.createReadStream('path/to/file'), + * }); + * ``` + */ + create(body: DeploymentCreateParams, options?: RequestOptions): APIPromise { + return this._client.post('/deployments', multipartFormRequestOptions({ body, ...options }, this._client)); + } + + /** + * Get information about a deployment's status. + * + * @example + * ```ts + * const deployment = await client.deployments.retrieve('id'); + * ``` + */ + retrieve(id: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/deployments/${id}`, options); + } + + /** + * Establishes a Server-Sent Events (SSE) stream that delivers real-time logs and + * status updates for a deployment. The stream terminates automatically once the + * deployment reaches a terminal state. + * + * @example + * ```ts + * const response = await client.deployments.follow('id'); + * ``` + */ + follow(id: string, options?: RequestOptions): APIPromise> { + return this._client.get(path`/deployments/${id}/events`, { + ...options, + headers: buildHeaders([{ Accept: 'text/event-stream' }, options?.headers]), + stream: true, + }) as APIPromise>; + } +} + +/** + * An event representing the current state of a deployment. + */ +export interface DeploymentStateEvent { + /** + * Deployment record information. + */ + deployment: DeploymentStateEvent.Deployment; + + /** + * Event type identifier (always "deployment_state"). + */ + event: 'deployment_state'; + + /** + * Time the state was reported. + */ + timestamp: string; +} + +export namespace DeploymentStateEvent { + /** + * Deployment record information. + */ + export interface Deployment { + /** + * Unique identifier for the deployment + */ + id: string; + + /** + * Timestamp when the deployment was created + */ + created_at: string; + + /** + * Deployment region code + */ + region: 'aws.us-east-1a'; + + /** + * Current status of the deployment + */ + status: 'queued' | 'in_progress' | 'running' | 'failed' | 'stopped'; + + /** + * Relative path to the application entrypoint + */ + entrypoint_rel_path?: string; + + /** + * Environment variables configured for this deployment + */ + env_vars?: Record; + + /** + * Status reason + */ + status_reason?: string; + + /** + * Timestamp when the deployment was last updated + */ + updated_at?: string | null; + } +} + +/** + * Deployment record information. + */ +export interface DeploymentCreateResponse { + /** + * Unique identifier for the deployment + */ + id: string; + + /** + * Timestamp when the deployment was created + */ + created_at: string; + + /** + * Deployment region code + */ + region: 'aws.us-east-1a'; + + /** + * Current status of the deployment + */ + status: 'queued' | 'in_progress' | 'running' | 'failed' | 'stopped'; + + /** + * Relative path to the application entrypoint + */ + entrypoint_rel_path?: string; + + /** + * Environment variables configured for this deployment + */ + env_vars?: Record; + + /** + * Status reason + */ + status_reason?: string; + + /** + * Timestamp when the deployment was last updated + */ + updated_at?: string | null; +} + +/** + * Deployment record information. + */ +export interface DeploymentRetrieveResponse { + /** + * Unique identifier for the deployment + */ + id: string; + + /** + * Timestamp when the deployment was created + */ + created_at: string; + + /** + * Deployment region code + */ + region: 'aws.us-east-1a'; + + /** + * Current status of the deployment + */ + status: 'queued' | 'in_progress' | 'running' | 'failed' | 'stopped'; + + /** + * Relative path to the application entrypoint + */ + entrypoint_rel_path?: string; + + /** + * Environment variables configured for this deployment + */ + env_vars?: Record; + + /** + * Status reason + */ + status_reason?: string; + + /** + * Timestamp when the deployment was last updated + */ + updated_at?: string | null; +} + +/** + * Union type representing any deployment event. + */ +export type DeploymentFollowResponse = + | Shared.LogEvent + | DeploymentStateEvent + | DeploymentFollowResponse.AppVersionSummaryEvent + | Shared.ErrorEvent; + +export namespace DeploymentFollowResponse { + /** + * Summary of an application version. + */ + export interface AppVersionSummaryEvent { + /** + * Unique identifier for the app version + */ + id: string; + + /** + * List of actions available on the app + */ + actions: Array; + + /** + * Name of the application + */ + app_name: string; + + /** + * Event type identifier (always "app_version_summary"). + */ + event: 'app_version_summary'; + + /** + * Deployment region code + */ + region: 'aws.us-east-1a'; + + /** + * Time the state was reported. + */ + timestamp: string; + + /** + * Version label for the application + */ + version: string; + + /** + * Environment variables configured for this app version + */ + env_vars?: Record; + } + + export namespace AppVersionSummaryEvent { + /** + * An action available on the app + */ + export interface Action { + /** + * Name of the action + */ + name: string; + } + } +} + +export interface DeploymentCreateParams { + /** + * Relative path to the entrypoint of the application + */ + entrypoint_rel_path: string; + + /** + * ZIP file containing the application source directory + */ + file: Uploadable; + + /** + * Map of environment variables to set for the deployed application. Each key-value + * pair represents an environment variable. + */ + env_vars?: Record; + + /** + * Allow overwriting an existing app version + */ + force?: boolean; + + /** + * Region for deployment. Currently we only support "aws.us-east-1a" + */ + region?: 'aws.us-east-1a'; + + /** + * Version of the application. Can be any string. + */ + version?: string; +} + +export declare namespace Deployments { + export { + type DeploymentStateEvent as DeploymentStateEvent, + type DeploymentCreateResponse as DeploymentCreateResponse, + type DeploymentRetrieveResponse as DeploymentRetrieveResponse, + type DeploymentFollowResponse as DeploymentFollowResponse, + type DeploymentCreateParams as DeploymentCreateParams, + }; +} diff --git a/src/resources/index.ts b/src/resources/index.ts index 5faf2a5..8c96ec4 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -1,5 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +export * from './shared'; export { Apps, type AppListResponse, type AppListParams } from './apps/apps'; export { Browsers, @@ -10,3 +11,21 @@ export { type BrowserCreateParams, type BrowserDeleteParams, } from './browsers'; +export { + Deployments, + type DeploymentStateEvent, + type DeploymentCreateResponse, + type DeploymentRetrieveResponse, + type DeploymentFollowResponse, + type DeploymentCreateParams, +} from './deployments'; +export { + Invocations, + type InvocationStateEvent, + type InvocationCreateResponse, + type InvocationRetrieveResponse, + type InvocationUpdateResponse, + type InvocationFollowResponse, + type InvocationCreateParams, + type InvocationUpdateParams, +} from './invocations'; diff --git a/src/resources/apps/invocations.ts b/src/resources/invocations.ts similarity index 60% rename from src/resources/apps/invocations.ts rename to src/resources/invocations.ts index 7479550..40e16b6 100644 --- a/src/resources/apps/invocations.ts +++ b/src/resources/invocations.ts @@ -1,9 +1,12 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../core/resource'; -import { APIPromise } from '../../core/api-promise'; -import { RequestOptions } from '../../internal/request-options'; -import { path } from '../../internal/utils/path'; +import { APIResource } from '../core/resource'; +import * as Shared from './shared'; +import { APIPromise } from '../core/api-promise'; +import { Stream } from '../core/streaming'; +import { buildHeaders } from '../internal/headers'; +import { RequestOptions } from '../internal/request-options'; +import { path } from '../internal/utils/path'; export class Invocations extends APIResource { /** @@ -11,7 +14,7 @@ export class Invocations extends APIResource { * * @example * ```ts - * const invocation = await client.apps.invocations.create({ + * const invocation = await client.invocations.create({ * action_name: 'analyze', * app_name: 'my-app', * version: '1.0.0', @@ -27,7 +30,7 @@ export class Invocations extends APIResource { * * @example * ```ts - * const invocation = await client.apps.invocations.retrieve( + * const invocation = await client.invocations.retrieve( * 'rr33xuugxj9h0bkf1rdt2bet', * ); * ``` @@ -41,10 +44,9 @@ export class Invocations extends APIResource { * * @example * ```ts - * const invocation = await client.apps.invocations.update( - * 'id', - * { status: 'succeeded' }, - * ); + * const invocation = await client.invocations.update('id', { + * status: 'succeeded', + * }); * ``` */ update( @@ -54,6 +56,92 @@ export class Invocations extends APIResource { ): APIPromise { return this._client.patch(path`/invocations/${id}`, { body, ...options }); } + + /** + * Establishes a Server-Sent Events (SSE) stream that delivers real-time logs and + * status updates for an invocation. The stream terminates automatically once the + * invocation reaches a terminal state. + * + * @example + * ```ts + * const response = await client.invocations.follow('id'); + * ``` + */ + follow(id: string, options?: RequestOptions): APIPromise> { + return this._client.get(path`/invocations/${id}/events`, { + ...options, + headers: buildHeaders([{ Accept: 'text/event-stream' }, options?.headers]), + stream: true, + }) as APIPromise>; + } +} + +/** + * An event representing the current state of an invocation. + */ +export interface InvocationStateEvent { + /** + * Event type identifier (always "invocation_state"). + */ + event: 'invocation_state'; + + invocation: InvocationStateEvent.Invocation; + + /** + * Time the state was reported. + */ + timestamp: string; +} + +export namespace InvocationStateEvent { + export interface Invocation { + /** + * ID of the invocation + */ + id: string; + + /** + * Name of the action invoked + */ + action_name: string; + + /** + * Name of the application + */ + app_name: string; + + /** + * RFC 3339 Nanoseconds timestamp when the invocation started + */ + started_at: string; + + /** + * Status of the invocation + */ + status: 'queued' | 'running' | 'succeeded' | 'failed'; + + /** + * RFC 3339 Nanoseconds timestamp when the invocation finished (null if still + * running) + */ + finished_at?: string | null; + + /** + * Output produced by the action, rendered as a JSON string. This could be: string, + * number, boolean, array, object, or null. + */ + output?: string; + + /** + * Payload provided to the invocation. This is a string that can be parsed as JSON. + */ + payload?: string; + + /** + * Status reason + */ + status_reason?: string; + } } export interface InvocationCreateResponse { @@ -177,6 +265,11 @@ export interface InvocationUpdateResponse { status_reason?: string; } +/** + * Union type representing any invocation event. + */ +export type InvocationFollowResponse = Shared.LogEvent | InvocationStateEvent | Shared.ErrorEvent; + export interface InvocationCreateParams { /** * Name of the action to invoke @@ -219,9 +312,11 @@ export interface InvocationUpdateParams { export declare namespace Invocations { export { + type InvocationStateEvent as InvocationStateEvent, type InvocationCreateResponse as InvocationCreateResponse, type InvocationRetrieveResponse as InvocationRetrieveResponse, type InvocationUpdateResponse as InvocationUpdateResponse, + type InvocationFollowResponse as InvocationFollowResponse, type InvocationCreateParams as InvocationCreateParams, type InvocationUpdateParams as InvocationUpdateParams, }; diff --git a/src/resources/shared.ts b/src/resources/shared.ts new file mode 100644 index 0000000..1760bd4 --- /dev/null +++ b/src/resources/shared.ts @@ -0,0 +1,69 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export interface ErrorDetail { + /** + * Lower-level error code providing more specific detail + */ + code?: string; + + /** + * Further detail about the error + */ + message?: string; +} + +/** + * An error event from the application. + */ +export interface ErrorEvent { + error: ErrorModel; + + /** + * Event type identifier (always "error"). + */ + event: 'error'; + + /** + * Time the error occurred. + */ + timestamp: string; +} + +export interface ErrorModel { + /** + * Application-specific error code (machine-readable) + */ + code: string; + + /** + * Human-readable error description for debugging + */ + message: string; + + /** + * Additional error details (for multiple errors) + */ + details?: Array; + + inner_error?: ErrorDetail; +} + +/** + * A log entry from the application. + */ +export interface LogEvent { + /** + * Event type identifier (always "log"). + */ + event: 'log'; + + /** + * Log message text. + */ + message: string; + + /** + * Time the log entry was produced. + */ + timestamp: string; +} diff --git a/src/version.ts b/src/version.ts index 1f5d158..30c2817 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.5.0'; // x-release-please-version +export const VERSION = '0.6.0'; // x-release-please-version diff --git a/tests/api-resources/deployments.test.ts b/tests/api-resources/deployments.test.ts new file mode 100644 index 0000000..d8d0b1d --- /dev/null +++ b/tests/api-resources/deployments.test.ts @@ -0,0 +1,61 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Kernel, { toFile } from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource deployments', () => { + // skipped: tests are disabled for the time being + test.skip('create: only required params', async () => { + const responsePromise = client.deployments.create({ + entrypoint_rel_path: 'src/app.py', + file: await toFile(Buffer.from('# my file contents'), 'README.md'), + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // skipped: tests are disabled for the time being + test.skip('create: required and optional params', async () => { + const response = await client.deployments.create({ + entrypoint_rel_path: 'src/app.py', + file: await toFile(Buffer.from('# my file contents'), 'README.md'), + env_vars: { foo: 'string' }, + force: false, + region: 'aws.us-east-1a', + version: '1.0.0', + }); + }); + + // skipped: tests are disabled for the time being + test.skip('retrieve', async () => { + const responsePromise = client.deployments.retrieve('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // skipped: currently no good way to test endpoints with content type text/event-stream, Prism mock server will fail + test.skip('follow', async () => { + const responsePromise = client.deployments.follow('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); +}); diff --git a/tests/api-resources/apps/invocations.test.ts b/tests/api-resources/invocations.test.ts similarity index 69% rename from tests/api-resources/apps/invocations.test.ts rename to tests/api-resources/invocations.test.ts index 9a1b028..2201a59 100644 --- a/tests/api-resources/apps/invocations.test.ts +++ b/tests/api-resources/invocations.test.ts @@ -10,7 +10,7 @@ const client = new Kernel({ describe('resource invocations', () => { // skipped: tests are disabled for the time being test.skip('create: only required params', async () => { - const responsePromise = client.apps.invocations.create({ + const responsePromise = client.invocations.create({ action_name: 'analyze', app_name: 'my-app', version: '1.0.0', @@ -26,7 +26,7 @@ describe('resource invocations', () => { // skipped: tests are disabled for the time being test.skip('create: required and optional params', async () => { - const response = await client.apps.invocations.create({ + const response = await client.invocations.create({ action_name: 'analyze', app_name: 'my-app', version: '1.0.0', @@ -37,7 +37,7 @@ describe('resource invocations', () => { // skipped: tests are disabled for the time being test.skip('retrieve', async () => { - const responsePromise = client.apps.invocations.retrieve('rr33xuugxj9h0bkf1rdt2bet'); + const responsePromise = client.invocations.retrieve('rr33xuugxj9h0bkf1rdt2bet'); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -49,7 +49,7 @@ describe('resource invocations', () => { // skipped: tests are disabled for the time being test.skip('update: only required params', async () => { - const responsePromise = client.apps.invocations.update('id', { status: 'succeeded' }); + const responsePromise = client.invocations.update('id', { status: 'succeeded' }); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -61,6 +61,18 @@ describe('resource invocations', () => { // skipped: tests are disabled for the time being test.skip('update: required and optional params', async () => { - const response = await client.apps.invocations.update('id', { status: 'succeeded', output: 'output' }); + const response = await client.invocations.update('id', { status: 'succeeded', output: 'output' }); + }); + + // skipped: currently no good way to test endpoints with content type text/event-stream, Prism mock server will fail + test.skip('follow', async () => { + const responsePromise = client.invocations.follow('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); }); }); diff --git a/tests/index.test.ts b/tests/index.test.ts index b452e0f..5c12c9a 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -323,6 +323,28 @@ describe('instantiate client', () => { const client = new Kernel({ apiKey: 'My API Key', baseURL: null, environment: 'production' }); expect(client.baseURL).toEqual('https://api.onkernel.com/'); }); + + test('in request options', () => { + const client = new Kernel({ apiKey: 'My API Key' }); + expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( + 'http://localhost:5000/option/foo', + ); + }); + + test('in request options overridden by client options', () => { + const client = new Kernel({ apiKey: 'My API Key', baseURL: 'http://localhost:5000/client' }); + expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( + 'http://localhost:5000/client/foo', + ); + }); + + test('in request options overridden by env variable', () => { + process.env['KERNEL_BASE_URL'] = 'http://localhost:5000/env'; + const client = new Kernel({ apiKey: 'My API Key' }); + expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( + 'http://localhost:5000/env/foo', + ); + }); }); test('maxRetries option is correctly set', () => { diff --git a/tsc-multi.json b/tsc-multi.json index 170bac7..384ddac 100644 --- a/tsc-multi.json +++ b/tsc-multi.json @@ -1,7 +1,15 @@ { "targets": [ - { "extname": ".js", "module": "commonjs", "shareHelpers": "internal/tslib.js" }, - { "extname": ".mjs", "module": "esnext", "shareHelpers": "internal/tslib.mjs" } + { + "extname": ".js", + "module": "commonjs", + "shareHelpers": "internal/tslib.js" + }, + { + "extname": ".mjs", + "module": "esnext", + "shareHelpers": "internal/tslib.mjs" + } ], "projects": ["tsconfig.build.json"] } diff --git a/yarn.lock b/yarn.lock index 49d3eb8..58c08d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3283,9 +3283,9 @@ ts-node@^10.5.0: v8-compile-cache-lib "^3.0.0" yn "3.1.1" -"tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.4/tsc-multi-1.1.4.tgz": - version "1.1.4" - resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.4/tsc-multi-1.1.4.tgz#cbed459a9e902f5295ec3daaf1c7aa3b10427e55" +"tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz": + version "1.1.8" + resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz#f544b359b8f05e607771ffacc280e58201476b04" dependencies: debug "^4.3.7" fast-glob "^3.3.2"