@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 +47 -0
- package/LICENSE +21 -0
- package/README.md +138 -0
- package/dist/index.cjs +245 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1674 -0
- package/dist/index.d.ts +1674 -0
- package/dist/index.js +236 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
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":";;;;;;;;;;;AAoBO,IAAM,UAAA,GAAN,MAAM,WAAA,SAAmB,KAAA,CAAM;AAAA;AAAA,EAE3B,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,SAAA;AAAA;AAAA,EAGA,IAAA;AAAA;AAAA,EAGA,QAAA;AAAA,EAET,WAAA,CACE,SACA,IAAA,EAOA;AACA,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,YAAA;AACZ,IAAA,IAAA,CAAK,SAAS,IAAA,CAAK,MAAA;AACnB,IAAA,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AACjB,IAAA,IAAA,CAAK,YAAY,IAAA,CAAK,SAAA;AACtB,IAAA,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AACjB,IAAA,IAAA,CAAK,WAAW,IAAA,CAAK,QAAA;AAErB,IAAA,MAAA,CAAO,cAAA,CAAe,IAAA,EAAM,WAAA,CAAW,SAAS,CAAA;AAAA,EAClD;AACF;AAKO,SAAS,cAAA,CACd,UACA,IAAA,EACY;AACZ,EAAA,MAAM,OAAA,GACH,OAAO,IAAA,EAAM,KAAA,KAAU,QAAA,IAAY,IAAA,CAAK,KAAA,IACzC,QAAA,CAAS,UAAA,IACT,CAAA,2BAAA,EAA8B,QAAA,CAAS,MAAM,CAAA,CAAA;AAC/C,EAAA,OAAO,IAAI,WAAW,OAAA,EAAS;AAAA,IAC7B,QAAQ,QAAA,CAAS,MAAA;AAAA,IACjB,IAAA,EAAM,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,cAAc,CAAA,IAAK,MAAA;AAAA,IAC9C,SAAA,EAAW,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,cAAc,CAAA,IAAK,MAAA;AAAA,IACnD,IAAA;AAAA,IACA;AAAA,GACD,CAAA;AACH;;;AC3EO,IAAM,gBAAA,GAAmB;AAqChC,IAAM,qCAAqB,IAAI,GAAA,CAAI,CAAC,GAAA,EAAK,GAAG,CAAC,CAAA;AAE7C,IAAM,eAAA,GAAkB,GAAA;AAExB,IAAM,cAAA,GAAiB,GAAA;AAEvB,IAAM,KAAA,GAAQ,CAAC,EAAA,KACb,IAAI,OAAA,CAAQ,CAAC,OAAA,KAAY,UAAA,CAAW,OAAA,EAAS,EAAE,CAAC,CAAA;AAMlD,SAAS,kBAAkB,WAAA,EAA2C;AACpE,EAAA,IAAI,CAAC,aAAa,OAAO,IAAA;AACzB,EAAA,MAAM,OAAA,GAAU,OAAO,WAAW,CAAA;AAClC,EAAA,IAAI,MAAA,CAAO,SAAS,OAAO,CAAA,SAAU,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,OAAA,GAAU,GAAI,CAAA;AAC/D,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,WAAW,CAAA;AACrC,EAAA,IAAI,MAAA,CAAO,QAAA,CAAS,MAAM,CAAA,EAAG,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,MAAA,GAAS,IAAA,CAAK,GAAA,EAAK,CAAA;AACnE,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,cAAA,CAAe,QAAgB,MAAA,EAAgC;AACtE,EAAA,OAAO;AAAA,IACL,SAAA,CAAU,EAAE,OAAA,EAAQ,EAAG;AACrB,MAAA,IAAI,WAAW,QAAA,EAAU;AACvB,QAAA,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,eAAA,EAAiB,CAAA,OAAA,EAAU,MAAM,CAAA,CAAE,CAAA;AAAA,MACzD,CAAA,MAAO;AACL,QAAA,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,WAAA,EAAa,MAAM,CAAA;AAAA,MACzC;AACA,MAAA,OAAO,OAAA;AAAA,IACT;AAAA,GACF;AACF;AAOA,SAAS,eAAA,CAAgB,YAAoB,OAAA,EAAmC;AAC9E,EAAA,OAAO;AAAA,IACL,MAAM,UAAA,CAAW,EAAE,OAAA,EAAS,UAAS,EAAG;AACtC,MAAA,IAAI,cAAc,CAAA,IAAK,CAAC,mBAAmB,GAAA,CAAI,QAAA,CAAS,MAAM,CAAA,EAAG;AAC/D,QAAA,OAAO,QAAA;AAAA,MACT;AACA,MAAA,IAAI,OAAA,GAAU,QAAA;AACd,MAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,IAAW,UAAA,EAAY,OAAA,EAAA,EAAW;AACtD,QAAA,IAAI,CAAC,kBAAA,CAAmB,GAAA,CAAI,OAAA,CAAQ,MAAM,CAAA,EAAG;AAC7C,QAAA,MAAM,eAAe,iBAAA,CAAkB,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,aAAa,CAAC,CAAA;AACzE,QAAA,MAAM,UAAU,IAAA,CAAK,GAAA,CAAI,kBAAkB,CAAA,KAAM,OAAA,GAAU,IAAI,cAAc,CAAA;AAC7E,QAAA,MAAM,KAAA,CAAM,gBAAgB,OAAO,CAAA;AACnC,QAAA,OAAA,GAAU,MAAM,OAAA,CAAQ,OAAA,CAAQ,KAAA,EAAO,CAAA;AAAA,MACzC;AACA,MAAA,OAAO,OAAA;AAAA,IACT;AAAA,GACF;AACF;AAWO,IAAM,cAAN,MAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAad,GAAA;AAAA;AAAA,EAGA,OAAA;AAAA,EAET,YAAY,OAAA,EAA6B;AACvC,IAAA,IAAI,CAAC,SAAS,MAAA,EAAQ;AACpB,MAAA,MAAM,IAAI,MAAM,oCAAoC,CAAA;AAAA,IACtD;AACA,IAAA,IAAA,CAAK,OAAA,GAAU,QAAQ,OAAA,IAAW,gBAAA;AAClC,IAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,KAAA,IAAS,UAAA,CAAW,KAAA;AAC5C,IAAA,MAAM,UAAA,GAAa,QAAQ,UAAA,IAAc,CAAA;AAEzC,IAAA,IAAA,CAAK,MAAMA,6BAAA,CAAoB;AAAA,MAC7B,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,GAAI,QAAQ,KAAA,GAAQ,EAAE,OAAO,OAAA,CAAQ,KAAA,KAAU;AAAC,KACjD,CAAA;AACD,IAAA,IAAA,CAAK,GAAA,CAAI,IAAI,cAAA,CAAe,OAAA,CAAQ,QAAQ,OAAA,CAAQ,UAAA,IAAc,WAAW,CAAC,CAAA;AAC9E,IAAA,IAAI,aAAa,CAAA,EAAG;AAClB,MAAA,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,eAAA,CAAgB,UAAA,EAAY,OAAO,CAAC,CAAA;AAAA,IACnD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMS,KAAA,GAAQ;AAAA;AAAA,IAEf,IAAA,EAAM,OAAO,OAAA,KAAsC;AACjD,MAAA,MAAM,EAAE,MAAM,KAAA,EAAO,QAAA,KAAa,MAAM,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,yBAAA,EAA2B;AAAA,QAC9E,MAAA,EAAQ,EAAE,IAAA,EAAM,EAAE,SAAQ;AAAE,OAC7B,CAAA;AACD,MAAA,IAAI,KAAA,KAAU,UAAa,CAAC,QAAA,CAAS,IAAI,MAAM,cAAA,CAAe,UAAU,KAAK,CAAA;AAC7E,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASA,MAAA,EAAQ,OACN,OAAA,EACA,IAAA,EAGA,IAAA,KACkB;AAClB,MAAA,MAAM,EAAE,MAAM,KAAA,EAAO,QAAA,KAAa,MAAM,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,yBAAA,EAA2B;AAAA,QAC/E,MAAA,EAAQ;AAAA,UACN,IAAA,EAAM,EAAE,OAAA,EAAQ;AAAA,UAChB,GAAI,IAAA,EAAM,cAAA,GACN,EAAE,MAAA,EAAQ,EAAE,iBAAA,EAAmB,IAAA,CAAK,cAAA,EAAe,EAAE,GACrD;AAAC,SACP;AAAA,QACA;AAAA,OACD,CAAA;AACD,MAAA,IAAI,KAAA,KAAU,UAAa,CAAC,QAAA,CAAS,IAAI,MAAM,cAAA,CAAe,UAAU,KAAK,CAAA;AAC7E,MAAA,MAAM,OAAO,IAAA,EAAM,IAAA;AACnB,MAAA,IAAI,CAAC,IAAA,EAAM;AACT,QAAA,MAAM,cAAA,CAAe,QAAA,EAAU,EAAE,KAAA,EAAO,uCAAuC,CAAA;AAAA,MACjF;AACA,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAAA;AAAA,IAGA,GAAA,EAAK,OAAO,OAAA,EAAiB,MAAA,KAAqC;AAChE,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAO,UAAS,GAAI,MAAM,KAAK,GAAA,CAAI,GAAA;AAAA,QAC/C,kCAAA;AAAA,QACA,EAAE,QAAQ,EAAE,IAAA,EAAM,EAAE,OAAA,EAAS,MAAA,IAAS;AAAE,OAC1C;AACA,MAAA,IAAI,KAAA,KAAU,UAAa,CAAC,QAAA,CAAS,IAAI,MAAM,cAAA,CAAe,UAAU,KAAK,CAAA;AAC7E,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUA,MAAA,EAAQ,OACN,OAAA,EACA,MAAA,EACA,IAAA,KAGqB;AACrB,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAO,UAAS,GAAI,MAAM,KAAK,GAAA,CAAI,GAAA;AAAA,QAC/C,2CAAA;AAAA,QACA,EAAE,QAAQ,EAAE,IAAA,EAAM,EAAE,OAAA,EAAS,MAAA,EAAO,EAAE,EAAG,IAAA;AAAK,OAChD;AACA,MAAA,IAAI,KAAA,KAAU,UAAa,CAAC,QAAA,CAAS,IAAI,MAAM,cAAA,CAAe,UAAU,KAAK,CAAA;AAC7E,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAAA;AAAA,IAGA,MAAA,EAAQ,OAAO,OAAA,EAAiB,MAAA,KAAqC;AACnE,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAO,UAAS,GAAI,MAAM,KAAK,GAAA,CAAI,MAAA;AAAA,QAC/C,kCAAA;AAAA,QACA,EAAE,QAAQ,EAAE,IAAA,EAAM,EAAE,OAAA,EAAS,MAAA,IAAS;AAAE,OAC1C;AACA,MAAA,IAAI,KAAA,KAAU,UAAa,CAAC,QAAA,CAAS,IAAI,MAAM,cAAA,CAAe,UAAU,KAAK,CAAA;AAC7E,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAAA;AAAA,IAGA,KAAA,EAAO,OAAO,OAAA,EAAiB,MAAA,KAAqC;AAClE,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAO,UAAS,GAAI,MAAM,KAAK,GAAA,CAAI,IAAA;AAAA,QAC/C,wCAAA;AAAA,QACA,EAAE,QAAQ,EAAE,IAAA,EAAM,EAAE,OAAA,EAAS,MAAA,IAAS;AAAE,OAC1C;AACA,MAAA,IAAI,KAAA,KAAU,UAAa,CAAC,QAAA,CAAS,IAAI,MAAM,cAAA,CAAe,UAAU,KAAK,CAAA;AAC7E,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAAA;AAAA,IAGA,IAAA,EAAM,OAAO,OAAA,EAAiB,MAAA,KAAqC;AACjE,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAO,UAAS,GAAI,MAAM,KAAK,GAAA,CAAI,IAAA;AAAA,QAC/C,uCAAA;AAAA,QACA,EAAE,QAAQ,EAAE,IAAA,EAAM,EAAE,OAAA,EAAS,MAAA,IAAS;AAAE,OAC1C;AACA,MAAA,IAAI,KAAA,KAAU,UAAa,CAAC,QAAA,CAAS,IAAI,MAAM,cAAA,CAAe,UAAU,KAAK,CAAA;AAC7E,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,GACF;AAAA;AAAA;AAAA;AAAA,EAMS,OAAA,GAAU;AAAA;AAAA,IAEjB,SAAS,YAA8B;AACrC,MAAA,MAAM,EAAE,MAAM,KAAA,EAAO,QAAA,KAAa,MAAM,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,UAAU,CAAA;AAC/D,MAAA,IAAI,KAAA,KAAU,UAAa,CAAC,QAAA,CAAS,IAAI,MAAM,cAAA,CAAe,UAAU,KAAK,CAAA;AAC7E,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAAA;AAAA,IAGA,QAAQ,YAA8B;AACpC,MAAA,MAAM,EAAE,MAAM,KAAA,EAAO,QAAA,KAAa,MAAM,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,SAAS,CAAA;AAC9D,MAAA,IAAI,KAAA,KAAU,UAAa,CAAC,QAAA,CAAS,IAAI,MAAM,cAAA,CAAe,UAAU,KAAK,CAAA;AAC7E,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAAA;AAAA,IAGA,SAAS,YAA8B;AACrC,MAAA,MAAM,EAAE,MAAM,KAAA,EAAO,QAAA,KAAa,MAAM,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,UAAU,CAAA;AAC/D,MAAA,IAAI,KAAA,KAAU,UAAa,CAAC,QAAA,CAAS,IAAI,MAAM,cAAA,CAAe,UAAU,KAAK,CAAA;AAC7E,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,GACF;AACF","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"]}
|