@krovacloud/sdk 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,47 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@krovacloud/sdk` are documented here. This project adheres to
4
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and the
5
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format.
6
+
7
+ ## 0.1.1
8
+
9
+ ### Fixed
10
+
11
+ - Regenerated the vendored OpenAPI types from the corrected Krova Cloud spec so
12
+ the SDK's response types match what the API actually returns. The most
13
+ visible changes:
14
+ - **`Cube`** now has the real shape: `state` (was `status`), a nested
15
+ `resources: { vcpu, ramGb, diskGb }` object (replacing the flat `vcpus` /
16
+ `ramMb` / `diskLimitGb` fields), and `image` (was `imageId`). The
17
+ `spaceId` field was removed (a Cube is addressed via its Space in the path,
18
+ not carried in the body). All fields are now `required`.
19
+ - **`TcpMapping`** now returns `hostPort` (was `publicPort`), a structured
20
+ `whitelistedIps: { id, cidr }[]` (was a flat `whitelistIps: string[]`), plus
21
+ `label`, `status`, `isSsh`, `createdAt`, and `updatedAt`.
22
+ - **`Snapshot`** gained `kind`; **`Domain`** gained `corsConfig`.
23
+ - `Domain`, `Snapshot`, `Webhook`, `WebhookDelivery`, and `Error` schema
24
+ fields are now `required` rather than optional, matching the real responses.
25
+ - Updated the README quickstart and the client tests to the corrected `Cube`
26
+ shape (`cube.state`, `cube.resources.vcpu`, `cube.image`).
27
+
28
+ ## 0.1.0
29
+
30
+ Initial release.
31
+
32
+ ### Added
33
+
34
+ - `KrovaClient` — a typed client for the Krova Cloud API, generated from the
35
+ vendored OpenAPI spec via `openapi-typescript` + `openapi-fetch`.
36
+ - `client.raw` — the underlying fully typed `openapi-fetch` client covering every
37
+ operation in the spec (31 operations across 22 resource paths: Cubes, Domains,
38
+ TCP mappings, Snapshots, Backups, Imports, Webhooks, and the public catalog).
39
+ - Ergonomic helpers that unwrap `data` and throw on non-2xx:
40
+ - `client.cubes`: `list`, `create`, `get`, `update`, `delete`, `sleep`, `wake`.
41
+ - `client.catalog`: `regions`, `images`, `pricing`.
42
+ - `KrovaError` — thrown by the helpers on non-2xx responses, with `status`,
43
+ `code`, `requestId`, and the parsed error `body`.
44
+ - `X-API-KEY` authentication (the spec's scheme) with an optional
45
+ `authScheme: "bearer"` mode.
46
+ - Automatic retry on `429` / `503`, honoring the `Retry-After` header.
47
+ - Dual ESM + CJS builds with bundled `.d.ts` type declarations.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Krova Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # @krovacloud/sdk
2
+
3
+ The official TypeScript SDK for the [Krova Cloud](https://krova.cloud) API — a fully typed client generated directly from the Krova Cloud OpenAPI specification.
4
+
5
+ Krova Cloud is a self-service platform for lightweight **Cubes** (Firecracker microVMs) on dedicated bare-metal servers. This SDK gives you typed access to every endpoint plus ergonomic helpers for the common Cube and catalog operations.
6
+
7
+ - **Fully typed** — request bodies, responses, and path params are typed from the OpenAPI spec via [`openapi-typescript`](https://github.com/openapi-ts/openapi-typescript) + [`openapi-fetch`](https://github.com/openapi-ts/openapi-typescript/tree/main/packages/openapi-fetch).
8
+ - **ESM + CJS** — ships both, with `.d.ts` types.
9
+ - **Ergonomic helpers** — `client.cubes.*` and `client.catalog.*` unwrap the response and throw on errors.
10
+ - **Escape hatch** — `client.raw` exposes the underlying typed client for every operation in the spec (31 operations across 22 resource paths).
11
+
12
+ ## Install
13
+
14
+ ```sh
15
+ pnpm add @krovacloud/sdk
16
+ # or: npm install @krovacloud/sdk
17
+ # or: yarn add @krovacloud/sdk
18
+ ```
19
+
20
+ Requires Node.js 18+ (uses the global `fetch`).
21
+
22
+ ## Quickstart
23
+
24
+ ```ts
25
+ import { KrovaClient, KrovaError } from "@krovacloud/sdk";
26
+
27
+ const krova = new KrovaClient({
28
+ apiKey: process.env.KROVA_API_KEY!, // a "kro_..." token
29
+ });
30
+
31
+ // List Cubes in a Space
32
+ const cubes = await krova.cubes.list("space_123");
33
+ console.log(cubes);
34
+
35
+ // Create a Cube
36
+ const cube = await krova.cubes.create("space_123", {
37
+ name: "web-server",
38
+ image: "ubuntu-24.04",
39
+ resources: { vcpu: 2, ramGb: 4, diskGb: 40 },
40
+ sshPublicKey: "ssh-ed25519 AAAA...your-key... you@host",
41
+ });
42
+ console.log(`Created cube ${cube.id} (${cube.state})`);
43
+ console.log(` ${cube.resources.vcpu} vCPU / ${cube.resources.ramGb} GB RAM, image ${cube.image}`);
44
+
45
+ // Sleep it, then wake it
46
+ await krova.cubes.sleep("space_123", cube.id);
47
+ await krova.cubes.wake("space_123", cube.id);
48
+ ```
49
+
50
+ ### Public catalog
51
+
52
+ The catalog endpoints do not require authentication, but the client sends your key harmlessly:
53
+
54
+ ```ts
55
+ const regions = await krova.catalog.regions();
56
+ const images = await krova.catalog.images();
57
+ const pricing = await krova.catalog.pricing();
58
+ ```
59
+
60
+ ### The raw client (every endpoint)
61
+
62
+ The ergonomic helpers cover Cubes and the catalog. For everything else — Domains, TCP mappings, Snapshots, Backups, Imports, Webhooks — use `krova.raw`, the fully typed [`openapi-fetch`](https://github.com/openapi-ts/openapi-typescript/tree/main/packages/openapi-fetch) client. It returns `{ data, error, response }` and never throws:
63
+
64
+ ```ts
65
+ const { data, error } = await krova.raw.POST(
66
+ "/spaces/{spaceId}/webhooks",
67
+ {
68
+ params: { path: { spaceId: "space_123" } },
69
+ body: { url: "https://example.com/hook", events: ["cube.running"] },
70
+ },
71
+ );
72
+ if (error) {
73
+ console.error("Webhook create failed:", error.error);
74
+ } else {
75
+ console.log(data);
76
+ }
77
+ ```
78
+
79
+ ## Authentication
80
+
81
+ Create an API key from your Space settings in the [Krova Cloud dashboard](https://krova.cloud). Keys are **scoped per Space** and inherit the permissions of the membership that created them; they look like `kro_...`.
82
+
83
+ By default the client sends the key as the `X-API-KEY` header, matching the API's security scheme. If your gateway expects a bearer token instead, pass `authScheme: "bearer"`:
84
+
85
+ ```ts
86
+ const krova = new KrovaClient({ apiKey: "kro_...", authScheme: "bearer" });
87
+ ```
88
+
89
+ ### Options
90
+
91
+ ```ts
92
+ new KrovaClient({
93
+ apiKey: "kro_...", // required
94
+ baseUrl: "https://krova.cloud/api/v1", // default
95
+ authScheme: "x-api-key", // "x-api-key" (default) | "bearer"
96
+ maxRetries: 2, // retries on 429/503 (honors Retry-After); 0 disables
97
+ fetch: customFetch, // optional fetch override
98
+ });
99
+ ```
100
+
101
+ ## Error handling
102
+
103
+ The ergonomic helpers throw a `KrovaError` on any non-2xx response. It carries the HTTP `status`, the API's error `message`, and — when the API sends them — a `code` (`X-Error-Code`), a `requestId` (`X-Request-Id`), and the parsed `body`:
104
+
105
+ ```ts
106
+ import { KrovaError } from "@krovacloud/sdk";
107
+
108
+ try {
109
+ await krova.cubes.get("space_123", "cube_missing");
110
+ } catch (err) {
111
+ if (err instanceof KrovaError) {
112
+ console.error(`[${err.status}] ${err.message}`);
113
+ if (err.requestId) console.error("request id:", err.requestId);
114
+ } else {
115
+ throw err;
116
+ }
117
+ }
118
+ ```
119
+
120
+ Mutating `POST` / `DELETE` endpoints are rate-limited (10 requests / 60s per client IP). The client automatically retries `429` and `503` responses (up to `maxRetries`, honoring the `Retry-After` header).
121
+
122
+ ## API reference
123
+
124
+ The full Krova Cloud API is documented in the OpenAPI spec vendored in this repo ([`openapi.json`](./openapi.json)). See [krova.cloud](https://krova.cloud) for the hosted docs and to obtain a key.
125
+
126
+ ## Development
127
+
128
+ ```sh
129
+ pnpm install
130
+ pnpm gen # regenerate src/generated/types.ts from openapi.json
131
+ pnpm typecheck
132
+ pnpm build # ESM + CJS + d.ts into dist/
133
+ pnpm test # node:test via tsx (no network)
134
+ ```
135
+
136
+ ## License
137
+
138
+ [MIT](./LICENSE) © 2026 Krova Inc.
package/dist/index.cjs ADDED
@@ -0,0 +1,245 @@
1
+ 'use strict';
2
+
3
+ var createClient = require('openapi-fetch');
4
+
5
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
6
+
7
+ var createClient__default = /*#__PURE__*/_interopDefault(createClient);
8
+
9
+ // src/client.ts
10
+
11
+ // src/error.ts
12
+ var KrovaError = class _KrovaError extends Error {
13
+ /** HTTP status code of the failing response. */
14
+ status;
15
+ /**
16
+ * A machine-readable error code, when the API surfaces one via the
17
+ * `X-Error-Code` response header. The documented error body only carries a
18
+ * human-readable `error` string, so this is best-effort.
19
+ */
20
+ code;
21
+ /**
22
+ * The request id from the `X-Request-Id` response header, when present.
23
+ * Useful when contacting Krova Cloud support about a specific failure.
24
+ */
25
+ requestId;
26
+ /** The parsed JSON error body, when the response had one. */
27
+ body;
28
+ /** The raw `Response` object, for callers that need headers/url/etc. */
29
+ response;
30
+ constructor(message, init) {
31
+ super(message);
32
+ this.name = "KrovaError";
33
+ this.status = init.status;
34
+ this.code = init.code;
35
+ this.requestId = init.requestId;
36
+ this.body = init.body;
37
+ this.response = init.response;
38
+ Object.setPrototypeOf(this, _KrovaError.prototype);
39
+ }
40
+ };
41
+ function krovaErrorFrom(response, body) {
42
+ const message = typeof body?.error === "string" && body.error || response.statusText || `Request failed with status ${response.status}`;
43
+ return new KrovaError(message, {
44
+ status: response.status,
45
+ code: response.headers.get("x-error-code") ?? void 0,
46
+ requestId: response.headers.get("x-request-id") ?? void 0,
47
+ body,
48
+ response
49
+ });
50
+ }
51
+
52
+ // src/client.ts
53
+ var DEFAULT_BASE_URL = "https://krova.cloud/api/v1";
54
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 503]);
55
+ var BASE_BACKOFF_MS = 500;
56
+ var MAX_BACKOFF_MS = 1e4;
57
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
58
+ function parseRetryAfterMs(headerValue) {
59
+ if (!headerValue) return null;
60
+ const seconds = Number(headerValue);
61
+ if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
62
+ const dateMs = Date.parse(headerValue);
63
+ if (Number.isFinite(dateMs)) return Math.max(0, dateMs - Date.now());
64
+ return null;
65
+ }
66
+ function authMiddleware(apiKey, scheme) {
67
+ return {
68
+ onRequest({ request }) {
69
+ if (scheme === "bearer") {
70
+ request.headers.set("Authorization", `Bearer ${apiKey}`);
71
+ } else {
72
+ request.headers.set("X-API-KEY", apiKey);
73
+ }
74
+ return request;
75
+ }
76
+ };
77
+ }
78
+ function retryMiddleware(maxRetries, doFetch) {
79
+ return {
80
+ async onResponse({ request, response }) {
81
+ if (maxRetries <= 0 || !RETRYABLE_STATUSES.has(response.status)) {
82
+ return response;
83
+ }
84
+ let current = response;
85
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
86
+ if (!RETRYABLE_STATUSES.has(current.status)) break;
87
+ const retryAfterMs = parseRetryAfterMs(current.headers.get("retry-after"));
88
+ const backoff = Math.min(BASE_BACKOFF_MS * 2 ** (attempt - 1), MAX_BACKOFF_MS);
89
+ await sleep(retryAfterMs ?? backoff);
90
+ current = await doFetch(request.clone());
91
+ }
92
+ return current;
93
+ }
94
+ };
95
+ }
96
+ var KrovaClient = class {
97
+ /**
98
+ * The underlying openapi-fetch client — a fully typed escape hatch to every
99
+ * path in the spec. Returns `{ data, error, response }` and never throws.
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * const { data, error } = await krova.raw.GET(
104
+ * "/spaces/{spaceId}/cubes/{cubeId}",
105
+ * { params: { path: { spaceId, cubeId } } },
106
+ * );
107
+ * ```
108
+ */
109
+ raw;
110
+ /** The resolved base URL in use. */
111
+ baseUrl;
112
+ constructor(options) {
113
+ if (!options?.apiKey) {
114
+ throw new Error("KrovaClient: `apiKey` is required.");
115
+ }
116
+ this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
117
+ const doFetch = options.fetch ?? globalThis.fetch;
118
+ const maxRetries = options.maxRetries ?? 2;
119
+ this.raw = createClient__default.default({
120
+ baseUrl: this.baseUrl,
121
+ ...options.fetch ? { fetch: options.fetch } : {}
122
+ });
123
+ this.raw.use(authMiddleware(options.apiKey, options.authScheme ?? "x-api-key"));
124
+ if (maxRetries > 0) {
125
+ this.raw.use(retryMiddleware(maxRetries, doFetch));
126
+ }
127
+ }
128
+ // ---------------------------------------------------------------------------
129
+ // Cubes
130
+ // ---------------------------------------------------------------------------
131
+ cubes = {
132
+ /** List Cubes in a Space. Returns the raw (paginated) response body. */
133
+ list: async (spaceId) => {
134
+ const { data, error, response } = await this.raw.GET("/spaces/{spaceId}/cubes", {
135
+ params: { path: { spaceId } }
136
+ });
137
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
138
+ return data;
139
+ },
140
+ /**
141
+ * Create a Cube. Returns the created {@link Cube}.
142
+ *
143
+ * @param spaceId Target Space id.
144
+ * @param body Cube spec — `{ name, image, resources, sshPublicKey, ... }`.
145
+ * @param opts Optional `idempotencyKey` (max 255 chars, scoped per space).
146
+ */
147
+ create: async (spaceId, body, opts) => {
148
+ const { data, error, response } = await this.raw.POST("/spaces/{spaceId}/cubes", {
149
+ params: {
150
+ path: { spaceId },
151
+ ...opts?.idempotencyKey ? { header: { "Idempotency-Key": opts.idempotencyKey } } : {}
152
+ },
153
+ body
154
+ });
155
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
156
+ const cube = data?.cube;
157
+ if (!cube) {
158
+ throw krovaErrorFrom(response, { error: "Create Cube response had no `cube`." });
159
+ }
160
+ return cube;
161
+ },
162
+ /** Get a single Cube. Returns the raw response body. */
163
+ get: async (spaceId, cubeId) => {
164
+ const { data, error, response } = await this.raw.GET(
165
+ "/spaces/{spaceId}/cubes/{cubeId}",
166
+ { params: { path: { spaceId, cubeId } } }
167
+ );
168
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
169
+ return data;
170
+ },
171
+ /**
172
+ * Update a Cube's SSH port.
173
+ *
174
+ * The Krova Cloud API exposes no general Cube-mutation endpoint; the only
175
+ * mutable Cube field over the API is its SSH port, via
176
+ * `PUT /spaces/{spaceId}/cubes/{cubeId}/ssh-port`. This helper maps to that
177
+ * endpoint. (Compute resize / rename are not part of the public API.)
178
+ */
179
+ update: async (spaceId, cubeId, body) => {
180
+ const { data, error, response } = await this.raw.PUT(
181
+ "/spaces/{spaceId}/cubes/{cubeId}/ssh-port",
182
+ { params: { path: { spaceId, cubeId } }, body }
183
+ );
184
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
185
+ return data;
186
+ },
187
+ /** Delete a Cube (asynchronous — deletion is enqueued). */
188
+ delete: async (spaceId, cubeId) => {
189
+ const { data, error, response } = await this.raw.DELETE(
190
+ "/spaces/{spaceId}/cubes/{cubeId}",
191
+ { params: { path: { spaceId, cubeId } } }
192
+ );
193
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
194
+ return data;
195
+ },
196
+ /** Sleep a running Cube (asynchronous — sleep is enqueued). */
197
+ sleep: async (spaceId, cubeId) => {
198
+ const { data, error, response } = await this.raw.POST(
199
+ "/spaces/{spaceId}/cubes/{cubeId}/sleep",
200
+ { params: { path: { spaceId, cubeId } } }
201
+ );
202
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
203
+ return data;
204
+ },
205
+ /** Wake a sleeping Cube (asynchronous — wake is enqueued). */
206
+ wake: async (spaceId, cubeId) => {
207
+ const { data, error, response } = await this.raw.POST(
208
+ "/spaces/{spaceId}/cubes/{cubeId}/wake",
209
+ { params: { path: { spaceId, cubeId } } }
210
+ );
211
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
212
+ return data;
213
+ }
214
+ };
215
+ // ---------------------------------------------------------------------------
216
+ // Public catalog (no auth required by the API, but the key is harmless)
217
+ // ---------------------------------------------------------------------------
218
+ catalog = {
219
+ /** List regions with available capacity. */
220
+ regions: async () => {
221
+ const { data, error, response } = await this.raw.GET("/regions");
222
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
223
+ return data;
224
+ },
225
+ /** List available OS images. */
226
+ images: async () => {
227
+ const { data, error, response } = await this.raw.GET("/images");
228
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
229
+ return data;
230
+ },
231
+ /** Per-resource hourly rates and volume pricing tiers. */
232
+ pricing: async () => {
233
+ const { data, error, response } = await this.raw.GET("/pricing");
234
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
235
+ return data;
236
+ }
237
+ };
238
+ };
239
+
240
+ exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
241
+ exports.KrovaClient = KrovaClient;
242
+ exports.KrovaError = KrovaError;
243
+ exports.krovaErrorFrom = krovaErrorFrom;
244
+ //# sourceMappingURL=index.cjs.map
245
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/error.ts","../src/client.ts"],"names":["createClient"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * The error body shape returned by the Krova Cloud API.\n *\n * Per the OpenAPI spec (`components.schemas.Error`), every non-2xx response\n * body is `{ \"error\": string }`. Additional fields may appear over time, so\n * we keep the type open.\n */\nexport interface KrovaErrorBody {\n error?: string;\n [key: string]: unknown;\n}\n\n/**\n * Error thrown by the ergonomic {@link KrovaClient} helpers when the API\n * responds with a non-2xx status.\n *\n * The raw openapi-fetch client (`client.raw`) never throws — it returns\n * `{ data, error, response }`. The helpers wrap that and throw `KrovaError`\n * so callers can `try/catch`.\n */\nexport class KrovaError extends Error {\n /** HTTP status code of the failing response. */\n readonly status: number;\n\n /**\n * A machine-readable error code, when the API surfaces one via the\n * `X-Error-Code` response header. The documented error body only carries a\n * human-readable `error` string, so this is best-effort.\n */\n readonly code?: string;\n\n /**\n * The request id from the `X-Request-Id` response header, when present.\n * Useful when contacting Krova Cloud support about a specific failure.\n */\n readonly requestId?: string;\n\n /** The parsed JSON error body, when the response had one. */\n readonly body?: KrovaErrorBody;\n\n /** The raw `Response` object, for callers that need headers/url/etc. */\n readonly response?: Response;\n\n constructor(\n message: string,\n init: {\n status: number;\n code?: string;\n requestId?: string;\n body?: KrovaErrorBody;\n response?: Response;\n },\n ) {\n super(message);\n this.name = \"KrovaError\";\n this.status = init.status;\n this.code = init.code;\n this.requestId = init.requestId;\n this.body = init.body;\n this.response = init.response;\n // Restore prototype chain for instanceof across compilation targets.\n Object.setPrototypeOf(this, KrovaError.prototype);\n }\n}\n\n/**\n * Build a {@link KrovaError} from a failing response + parsed error body.\n */\nexport function krovaErrorFrom(\n response: Response,\n body: KrovaErrorBody | undefined,\n): KrovaError {\n const message =\n (typeof body?.error === \"string\" && body.error) ||\n response.statusText ||\n `Request failed with status ${response.status}`;\n return new KrovaError(message, {\n status: response.status,\n code: response.headers.get(\"x-error-code\") ?? undefined,\n requestId: response.headers.get(\"x-request-id\") ?? undefined,\n body,\n response,\n });\n}\n","import createClient, { type Client, type Middleware } from \"openapi-fetch\";\nimport { krovaErrorFrom } from \"./error.js\";\nimport type { components, paths } from \"./generated/types.js\";\n\n/** The Cube resource, as defined in the Krova Cloud OpenAPI spec. */\nexport type Cube = components[\"schemas\"][\"Cube\"];\n\n/** Default API base URL — the single `servers[0].url` from the OpenAPI spec. */\nexport const DEFAULT_BASE_URL = \"https://krova.cloud/api/v1\";\n\n/**\n * How the API key is presented to the server.\n *\n * - `\"x-api-key\"` (default) — `X-API-KEY: <key>`, matching the spec's\n * `components.securitySchemes.ApiKeyAuth` (an `apiKey` header named\n * `X-API-KEY`).\n * - `\"bearer\"` — `Authorization: Bearer <key>`, for gateways that expect it.\n */\nexport type AuthScheme = \"x-api-key\" | \"bearer\";\n\nexport interface KrovaClientOptions {\n /**\n * Your Krova Cloud API key (a `kro_...` token). Keys are scoped per Space\n * and inherit the permissions of the membership that created them.\n */\n apiKey: string;\n /** Override the API base URL. Defaults to {@link DEFAULT_BASE_URL}. */\n baseUrl?: string;\n /**\n * Auth header scheme. Defaults to `\"x-api-key\"` (the spec's scheme).\n */\n authScheme?: AuthScheme;\n /**\n * Max automatic retries on retryable statuses (429, 503).\n * Defaults to 2. Set to 0 to disable retries.\n */\n maxRetries?: number;\n /**\n * A custom `fetch` implementation (e.g. for tests or a proxy). Defaults to\n * the global `fetch`.\n */\n fetch?: typeof fetch;\n}\n\n/** Statuses the retry middleware treats as transient. */\nconst RETRYABLE_STATUSES = new Set([429, 503]);\n/** Fallback backoff (ms) when the server sends no `Retry-After` header. */\nconst BASE_BACKOFF_MS = 500;\n/** Cap on any single backoff wait (ms), to keep retries \"small but real\". */\nconst MAX_BACKOFF_MS = 10_000;\n\nconst sleep = (ms: number): Promise<void> =>\n new Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * Parse a `Retry-After` header (RFC 7231): either delta-seconds or an\n * HTTP-date. Returns milliseconds to wait, or `null` if absent/unparseable.\n */\nfunction parseRetryAfterMs(headerValue: string | null): number | null {\n if (!headerValue) return null;\n const seconds = Number(headerValue);\n if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);\n const dateMs = Date.parse(headerValue);\n if (Number.isFinite(dateMs)) return Math.max(0, dateMs - Date.now());\n return null;\n}\n\nfunction authMiddleware(apiKey: string, scheme: AuthScheme): Middleware {\n return {\n onRequest({ request }) {\n if (scheme === \"bearer\") {\n request.headers.set(\"Authorization\", `Bearer ${apiKey}`);\n } else {\n request.headers.set(\"X-API-KEY\", apiKey);\n }\n return request;\n },\n };\n}\n\n/**\n * Retry middleware: on a retryable status, wait (honoring `Retry-After` when\n * present, else exponential backoff) and re-issue the request. openapi-fetch\n * clones the request per attempt, so re-fetching here is safe.\n */\nfunction retryMiddleware(maxRetries: number, doFetch: typeof fetch): Middleware {\n return {\n async onResponse({ request, response }) {\n if (maxRetries <= 0 || !RETRYABLE_STATUSES.has(response.status)) {\n return response;\n }\n let current = response;\n for (let attempt = 1; attempt <= maxRetries; attempt++) {\n if (!RETRYABLE_STATUSES.has(current.status)) break;\n const retryAfterMs = parseRetryAfterMs(current.headers.get(\"retry-after\"));\n const backoff = Math.min(BASE_BACKOFF_MS * 2 ** (attempt - 1), MAX_BACKOFF_MS);\n await sleep(retryAfterMs ?? backoff);\n current = await doFetch(request.clone());\n }\n return current;\n },\n };\n}\n\n/**\n * A typed client for the Krova Cloud API.\n *\n * @example\n * ```ts\n * const krova = new KrovaClient({ apiKey: \"kro_...\" });\n * const cubes = await krova.cubes.list(\"space_123\");\n * ```\n */\nexport class KrovaClient {\n /**\n * The underlying openapi-fetch client — a fully typed escape hatch to every\n * path in the spec. Returns `{ data, error, response }` and never throws.\n *\n * @example\n * ```ts\n * const { data, error } = await krova.raw.GET(\n * \"/spaces/{spaceId}/cubes/{cubeId}\",\n * { params: { path: { spaceId, cubeId } } },\n * );\n * ```\n */\n readonly raw: Client<paths>;\n\n /** The resolved base URL in use. */\n readonly baseUrl: string;\n\n constructor(options: KrovaClientOptions) {\n if (!options?.apiKey) {\n throw new Error(\"KrovaClient: `apiKey` is required.\");\n }\n this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n const doFetch = options.fetch ?? globalThis.fetch;\n const maxRetries = options.maxRetries ?? 2;\n\n this.raw = createClient<paths>({\n baseUrl: this.baseUrl,\n ...(options.fetch ? { fetch: options.fetch } : {}),\n });\n this.raw.use(authMiddleware(options.apiKey, options.authScheme ?? \"x-api-key\"));\n if (maxRetries > 0) {\n this.raw.use(retryMiddleware(maxRetries, doFetch));\n }\n }\n\n // ---------------------------------------------------------------------------\n // Cubes\n // ---------------------------------------------------------------------------\n\n readonly cubes = {\n /** List Cubes in a Space. Returns the raw (paginated) response body. */\n list: async (spaceId: string): Promise<unknown> => {\n const { data, error, response } = await this.raw.GET(\"/spaces/{spaceId}/cubes\", {\n params: { path: { spaceId } },\n });\n if (error !== undefined || !response.ok) throw krovaErrorFrom(response, error);\n return data;\n },\n\n /**\n * Create a Cube. Returns the created {@link Cube}.\n *\n * @param spaceId Target Space id.\n * @param body Cube spec — `{ name, image, resources, sshPublicKey, ... }`.\n * @param opts Optional `idempotencyKey` (max 255 chars, scoped per space).\n */\n create: async (\n spaceId: string,\n body: NonNullable<\n paths[\"/spaces/{spaceId}/cubes\"][\"post\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n opts?: { idempotencyKey?: string },\n ): Promise<Cube> => {\n const { data, error, response } = await this.raw.POST(\"/spaces/{spaceId}/cubes\", {\n params: {\n path: { spaceId },\n ...(opts?.idempotencyKey\n ? { header: { \"Idempotency-Key\": opts.idempotencyKey } }\n : {}),\n },\n body,\n });\n if (error !== undefined || !response.ok) throw krovaErrorFrom(response, error);\n const cube = data?.cube;\n if (!cube) {\n throw krovaErrorFrom(response, { error: \"Create Cube response had no `cube`.\" });\n }\n return cube;\n },\n\n /** Get a single Cube. Returns the raw response body. */\n get: async (spaceId: string, cubeId: string): Promise<unknown> => {\n const { data, error, response } = await this.raw.GET(\n \"/spaces/{spaceId}/cubes/{cubeId}\",\n { params: { path: { spaceId, cubeId } } },\n );\n if (error !== undefined || !response.ok) throw krovaErrorFrom(response, error);\n return data;\n },\n\n /**\n * Update a Cube's SSH port.\n *\n * The Krova Cloud API exposes no general Cube-mutation endpoint; the only\n * mutable Cube field over the API is its SSH port, via\n * `PUT /spaces/{spaceId}/cubes/{cubeId}/ssh-port`. This helper maps to that\n * endpoint. (Compute resize / rename are not part of the public API.)\n */\n update: async (\n spaceId: string,\n cubeId: string,\n body: NonNullable<\n paths[\"/spaces/{spaceId}/cubes/{cubeId}/ssh-port\"][\"put\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n ): Promise<unknown> => {\n const { data, error, response } = await this.raw.PUT(\n \"/spaces/{spaceId}/cubes/{cubeId}/ssh-port\",\n { params: { path: { spaceId, cubeId } }, body },\n );\n if (error !== undefined || !response.ok) throw krovaErrorFrom(response, error);\n return data;\n },\n\n /** Delete a Cube (asynchronous — deletion is enqueued). */\n delete: async (spaceId: string, cubeId: string): Promise<unknown> => {\n const { data, error, response } = await this.raw.DELETE(\n \"/spaces/{spaceId}/cubes/{cubeId}\",\n { params: { path: { spaceId, cubeId } } },\n );\n if (error !== undefined || !response.ok) throw krovaErrorFrom(response, error);\n return data;\n },\n\n /** Sleep a running Cube (asynchronous — sleep is enqueued). */\n sleep: async (spaceId: string, cubeId: string): Promise<unknown> => {\n const { data, error, response } = await this.raw.POST(\n \"/spaces/{spaceId}/cubes/{cubeId}/sleep\",\n { params: { path: { spaceId, cubeId } } },\n );\n if (error !== undefined || !response.ok) throw krovaErrorFrom(response, error);\n return data;\n },\n\n /** Wake a sleeping Cube (asynchronous — wake is enqueued). */\n wake: async (spaceId: string, cubeId: string): Promise<unknown> => {\n const { data, error, response } = await this.raw.POST(\n \"/spaces/{spaceId}/cubes/{cubeId}/wake\",\n { params: { path: { spaceId, cubeId } } },\n );\n if (error !== undefined || !response.ok) throw krovaErrorFrom(response, error);\n return data;\n },\n };\n\n // ---------------------------------------------------------------------------\n // Public catalog (no auth required by the API, but the key is harmless)\n // ---------------------------------------------------------------------------\n\n readonly catalog = {\n /** List regions with available capacity. */\n regions: async (): Promise<unknown> => {\n const { data, error, response } = await this.raw.GET(\"/regions\");\n if (error !== undefined || !response.ok) throw krovaErrorFrom(response, error);\n return data;\n },\n\n /** List available OS images. */\n images: async (): Promise<unknown> => {\n const { data, error, response } = await this.raw.GET(\"/images\");\n if (error !== undefined || !response.ok) throw krovaErrorFrom(response, error);\n return data;\n },\n\n /** Per-resource hourly rates and volume pricing tiers. */\n pricing: async (): Promise<unknown> => {\n const { data, error, response } = await this.raw.GET(\"/pricing\");\n if (error !== undefined || !response.ok) throw krovaErrorFrom(response, error);\n return data;\n },\n };\n}\n"]}