From 59e2eec2e1cf4da65008f6e51ba5c298acf4a595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Gr=C3=B8ndahl?= Date: Mon, 22 Jun 2026 16:04:28 +0200 Subject: [PATCH 1/2] docs: add TypeScript SDK reference section Adds four pages under typescript-sdk/ covering overview, retries and timeouts, pagination, and error handling. Registers the SDK under a new "SDKs" menu item in the Reference tab, positioned after CLI and API reference. --- config/navigation.json | 26 +++- typescript-sdk/error-handling.mdx | 101 ++++++++++++++ typescript-sdk/index.mdx | 178 ++++++++++++++++++++++++ typescript-sdk/pagination.mdx | 106 ++++++++++++++ typescript-sdk/retries-and-timeouts.mdx | 100 +++++++++++++ 5 files changed, 506 insertions(+), 5 deletions(-) create mode 100644 typescript-sdk/error-handling.mdx create mode 100644 typescript-sdk/index.mdx create mode 100644 typescript-sdk/pagination.mdx create mode 100644 typescript-sdk/retries-and-timeouts.mdx diff --git a/config/navigation.json b/config/navigation.json index 4c53f95..8d8427a 100644 --- a/config/navigation.json +++ b/config/navigation.json @@ -431,6 +431,27 @@ } ] }, + { + "item": "API Reference", + "icon": "code", + "openapi": "https://app.kosli.com/api/v2/openapi.json" + }, + { + "item": "SDKs", + "icon": "puzzle-piece", + "groups": [ + { + "group": "TypeScript", + "icon": "js", + "pages": [ + "typescript-sdk/index", + "typescript-sdk/retries-and-timeouts", + "typescript-sdk/pagination", + "typescript-sdk/error-handling" + ] + } + ] + }, { "item": "Template Reference", "icon": "file-code", @@ -457,11 +478,6 @@ } ] }, - { - "item": "API Reference", - "icon": "code", - "openapi": "https://app.kosli.com/api/v2/openapi.json" - }, { "item": "Helm Reference", "icon": "layer-group", diff --git a/typescript-sdk/error-handling.mdx b/typescript-sdk/error-handling.mdx new file mode 100644 index 0000000..bfc0c9f --- /dev/null +++ b/typescript-sdk/error-handling.mdx @@ -0,0 +1,101 @@ +--- +title: Error handling +description: How to catch and inspect errors thrown by the Kosli TypeScript SDK. +--- + +All HTTP errors from the SDK are instances of `KosliError` or one of its subclasses. Network-level failures throw one of the client error classes listed below. + +## KosliError + +`KosliError` is the base class for any non-2xx HTTP response. It exposes: + +| Property | Type | Description | +|----------|------|-------------| +| `message` | `string` | Error message | +| `statusCode` | `number` | HTTP status code (e.g. `404`, `403`) | +| `headers` | `Headers` | Response headers | +| `body` | `string` | Raw response body (may be empty) | +| `rawResponse` | `Response` | The full HTTP response object | +| `data$` | varies | Structured error data for specific error types (see below) | + +## HTTP error classes + +These named subclasses cover the most common API errors: + +| Class | Status | Description | +|-------|--------|-------------| +| `BadRequestError` | `400` | Invalid request | +| `UnauthorizedError` | `401` | Permission denied or not authenticated | +| `NotFoundError` | `404` | Resource not found | +| `RateLimitedError` | `429` | Rate limit exceeded | + +```typescript +import { Kosli } from "@kosli/sdk"; +import * as errors from "@kosli/sdk/models/errors"; + +const kosli = new Kosli({ + httpBearer: process.env["KOSLI_API_KEY"] ?? "", +}); + +try { + const trail = await kosli.trails.get({}, { + org: "my-org", + flowName: "my-flow", + trailName: "my-trail", + }); +} catch (error) { + if (error instanceof errors.NotFoundError) { + console.error("Trail not found"); + } else if (error instanceof errors.UnauthorizedError) { + console.error("Access denied:", error.data$.message); + } else if (error instanceof errors.RateLimitedError) { + console.error("Rate limit hit - back off and retry"); + } else if (error instanceof errors.KosliError) { + console.error(`HTTP ${error.statusCode}: ${error.message}`); + } +} +``` + +## Network errors + +These errors are thrown when the request cannot be completed at the transport level: + +| Class | Description | +|-------|-------------| +| `ConnectionError` | The HTTP client could not reach the server | +| `RequestTimeoutError` | The request was aborted by an `AbortSignal` timeout | +| `RequestAbortedError` | The request was cancelled by the caller | +| `InvalidRequestError` | The request could not be constructed (invalid input) | +| `UnexpectedClientError` | An unrecognised error occurred in the HTTP client | + +Network errors do not have a `statusCode` - handle them separately from `KosliError`: + +```typescript +import * as errors from "@kosli/sdk/models/errors"; + +try { + const result = await kosli.trails.list({}, { org: "my-org", flowName: "my-flow" }); +} catch (error) { + if (error instanceof errors.ConnectionError) { + console.error("Could not connect to Kosli - check your network"); + } else if (error instanceof errors.RequestTimeoutError) { + console.error("Request timed out"); + } else if (error instanceof errors.KosliError) { + console.error(`API error ${error.statusCode}: ${error.message}`); + } else { + throw error; + } +} +``` + +## Other errors + +A small number of operations can throw additional error types for specific conditions: + +| Class | Status | Description | +|-------|--------|-------------| +| `ConflictResponseModelError` | `409` | Conflict (e.g. resource already exists) | +| `RequestEntityTooLargeResponseModelError` | `413` | Request body too large | +| `ResponseValidationError` | - | Response from server did not match the expected schema; inspect `error.rawValue` or call `error.pretty()` | + +Check the method's documentation to see which errors apply to a specific operation. diff --git a/typescript-sdk/index.mdx b/typescript-sdk/index.mdx new file mode 100644 index 0000000..5af1086 --- /dev/null +++ b/typescript-sdk/index.mdx @@ -0,0 +1,178 @@ +--- +title: Overview +description: Use the Kosli TypeScript SDK to interact with the Kosli API from Node.js, Bun, Deno, and browser environments. +--- + + +**Early access.** The TypeScript SDK is an unsupported work in progress and may change or be removed without notice. It is provided for evaluation only - do not use it in production. + + +The `@kosli/sdk` package provides a type-safe TypeScript client for the Kosli API. It supports Node.js, Bun, Deno, and all modern browsers. + +## Installation + +Install using your preferred package manager: + + +```bash npm +npm add @kosli/sdk +``` + +```bash pnpm +pnpm add @kosli/sdk +``` + +```bash bun +bun add @kosli/sdk +``` + +```bash yarn +yarn add @kosli/sdk +``` + + + +The package is published as an ES Module (ESM) only. CommonJS projects must use dynamic import: + +```typescript +const { Kosli } = await import("@kosli/sdk"); +``` + + +## Quick start + +```typescript +import { Kosli } from "@kosli/sdk"; + +const kosli = new Kosli({ + httpBearer: process.env["KOSLI_API_KEY"] ?? "", +}); + +const trails = await kosli.trails.list({}, { + org: "my-org", + flowName: "my-flow", +}); + +console.log(trails); +``` + +## Authentication + +Set your API key when constructing the client. This applies it to all requests: + +```typescript +import { Kosli } from "@kosli/sdk"; + +const kosli = new Kosli({ + httpBearer: process.env["KOSLI_API_KEY"] ?? "", +}); +``` + +Some operations accept the API key at the call site instead. Use this when you need per-request credentials: + +```typescript +const result = await kosli.trails.list( + { httpBearer: process.env["KOSLI_API_KEY"] ?? "" }, // security + { org: "my-org", flowName: "my-flow" }, // request params +); +``` + +See [personal API keys](/user/personal_api_keys) and [service accounts](/administration/authentication/service_accounts) for how to create API keys. + +## Server selection + +By default the SDK targets the EU region. Pass `server: "us"` to use the US region: + +```typescript +const kosli = new Kosli({ + httpBearer: process.env["KOSLI_API_KEY"] ?? "", + server: "us", +}); +``` + +| Name | URL | Region | +|------|-----|--------| +| `"eu"` | `https://app.kosli.com/api/v2` | EU (default) | +| `"us"` | `https://app.us.kosli.com/api/v2` | US | + +To point at a custom URL (e.g. a proxy or local instance), use `serverURL` instead: + +```typescript +const kosli = new Kosli({ + httpBearer: process.env["KOSLI_API_KEY"] ?? "", + serverURL: "https://app.kosli.com/api/v2", +}); +``` + +## Available resources + +The client exposes the following resource groups: + +| Resource | Description | +|----------|-------------| +| `kosli.actions` | Environment and flow actions | +| `kosli.allowlists` | Artifact allowlists | +| `kosli.approvals` | Approvals | +| `kosli.artifacts` | Artifact reporting and lookup | +| `kosli.attestations` | Attestations (custom, JUnit, Jira, Snyk, Sonar, pull request) | +| `kosli.builds` | Builds | +| `kosli.customAttestationTypes` | Custom attestation type management | +| `kosli.deployments` | Deployments | +| `kosli.environments` | Environment management and reporting | +| `kosli.flows` | Flow management | +| `kosli.organizations` | Organization settings and notifications | +| `kosli.policies` | Policy management | +| `kosli.repos` | Repository live artifact lookup | +| `kosli.schemas` | Policy and template schemas | +| `kosli.search` | Artifact search by SHA or fingerprint | +| `kosli.serviceAccounts` | Service account API key management | +| `kosli.snapshots` | Environment snapshots | +| `kosli.tags` | Tag management | +| `kosli.trails` | Trail management | +| `kosli.user` | User settings | + +## Standalone functions + +All methods are also available as individually importable functions, which allows bundlers to tree-shake unused code: + +```typescript +import { KosliCore } from "@kosli/sdk/core.js"; +import { trailsList } from "@kosli/sdk/funcs/trails-list.js"; + +const kosli = new KosliCore({ + httpBearer: process.env["KOSLI_API_KEY"] ?? "", +}); + +const result = await trailsList(kosli, {}, { + org: "my-org", + flowName: "my-flow", +}); +``` + +## Supported runtimes + +The SDK requires ECMAScript 2020 or later and the Web Fetch API. Supported environments: + +- **Browsers**: Chrome, Safari, Edge, Firefox (evergreen) +- **Node.js**: active and maintenance LTS releases (v18, v20+) +- **Bun**: v1 and above +- **Deno**: v1.39+ + +For TypeScript projects, add `"lib": ["es2020", "dom", "dom.iterable"]` to your `tsconfig.json` to get full type support for async iterables, streams, and fetch-related APIs. + +## Debugging + +Pass a logger to emit debug output for all requests and responses: + +```typescript +const kosli = new Kosli({ + httpBearer: process.env["KOSLI_API_KEY"] ?? "", + debugLogger: console, +}); +``` + +Alternatively, set the environment variable `KOSLI_DEBUG=true`. + + +Debug logging prints headers including your API key in plain text. Use it only during local development. + diff --git a/typescript-sdk/pagination.mdx b/typescript-sdk/pagination.mdx new file mode 100644 index 0000000..e219b66 --- /dev/null +++ b/typescript-sdk/pagination.mdx @@ -0,0 +1,106 @@ +--- +title: Pagination +description: How to work with paginated responses in the Kosli TypeScript SDK. +--- + +List operations in the Kosli API return results in pages. The SDK exposes the underlying page controls directly - there is no automatic iterator; you request each page explicitly. + +## Request parameters + +Paginated operations accept `page` and `perPage` query parameters: + +| Parameter | Type | Description | +|-----------|------|-------------| +| `page` | `number` | Page number, starting at `1` | +| `perPage` | `number` | Number of results per page | + +```typescript +import { Kosli } from "@kosli/sdk"; + +const kosli = new Kosli({ + httpBearer: process.env["KOSLI_API_KEY"] ?? "", +}); + +const result = await kosli.trails.list({}, { + org: "my-org", + flowName: "my-flow", + page: 1, + perPage: 20, +}); +``` + +## Response formats + +Different endpoints return pagination metadata in different ways. + +### Pagination object in the response body + +Some operations return a `pagination` field alongside the results. It is omitted when all results fit on one page. + +```typescript +const result = await kosli.trails.list({}, { + org: "my-org", + flowName: "my-flow", + page: 1, + perPage: 20, +}); + +if (result.pagination) { + console.log(`Page ${result.pagination.page} of ${result.pagination.pageCount}`); + console.log(`${result.pagination.total} total trails`); +} +``` + +The `pagination` object contains: + +| Field | Type | Description | +|-------|------|-------------| +| `total` | `number` | Total number of results across all pages | +| `page` | `number` | Current page number | +| `perPage` | `number` | Results per page | +| `pageCount` | `number` | Total number of pages | + +### Link header + +Some operations return pagination links as a `Link` response header. Parse these to navigate to the next page without constructing URLs manually. + +``` +; rel="first", +; rel="prev", +; rel="next", +; rel="last" +``` + +The `Link` header is only present when there are multiple pages. The SDK does not parse this header automatically - access it via the raw response if needed. + +## Iterating all pages + +Fetch pages in a loop until there are no more results: + +```typescript +import { Kosli } from "@kosli/sdk"; + +const kosli = new Kosli({ + httpBearer: process.env["KOSLI_API_KEY"] ?? "", +}); + +async function fetchAllTrails(org: string, flowName: string) { + const allTrails = []; + let page = 1; + const perPage = 50; + + while (true) { + const result = await kosli.trails.list({}, { org, flowName, page, perPage }); + + const trails = Array.isArray(result) ? result : result.data ?? []; + allTrails.push(...trails); + + if (!result.pagination || page >= result.pagination.pageCount) { + break; + } + page++; + } + + return allTrails; +} +``` diff --git a/typescript-sdk/retries-and-timeouts.mdx b/typescript-sdk/retries-and-timeouts.mdx new file mode 100644 index 0000000..f66e82a --- /dev/null +++ b/typescript-sdk/retries-and-timeouts.mdx @@ -0,0 +1,100 @@ +--- +title: Retries and timeouts +description: How the Kosli TypeScript SDK handles automatic retries and request timeouts. +--- + +## Retries + +Many SDK operations retry automatically on transient failures. The default policy retries on: + +- HTTP `5XX` responses (server errors) +- HTTP `429` responses (rate limiting) +- HTTP `409` responses (lock conflict) +- Network connection errors + +Retries use an exponential backoff: 1s initial interval, 10s maximum interval, approximately 30s total budget. + +### Override retries for a single call + +Pass a `retries` object in the call options to change the retry behavior for one request: + +```typescript +import { Kosli } from "@kosli/sdk"; + +const kosli = new Kosli({ + httpBearer: process.env["KOSLI_API_KEY"] ?? "", +}); + +const result = await kosli.trails.list({}, { org: "my-org", flowName: "my-flow" }, { + retries: { + strategy: "backoff", + backoff: { + initialInterval: 500, // ms before first retry + maxInterval: 30_000, // ms cap per interval + exponent: 1.5, + maxElapsedTime: 60_000, // ms total budget + }, + retryConnectionErrors: true, + }, +}); +``` + +### Override retries for all calls + +Set `retryConfig` in the constructor to apply a retry policy to all requests: + +```typescript +import { Kosli } from "@kosli/sdk"; + +const kosli = new Kosli({ + httpBearer: process.env["KOSLI_API_KEY"] ?? "", + retryConfig: { + strategy: "backoff", + backoff: { + initialInterval: 500, + maxInterval: 30_000, + exponent: 1.5, + maxElapsedTime: 60_000, + }, + retryConnectionErrors: true, + }, +}); +``` + +### Disable retries + +Set `strategy: "none"` to disable retries entirely: + +```typescript +const result = await kosli.trails.list({}, { org: "my-org", flowName: "my-flow" }, { + retries: { strategy: "none" }, +}); +``` + +## Timeouts + +The SDK does not have a built-in timeout parameter. Set timeouts via the `AbortSignal` API when constructing a custom HTTP client. + +```typescript +import { Kosli } from "@kosli/sdk"; +import { HTTPClient } from "@kosli/sdk/lib/http"; + +const httpClient = new HTTPClient(); + +httpClient.addHook("beforeRequest", (request) => { + return new Request(request, { + signal: request.signal ?? AbortSignal.timeout(10_000), // 10s timeout + }); +}); + +const kosli = new Kosli({ + httpBearer: process.env["KOSLI_API_KEY"] ?? "", + httpClient, +}); +``` + +A timed-out request throws a `RequestTimeoutError`. See [error handling](/typescript-sdk/error-handling) for details. + + +`AbortSignal.timeout()` is available in Node.js v17.3+, Bun v1+, and modern browsers. For older Node.js, use `AbortController` with `setTimeout` instead. + From 7b2c3563853b8cf2adf9c8b635208cc7164fa0b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Gr=C3=B8ndahl?= Date: Tue, 23 Jun 2026 13:51:36 +0200 Subject: [PATCH 2/2] docs: link to npm package on TypeScript SDK overview --- typescript-sdk/index.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/typescript-sdk/index.mdx b/typescript-sdk/index.mdx index 5af1086..22ddd4f 100644 --- a/typescript-sdk/index.mdx +++ b/typescript-sdk/index.mdx @@ -176,3 +176,7 @@ Alternatively, set the environment variable `KOSLI_DEBUG=true`. Debug logging prints headers including your API key in plain text. Use it only during local development. + +## Package + +The SDK is published on npm as [`@kosli/sdk`](https://www.npmjs.com/package/@kosli/sdk).