@krovacloud/sdk 0.4.0 → 0.4.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/dist/index.cjs CHANGED
@@ -1,554 +1,579 @@
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
- }
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
40
18
  };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+ //#endregion
24
+ let openapi_fetch = require("openapi-fetch");
25
+ openapi_fetch = __toESM(openapi_fetch, 1);
26
+ //#region src/error.ts
27
+ /**
28
+ * Error thrown by the ergonomic {@link KrovaClient} helpers when the API
29
+ * responds with a non-2xx status.
30
+ *
31
+ * The raw openapi-fetch client (`client.raw`) never throws — it returns
32
+ * `{ data, error, response }`. The helpers wrap that and throw `KrovaError`
33
+ * so callers can `try/catch`.
34
+ */
35
+ var KrovaError = class KrovaError extends Error {
36
+ /** HTTP status code of the failing response. */
37
+ status;
38
+ /**
39
+ * A machine-readable error code, when the API surfaces one via the
40
+ * `X-Error-Code` response header. The documented error body only carries a
41
+ * human-readable `error` string, so this is best-effort.
42
+ */
43
+ code;
44
+ /**
45
+ * The request id from the `X-Request-Id` response header, when present.
46
+ * Useful when contacting Krova Cloud support about a specific failure.
47
+ */
48
+ requestId;
49
+ /** The parsed JSON error body, when the response had one. */
50
+ body;
51
+ /** The raw `Response` object, for callers that need headers/url/etc. */
52
+ response;
53
+ constructor(message, init) {
54
+ super(message);
55
+ this.name = "KrovaError";
56
+ this.status = init.status;
57
+ this.code = init.code;
58
+ this.requestId = init.requestId;
59
+ this.body = init.body;
60
+ this.response = init.response;
61
+ Object.setPrototypeOf(this, KrovaError.prototype);
62
+ }
63
+ };
64
+ /**
65
+ * Build a {@link KrovaError} from a failing response + parsed error body.
66
+ */
41
67
  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
- });
68
+ return new KrovaError(typeof body?.error === "string" && body.error || response.statusText || `Request failed with status ${response.status}`, {
69
+ status: response.status,
70
+ code: response.headers.get("x-error-code") ?? void 0,
71
+ requestId: response.headers.get("x-request-id") ?? void 0,
72
+ body,
73
+ response
74
+ });
50
75
  }
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));
76
+ //#endregion
77
+ //#region src/client.ts
78
+ /** Default API base URL — the single `servers[0].url` from the OpenAPI spec. */
79
+ const DEFAULT_BASE_URL = "https://krova.cloud/api/v1";
80
+ /** Statuses the retry middleware treats as transient. */
81
+ const RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 503]);
82
+ /** Fallback backoff (ms) when the server sends no `Retry-After` header. */
83
+ const BASE_BACKOFF_MS = 500;
84
+ /** Cap on any single backoff wait (ms), to keep retries "small but real". */
85
+ const MAX_BACKOFF_MS = 1e4;
86
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
87
+ /**
88
+ * Parse a `Retry-After` header (RFC 7231): either delta-seconds or an
89
+ * HTTP-date. Returns milliseconds to wait, or `null` if absent/unparseable.
90
+ */
58
91
  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;
92
+ if (!headerValue) return null;
93
+ const seconds = Number(headerValue);
94
+ if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
95
+ const dateMs = Date.parse(headerValue);
96
+ if (Number.isFinite(dateMs)) return Math.max(0, dateMs - Date.now());
97
+ return null;
65
98
  }
66
99
  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
- };
100
+ return { onRequest({ request }) {
101
+ if (scheme === "bearer") request.headers.set("Authorization", `Bearer ${apiKey}`);
102
+ else request.headers.set("X-API-KEY", apiKey);
103
+ return request;
104
+ } };
77
105
  }
106
+ /**
107
+ * Retry middleware: on a retryable status, wait (honoring `Retry-After` when
108
+ * present, else exponential backoff) and re-issue the request.
109
+ *
110
+ * A retried request may have a body (POST/PUT/DELETE — exactly the mutating,
111
+ * rate-limited endpoints). By the time `onResponse` runs, the request that was
112
+ * handed to `fetch` has had its body stream consumed, so `request.clone()` here
113
+ * throws `TypeError: unusable`. To re-issue it we stash a *pristine* clone in
114
+ * `onRequest` — captured before the body is read — keyed by openapi-fetch's
115
+ * per-request `id`, and clone from that pristine copy on each attempt.
116
+ */
78
117
  function retryMiddleware(maxRetries, doFetch) {
79
- const pristine = /* @__PURE__ */ new Map();
80
- return {
81
- onRequest({ request, id }) {
82
- pristine.set(id, request.clone());
83
- return request;
84
- },
85
- onError({ id }) {
86
- pristine.delete(id);
87
- },
88
- async onResponse({ request, response, id }) {
89
- const original = pristine.get(id) ?? request;
90
- pristine.delete(id);
91
- if (maxRetries <= 0 || !RETRYABLE_STATUSES.has(response.status)) {
92
- return response;
93
- }
94
- let current = response;
95
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
96
- if (!RETRYABLE_STATUSES.has(current.status)) break;
97
- const retryAfterMs = parseRetryAfterMs(current.headers.get("retry-after"));
98
- const backoff = Math.min(BASE_BACKOFF_MS * 2 ** (attempt - 1), MAX_BACKOFF_MS);
99
- await sleep(Math.min(retryAfterMs ?? backoff, MAX_BACKOFF_MS));
100
- current = await doFetch(original.clone());
101
- }
102
- return current;
103
- }
104
- };
118
+ const pristine = /* @__PURE__ */ new Map();
119
+ return {
120
+ onRequest({ request, id }) {
121
+ pristine.set(id, request.clone());
122
+ return request;
123
+ },
124
+ onError({ id }) {
125
+ pristine.delete(id);
126
+ },
127
+ async onResponse({ request, response, id }) {
128
+ const original = pristine.get(id) ?? request;
129
+ pristine.delete(id);
130
+ if (maxRetries <= 0 || !RETRYABLE_STATUSES.has(response.status)) return response;
131
+ let current = response;
132
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
133
+ if (!RETRYABLE_STATUSES.has(current.status)) break;
134
+ const retryAfterMs = parseRetryAfterMs(current.headers.get("retry-after"));
135
+ const backoff = Math.min(BASE_BACKOFF_MS * 2 ** (attempt - 1), MAX_BACKOFF_MS);
136
+ await sleep(Math.min(retryAfterMs ?? backoff, MAX_BACKOFF_MS));
137
+ current = await doFetch(original.clone());
138
+ }
139
+ return current;
140
+ }
141
+ };
105
142
  }
143
+ /**
144
+ * A typed client for the Krova Cloud API.
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * const krova = new KrovaClient({ apiKey: "kro_..." });
149
+ * const cubes = await krova.cubes.list("space_123");
150
+ * ```
151
+ */
106
152
  var KrovaClient = class {
107
- /**
108
- * The underlying openapi-fetch client — a fully typed escape hatch to every
109
- * path in the spec. Returns `{ data, error, response }` and never throws.
110
- *
111
- * @example
112
- * ```ts
113
- * const { data, error } = await krova.raw.GET(
114
- * "/spaces/{spaceId}/cubes/{cubeId}",
115
- * { params: { path: { spaceId, cubeId } } },
116
- * );
117
- * ```
118
- */
119
- raw;
120
- /** The resolved base URL in use. */
121
- baseUrl;
122
- constructor(options) {
123
- if (!options?.apiKey) {
124
- throw new Error("KrovaClient: `apiKey` is required.");
125
- }
126
- this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
127
- const doFetch = options.fetch ?? globalThis.fetch;
128
- const maxRetries = options.maxRetries ?? 2;
129
- this.raw = createClient__default.default({
130
- baseUrl: this.baseUrl,
131
- // SECURITY: never auto-follow redirects. The Krova Cloud API is a plain
132
- // JSON API and never legitimately 3xx's a data call. Following a redirect
133
- // would resend the `X-API-KEY` header to the redirect target — and unlike
134
- // `Authorization`, `Cookie`, and `Proxy-Authorization`, the Fetch spec does
135
- // NOT strip a custom header like `X-API-KEY` on a cross-origin redirect
136
- // (verified against undici/Node fetch). A compromised/misconfigured proxy,
137
- // an open-redirect on the API, or a MITM could otherwise exfiltrate the key
138
- // to an attacker's host. With `"manual"`, a redirect comes back as a
139
- // non-ok response and the helpers throw `KrovaError` instead of leaking.
140
- redirect: "manual",
141
- ...options.fetch ? { fetch: options.fetch } : {}
142
- });
143
- this.raw.use(authMiddleware(options.apiKey, options.authScheme ?? "x-api-key"));
144
- if (maxRetries > 0) {
145
- this.raw.use(retryMiddleware(maxRetries, doFetch));
146
- }
147
- }
148
- // ---------------------------------------------------------------------------
149
- // Cubes
150
- // ---------------------------------------------------------------------------
151
- cubes = {
152
- /** List Cubes in a Space, with pagination metadata. */
153
- list: async (spaceId) => {
154
- const { data, error, response } = await this.raw.GET("/spaces/{spaceId}/cubes", {
155
- params: { path: { spaceId } }
156
- });
157
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
158
- if (data === void 0)
159
- throw krovaErrorFrom(response, { error: "List Cubes response was empty." });
160
- return data;
161
- },
162
- /**
163
- * Create a Cube. Returns the created {@link Cube}.
164
- *
165
- * @param spaceId Target Space id.
166
- * @param body Cube spec — `{ name, image, resources, sshPublicKey, ... }`.
167
- * @param opts Optional `idempotencyKey` (max 255 chars, scoped per space).
168
- */
169
- create: async (spaceId, body, opts) => {
170
- const { data, error, response } = await this.raw.POST("/spaces/{spaceId}/cubes", {
171
- params: {
172
- path: { spaceId },
173
- ...opts?.idempotencyKey ? { header: { "Idempotency-Key": opts.idempotencyKey } } : {}
174
- },
175
- body
176
- });
177
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
178
- const cube = data?.cube;
179
- if (!cube) {
180
- throw krovaErrorFrom(response, { error: "Create Cube response had no `cube`." });
181
- }
182
- return cube;
183
- },
184
- /** Get a single Cube. Returns the {@link Cube}. */
185
- get: async (spaceId, cubeId) => {
186
- const { data, error, response } = await this.raw.GET(
187
- "/spaces/{spaceId}/cubes/{cubeId}",
188
- { params: { path: { spaceId, cubeId } } }
189
- );
190
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
191
- const cube = data?.cube;
192
- if (!cube) {
193
- throw krovaErrorFrom(response, { error: "Get Cube response had no `cube`." });
194
- }
195
- return cube;
196
- },
197
- /**
198
- * Update the IN-CUBE port that SSH is forwarded to.
199
- *
200
- * `cubePort` is the port **inside** the Cube that sshd listens on NOT the
201
- * host port you connect to. The host port is allocated by Krova and is not
202
- * changed by this call. Pointing this at a port nothing is listening on
203
- * inside the Cube will silently make SSH unreachable; the default is 22.
204
- *
205
- * The Krova Cloud API exposes no general Cube-mutation endpoint; the only
206
- * mutable Cube field over the API is this port, via
207
- * `PUT /spaces/{spaceId}/cubes/{cubeId}/ssh-port`. This helper maps to that
208
- * endpoint. (Compute resize / rename are not part of the public API.)
209
- */
210
- update: async (spaceId, cubeId, body) => {
211
- const { data, error, response } = await this.raw.PUT(
212
- "/spaces/{spaceId}/cubes/{cubeId}/ssh-port",
213
- { params: { path: { spaceId, cubeId } }, body }
214
- );
215
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
216
- return data;
217
- },
218
- /** Delete a Cube (asynchronous — deletion is enqueued). */
219
- delete: async (spaceId, cubeId) => {
220
- const { data, error, response } = await this.raw.DELETE(
221
- "/spaces/{spaceId}/cubes/{cubeId}",
222
- { params: { path: { spaceId, cubeId } } }
223
- );
224
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
225
- if (data === void 0)
226
- throw krovaErrorFrom(response, { error: "Delete Cube response was empty." });
227
- return data;
228
- },
229
- /** Power off a running Cube (asynchronous power-off is enqueued). The Cube
230
- * becomes `stopped` (its host RAM is freed); start it again with `wake`. */
231
- powerOff: async (spaceId, cubeId) => {
232
- const { data, error, response } = await this.raw.POST(
233
- "/spaces/{spaceId}/cubes/{cubeId}/power-off",
234
- { params: { path: { spaceId, cubeId } } }
235
- );
236
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
237
- return data;
238
- },
239
- /** Start a stopped Cube (asynchronous — start is enqueued). */
240
- /**
241
- * Restart a Cube (COLD restart).
242
- *
243
- * The hypervisor process is stopped and relaunched, so the Cube boots
244
- * against the host's current kernel. This is the only way a Cube picks up a
245
- * refreshed guest kernel after a platform image update — a `reboot` issued
246
- * INSIDE the Cube cannot do it, because Firecracker treats a guest reboot as
247
- * a shutdown and the kernel is supplied externally by the host.
248
- *
249
- * Disk state is preserved; only the kernel changes. The Cube must be
250
- * `running`. Concurrent restarts of the same Cube are rejected (409) rather
251
- * than queued twice.
252
- */
253
- restart: async (spaceId, cubeId) => {
254
- const { data, error, response } = await this.raw.POST(
255
- "/spaces/{spaceId}/cubes/{cubeId}/restart",
256
- { params: { path: { spaceId, cubeId } } }
257
- );
258
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
259
- return data;
260
- },
261
- wake: async (spaceId, cubeId) => {
262
- const { data, error, response } = await this.raw.POST(
263
- "/spaces/{spaceId}/cubes/{cubeId}/wake",
264
- { params: { path: { spaceId, cubeId } } }
265
- );
266
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
267
- return data;
268
- },
269
- /**
270
- * Get a Cube's SSH connection info — host, port, login user, and (when
271
- * available) the pinned host public keys for strict host-key verification.
272
- */
273
- ssh: async (spaceId, cubeId) => {
274
- const { data, error, response } = await this.raw.GET(
275
- "/spaces/{spaceId}/cubes/{cubeId}/ssh",
276
- { params: { path: { spaceId, cubeId } } }
277
- );
278
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
279
- if (data === void 0)
280
- throw krovaErrorFrom(response, { error: "Cube SSH-info response was empty." });
281
- return data;
282
- },
283
- /**
284
- * Restore a Cube's disk from one of its {@link Snapshot}s (asynchronous —
285
- * the restore is enqueued). The Cube's current disk is replaced.
286
- */
287
- restore: async (spaceId, cubeId, snapshotId) => {
288
- const { data, error, response } = await this.raw.POST(
289
- "/spaces/{spaceId}/cubes/{cubeId}/restore",
290
- { params: { path: { spaceId, cubeId } }, body: { snapshotId } }
291
- );
292
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
293
- return data;
294
- }
295
- };
296
- /**
297
- * Resolve the {@link Space} this API key is scoped to — so you don't have to
298
- * hardcode a `spaceId`. Handy right after constructing the client:
299
- *
300
- * @example
301
- * ```ts
302
- * const space = await krova.getSpace();
303
- * const cubes = await krova.cubes.list(space.id);
304
- * ```
305
- */
306
- async getSpace() {
307
- const { data, error, response } = await this.raw.GET("/space");
308
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
309
- if (data === void 0)
310
- throw krovaErrorFrom(response, { error: "Space response was empty." });
311
- return data;
312
- }
313
- // ---------------------------------------------------------------------------
314
- // Custom domains
315
- // ---------------------------------------------------------------------------
316
- domains = {
317
- /** List the custom domains attached to a Cube. */
318
- list: async (spaceId, cubeId) => {
319
- const { data, error, response } = await this.raw.GET(
320
- "/spaces/{spaceId}/cubes/{cubeId}/domains",
321
- { params: { path: { spaceId, cubeId } } }
322
- );
323
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
324
- return data?.domains ?? [];
325
- },
326
- /**
327
- * Attach a custom domain to a Cube. `domain` + `port` are required.
328
- *
329
- * Returns the domain AND the DNS records you must publish for it to work —
330
- * so you can create them in the same run, without a second call and without
331
- * hard-coding record shapes. A wildcard needs three; an exact host needs one.
332
- *
333
- * ⛔ BREAKING in 0.4.0: this used to resolve to `Domain`. It now resolves to
334
- * `{ domain, records }`, because for a wildcard two of the three records
335
- * (the ownership TXT and the `_acme-challenge` delegation) were not
336
- * derivable from anything the SDK returned an integration had to read
337
- * them out of the docs and hope they still matched the server.
338
- */
339
- create: async (spaceId, cubeId, body) => {
340
- const { data, error, response } = await this.raw.POST(
341
- "/spaces/{spaceId}/cubes/{cubeId}/domains",
342
- { params: { path: { spaceId, cubeId } }, body }
343
- );
344
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
345
- if (!data?.domain)
346
- throw krovaErrorFrom(response, { error: "Create domain response had no `domain`." });
347
- return { domain: data.domain, records: data.records ?? [] };
348
- },
349
- /**
350
- * The DNS records a domain needs, each checked against live DNS.
351
- *
352
- * Poll this after publishing them: `summary.complete` turns true only once
353
- * every record is `found`. Each call performs real DNS lookups and is rate
354
- * limited, so poll on an interval rather than in a tight loop.
355
- */
356
- records: async (spaceId, cubeId, mappingId) => {
357
- const { data, error, response } = await this.raw.GET(
358
- "/spaces/{spaceId}/cubes/{cubeId}/domains/{mappingId}/records",
359
- { params: { path: { spaceId, cubeId, mappingId } } }
360
- );
361
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
362
- if (!data)
363
- throw krovaErrorFrom(response, { error: "Domain records response was empty." });
364
- return data;
365
- },
366
- /** Update a domain's per-domain proxy settings. */
367
- update: async (spaceId, cubeId, mappingId, body) => {
368
- const { data, error, response } = await this.raw.PATCH(
369
- "/spaces/{spaceId}/cubes/{cubeId}/domains/{mappingId}",
370
- { params: { path: { spaceId, cubeId, mappingId } }, body }
371
- );
372
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
373
- if (!data?.domain)
374
- throw krovaErrorFrom(response, { error: "Update domain response had no `domain`." });
375
- return data.domain;
376
- },
377
- /** Detach a custom domain from a Cube. */
378
- delete: async (spaceId, cubeId, mappingId) => {
379
- const { data, error, response } = await this.raw.DELETE(
380
- "/spaces/{spaceId}/cubes/{cubeId}/domains/{mappingId}",
381
- { params: { path: { spaceId, cubeId, mappingId } } }
382
- );
383
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
384
- return data;
385
- }
386
- };
387
- // ---------------------------------------------------------------------------
388
- // Snapshots
389
- // ---------------------------------------------------------------------------
390
- snapshots = {
391
- /** List a Cube's snapshots. */
392
- list: async (spaceId, cubeId) => {
393
- const { data, error, response } = await this.raw.GET(
394
- "/spaces/{spaceId}/cubes/{cubeId}/snapshots",
395
- { params: { path: { spaceId, cubeId } } }
396
- );
397
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
398
- return data?.snapshots ?? [];
399
- },
400
- /** Create a snapshot of a Cube's disk (asynchronous — enqueued). */
401
- create: async (spaceId, cubeId, body) => {
402
- const { data, error, response } = await this.raw.POST(
403
- "/spaces/{spaceId}/cubes/{cubeId}/snapshots",
404
- { params: { path: { spaceId, cubeId } }, body: body ?? {} }
405
- );
406
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
407
- if (!data?.snapshot)
408
- throw krovaErrorFrom(response, { error: "Create snapshot response had no `snapshot`." });
409
- return data.snapshot;
410
- },
411
- /** Delete a snapshot. */
412
- delete: async (spaceId, cubeId, snapshotId) => {
413
- const { data, error, response } = await this.raw.DELETE(
414
- "/spaces/{spaceId}/cubes/{cubeId}/snapshots/{snapshotId}",
415
- { params: { path: { spaceId, cubeId, snapshotId } } }
416
- );
417
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
418
- return data;
419
- }
420
- };
421
- // ---------------------------------------------------------------------------
422
- // TCP port mappings
423
- // ---------------------------------------------------------------------------
424
- tcpMappings = {
425
- /** List a Cube's TCP port mappings. */
426
- list: async (spaceId, cubeId) => {
427
- const { data, error, response } = await this.raw.GET(
428
- "/spaces/{spaceId}/cubes/{cubeId}/tcp-mappings",
429
- { params: { path: { spaceId, cubeId } } }
430
- );
431
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
432
- return data?.tcpMappings ?? [];
433
- },
434
- /**
435
- * Create a TCP port mapping exposing a Cube port on the host. `cubePort` is
436
- * required; `whitelistIps` optionally restricts who can reach it.
437
- */
438
- create: async (spaceId, cubeId, body) => {
439
- const { data, error, response } = await this.raw.POST(
440
- "/spaces/{spaceId}/cubes/{cubeId}/tcp-mappings",
441
- { params: { path: { spaceId, cubeId } }, body }
442
- );
443
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
444
- if (!data?.tcpMapping)
445
- throw krovaErrorFrom(response, { error: "Create TCP mapping response had no `tcpMapping`." });
446
- return data.tcpMapping;
447
- },
448
- /** Delete a TCP port mapping. */
449
- delete: async (spaceId, cubeId, mappingId) => {
450
- const { data, error, response } = await this.raw.DELETE(
451
- "/spaces/{spaceId}/cubes/{cubeId}/tcp-mappings/{mappingId}",
452
- { params: { path: { spaceId, cubeId, mappingId } } }
453
- );
454
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
455
- return data;
456
- }
457
- };
458
- // ---------------------------------------------------------------------------
459
- // Imports & backups (.cube archive import / export)
460
- // ---------------------------------------------------------------------------
461
- imports = {
462
- /**
463
- * Start importing a `.cube` archive into a new Cube. Returns the multipart
464
- * upload target (`importId`, `uploadId`, presigned `parts`, …). Upload the
465
- * archive to those URLs, then call {@link imports.complete}.
466
- */
467
- create: async (spaceId, body) => {
468
- const { data, error, response } = await this.raw.POST("/spaces/{spaceId}/cubes/imports", {
469
- params: { path: { spaceId } },
470
- body
471
- });
472
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
473
- return data;
474
- },
475
- /** Get an in-progress or completed import by id. */
476
- get: async (spaceId, importId) => {
477
- const { data, error, response } = await this.raw.GET(
478
- "/spaces/{spaceId}/cubes/imports/{importId}",
479
- { params: { path: { spaceId, importId } } }
480
- );
481
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
482
- return data;
483
- },
484
- /**
485
- * Finish an import after the archive has been uploaded provisions the
486
- * Cube. Pass the uploaded `parts` (partNumber + etag) and the resolved
487
- * `config`.
488
- */
489
- complete: async (spaceId, importId, body) => {
490
- const { data, error, response } = await this.raw.POST(
491
- "/spaces/{spaceId}/cubes/imports/{importId}/complete",
492
- { params: { path: { spaceId, importId } }, body }
493
- );
494
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
495
- return data;
496
- },
497
- /** Cancel an in-progress import. */
498
- cancel: async (spaceId, importId) => {
499
- const { data, error, response } = await this.raw.DELETE(
500
- "/spaces/{spaceId}/cubes/imports/{importId}",
501
- { params: { path: { spaceId, importId } } }
502
- );
503
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
504
- return data;
505
- }
506
- };
507
- backups = {
508
- /** Get a time-limited download URL for a backup `.cube` archive. */
509
- download: async (spaceId, backupId) => {
510
- const { data, error, response } = await this.raw.GET(
511
- "/spaces/{spaceId}/backups/{backupId}/download",
512
- { params: { path: { spaceId, backupId } } }
513
- );
514
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
515
- return data;
516
- }
517
- };
518
- // ---------------------------------------------------------------------------
519
- // Public catalog (no auth required by the API, but the key is harmless)
520
- // ---------------------------------------------------------------------------
521
- catalog = {
522
- /** List regions with available capacity. */
523
- regions: async () => {
524
- const { data, error, response } = await this.raw.GET("/regions");
525
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
526
- if (data === void 0)
527
- throw krovaErrorFrom(response, { error: "Regions response was empty." });
528
- return data;
529
- },
530
- /** List available OS images. */
531
- images: async () => {
532
- const { data, error, response } = await this.raw.GET("/images");
533
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
534
- if (data === void 0)
535
- throw krovaErrorFrom(response, { error: "Images response was empty." });
536
- return data;
537
- },
538
- /** Per-resource hourly rates and volume pricing tiers. */
539
- pricing: async () => {
540
- const { data, error, response } = await this.raw.GET("/pricing");
541
- if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
542
- if (data === void 0)
543
- throw krovaErrorFrom(response, { error: "Pricing response was empty." });
544
- return data;
545
- }
546
- };
153
+ /**
154
+ * The underlying openapi-fetch client — a fully typed escape hatch to every
155
+ * path in the spec. Returns `{ data, error, response }` and never throws.
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * const { data, error } = await krova.raw.GET(
160
+ * "/spaces/{spaceId}/cubes/{cubeId}",
161
+ * { params: { path: { spaceId, cubeId } } },
162
+ * );
163
+ * ```
164
+ */
165
+ raw;
166
+ /** The resolved base URL in use. */
167
+ baseUrl;
168
+ constructor(options) {
169
+ if (!options?.apiKey) throw new Error("KrovaClient: `apiKey` is required.");
170
+ this.baseUrl = options.baseUrl ?? "https://krova.cloud/api/v1";
171
+ const doFetch = options.fetch ?? globalThis.fetch;
172
+ const maxRetries = options.maxRetries ?? 2;
173
+ this.raw = (0, openapi_fetch.default)({
174
+ baseUrl: this.baseUrl,
175
+ redirect: "manual",
176
+ ...options.fetch ? { fetch: options.fetch } : {}
177
+ });
178
+ this.raw.use(authMiddleware(options.apiKey, options.authScheme ?? "x-api-key"));
179
+ if (maxRetries > 0) this.raw.use(retryMiddleware(maxRetries, doFetch));
180
+ }
181
+ cubes = {
182
+ /** List Cubes in a Space, with pagination metadata. */
183
+ list: async (spaceId) => {
184
+ const { data, error, response } = await this.raw.GET("/spaces/{spaceId}/cubes", { params: { path: { spaceId } } });
185
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
186
+ if (data === void 0) throw krovaErrorFrom(response, { error: "List Cubes response was empty." });
187
+ return data;
188
+ },
189
+ /**
190
+ * Create a Cube. Returns the created {@link Cube}.
191
+ *
192
+ * @param spaceId Target Space id.
193
+ * @param body Cube spec — `{ name, image, resources, sshPublicKey, ... }`.
194
+ * @param opts Optional `idempotencyKey` (max 255 chars, scoped per space).
195
+ */
196
+ create: async (spaceId, body, opts) => {
197
+ const { data, error, response } = await this.raw.POST("/spaces/{spaceId}/cubes", {
198
+ params: {
199
+ path: { spaceId },
200
+ ...opts?.idempotencyKey ? { header: { "Idempotency-Key": opts.idempotencyKey } } : {}
201
+ },
202
+ body
203
+ });
204
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
205
+ const cube = data?.cube;
206
+ if (!cube) throw krovaErrorFrom(response, { error: "Create Cube response had no `cube`." });
207
+ return cube;
208
+ },
209
+ /** Get a single Cube. Returns the {@link Cube}. */
210
+ get: async (spaceId, cubeId) => {
211
+ const { data, error, response } = await this.raw.GET("/spaces/{spaceId}/cubes/{cubeId}", { params: { path: {
212
+ spaceId,
213
+ cubeId
214
+ } } });
215
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
216
+ const cube = data?.cube;
217
+ if (!cube) throw krovaErrorFrom(response, { error: "Get Cube response had no `cube`." });
218
+ return cube;
219
+ },
220
+ /**
221
+ * Update the IN-CUBE port that SSH is forwarded to.
222
+ *
223
+ * `cubePort` is the port **inside** the Cube that sshd listens on — NOT the
224
+ * host port you connect to. The host port is allocated by Krova and is not
225
+ * changed by this call. Pointing this at a port nothing is listening on
226
+ * inside the Cube will silently make SSH unreachable; the default is 22.
227
+ *
228
+ * The Krova Cloud API exposes no general Cube-mutation endpoint; the only
229
+ * mutable Cube field over the API is this port, via
230
+ * `PUT /spaces/{spaceId}/cubes/{cubeId}/ssh-port`. This helper maps to that
231
+ * endpoint. (Compute resize / rename are not part of the public API.)
232
+ */
233
+ update: async (spaceId, cubeId, body) => {
234
+ const { data, error, response } = await this.raw.PUT("/spaces/{spaceId}/cubes/{cubeId}/ssh-port", {
235
+ params: { path: {
236
+ spaceId,
237
+ cubeId
238
+ } },
239
+ body
240
+ });
241
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
242
+ return data;
243
+ },
244
+ /** Delete a Cube (asynchronous deletion is enqueued). */
245
+ delete: async (spaceId, cubeId) => {
246
+ const { data, error, response } = await this.raw.DELETE("/spaces/{spaceId}/cubes/{cubeId}", { params: { path: {
247
+ spaceId,
248
+ cubeId
249
+ } } });
250
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
251
+ if (data === void 0) throw krovaErrorFrom(response, { error: "Delete Cube response was empty." });
252
+ return data;
253
+ },
254
+ /** Power off a running Cube (asynchronous power-off is enqueued). The Cube
255
+ * becomes `stopped` (its host RAM is freed); start it again with `wake`. */
256
+ powerOff: async (spaceId, cubeId) => {
257
+ const { data, error, response } = await this.raw.POST("/spaces/{spaceId}/cubes/{cubeId}/power-off", { params: { path: {
258
+ spaceId,
259
+ cubeId
260
+ } } });
261
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
262
+ return data;
263
+ },
264
+ /** Start a stopped Cube (asynchronous — start is enqueued). */
265
+ /**
266
+ * Restart a Cube (COLD restart).
267
+ *
268
+ * The hypervisor process is stopped and relaunched, so the Cube boots
269
+ * against the host's current kernel. This is the only way a Cube picks up a
270
+ * refreshed guest kernel after a platform image update — a `reboot` issued
271
+ * INSIDE the Cube cannot do it, because Firecracker treats a guest reboot as
272
+ * a shutdown and the kernel is supplied externally by the host.
273
+ *
274
+ * Disk state is preserved; only the kernel changes. The Cube must be
275
+ * `running`. Concurrent restarts of the same Cube are rejected (409) rather
276
+ * than queued twice.
277
+ */
278
+ restart: async (spaceId, cubeId) => {
279
+ const { data, error, response } = await this.raw.POST("/spaces/{spaceId}/cubes/{cubeId}/restart", { params: { path: {
280
+ spaceId,
281
+ cubeId
282
+ } } });
283
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
284
+ return data;
285
+ },
286
+ wake: async (spaceId, cubeId) => {
287
+ const { data, error, response } = await this.raw.POST("/spaces/{spaceId}/cubes/{cubeId}/wake", { params: { path: {
288
+ spaceId,
289
+ cubeId
290
+ } } });
291
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
292
+ return data;
293
+ },
294
+ /**
295
+ * Get a Cube's SSH connection info host, port, login user, and (when
296
+ * available) the pinned host public keys for strict host-key verification.
297
+ */
298
+ ssh: async (spaceId, cubeId) => {
299
+ const { data, error, response } = await this.raw.GET("/spaces/{spaceId}/cubes/{cubeId}/ssh", { params: { path: {
300
+ spaceId,
301
+ cubeId
302
+ } } });
303
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
304
+ if (data === void 0) throw krovaErrorFrom(response, { error: "Cube SSH-info response was empty." });
305
+ return data;
306
+ },
307
+ /**
308
+ * Restore a Cube's disk from one of its {@link Snapshot}s (asynchronous —
309
+ * the restore is enqueued). The Cube's current disk is replaced.
310
+ */
311
+ restore: async (spaceId, cubeId, snapshotId) => {
312
+ const { data, error, response } = await this.raw.POST("/spaces/{spaceId}/cubes/{cubeId}/restore", {
313
+ params: { path: {
314
+ spaceId,
315
+ cubeId
316
+ } },
317
+ body: { snapshotId }
318
+ });
319
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
320
+ return data;
321
+ }
322
+ };
323
+ /**
324
+ * Resolve the {@link Space} this API key is scoped to — so you don't have to
325
+ * hardcode a `spaceId`. Handy right after constructing the client:
326
+ *
327
+ * @example
328
+ * ```ts
329
+ * const space = await krova.getSpace();
330
+ * const cubes = await krova.cubes.list(space.id);
331
+ * ```
332
+ */
333
+ async getSpace() {
334
+ const { data, error, response } = await this.raw.GET("/space");
335
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
336
+ if (data === void 0) throw krovaErrorFrom(response, { error: "Space response was empty." });
337
+ return data;
338
+ }
339
+ domains = {
340
+ /** List the custom domains attached to a Cube. */
341
+ list: async (spaceId, cubeId) => {
342
+ const { data, error, response } = await this.raw.GET("/spaces/{spaceId}/cubes/{cubeId}/domains", { params: { path: {
343
+ spaceId,
344
+ cubeId
345
+ } } });
346
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
347
+ return data?.domains ?? [];
348
+ },
349
+ /**
350
+ * Attach a custom domain to a Cube. `domain` + `port` are required.
351
+ *
352
+ * Returns the domain AND the DNS records you must publish for it to work —
353
+ * so you can create them in the same run, without a second call and without
354
+ * hard-coding record shapes. A wildcard needs three; an exact host needs one.
355
+ *
356
+ * BREAKING in 0.4.0: this used to resolve to `Domain`. It now resolves to
357
+ * `{ domain, records }`, because for a wildcard two of the three records
358
+ * (the ownership TXT and the `_acme-challenge` delegation) were not
359
+ * derivable from anything the SDK returned — an integration had to read
360
+ * them out of the docs and hope they still matched the server.
361
+ */
362
+ create: async (spaceId, cubeId, body) => {
363
+ const { data, error, response } = await this.raw.POST("/spaces/{spaceId}/cubes/{cubeId}/domains", {
364
+ params: { path: {
365
+ spaceId,
366
+ cubeId
367
+ } },
368
+ body
369
+ });
370
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
371
+ if (!data?.domain) throw krovaErrorFrom(response, { error: "Create domain response had no `domain`." });
372
+ return {
373
+ domain: data.domain,
374
+ records: data.records ?? []
375
+ };
376
+ },
377
+ /**
378
+ * The DNS records a domain needs, each checked against live DNS.
379
+ *
380
+ * Poll this after publishing them: `summary.complete` turns true only once
381
+ * every record is `found`. Each call performs real DNS lookups and is rate
382
+ * limited, so poll on an interval rather than in a tight loop.
383
+ */
384
+ records: async (spaceId, cubeId, mappingId) => {
385
+ const { data, error, response } = await this.raw.GET("/spaces/{spaceId}/cubes/{cubeId}/domains/{mappingId}/records", { params: { path: {
386
+ spaceId,
387
+ cubeId,
388
+ mappingId
389
+ } } });
390
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
391
+ if (!data) throw krovaErrorFrom(response, { error: "Domain records response was empty." });
392
+ return data;
393
+ },
394
+ /** Update a domain's per-domain proxy settings. */
395
+ update: async (spaceId, cubeId, mappingId, body) => {
396
+ const { data, error, response } = await this.raw.PATCH("/spaces/{spaceId}/cubes/{cubeId}/domains/{mappingId}", {
397
+ params: { path: {
398
+ spaceId,
399
+ cubeId,
400
+ mappingId
401
+ } },
402
+ body
403
+ });
404
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
405
+ if (!data?.domain) throw krovaErrorFrom(response, { error: "Update domain response had no `domain`." });
406
+ return data.domain;
407
+ },
408
+ /** Detach a custom domain from a Cube. */
409
+ delete: async (spaceId, cubeId, mappingId) => {
410
+ const { data, error, response } = await this.raw.DELETE("/spaces/{spaceId}/cubes/{cubeId}/domains/{mappingId}", { params: { path: {
411
+ spaceId,
412
+ cubeId,
413
+ mappingId
414
+ } } });
415
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
416
+ return data;
417
+ }
418
+ };
419
+ snapshots = {
420
+ /** List a Cube's snapshots. */
421
+ list: async (spaceId, cubeId) => {
422
+ const { data, error, response } = await this.raw.GET("/spaces/{spaceId}/cubes/{cubeId}/snapshots", { params: { path: {
423
+ spaceId,
424
+ cubeId
425
+ } } });
426
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
427
+ return data?.snapshots ?? [];
428
+ },
429
+ /** Create a snapshot of a Cube's disk (asynchronous — enqueued). */
430
+ create: async (spaceId, cubeId, body) => {
431
+ const { data, error, response } = await this.raw.POST("/spaces/{spaceId}/cubes/{cubeId}/snapshots", {
432
+ params: { path: {
433
+ spaceId,
434
+ cubeId
435
+ } },
436
+ body: body ?? {}
437
+ });
438
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
439
+ if (!data?.snapshot) throw krovaErrorFrom(response, { error: "Create snapshot response had no `snapshot`." });
440
+ return data.snapshot;
441
+ },
442
+ /** Delete a snapshot. */
443
+ delete: async (spaceId, cubeId, snapshotId) => {
444
+ const { data, error, response } = await this.raw.DELETE("/spaces/{spaceId}/cubes/{cubeId}/snapshots/{snapshotId}", { params: { path: {
445
+ spaceId,
446
+ cubeId,
447
+ snapshotId
448
+ } } });
449
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
450
+ return data;
451
+ }
452
+ };
453
+ tcpMappings = {
454
+ /** List a Cube's TCP port mappings. */
455
+ list: async (spaceId, cubeId) => {
456
+ const { data, error, response } = await this.raw.GET("/spaces/{spaceId}/cubes/{cubeId}/tcp-mappings", { params: { path: {
457
+ spaceId,
458
+ cubeId
459
+ } } });
460
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
461
+ return data?.tcpMappings ?? [];
462
+ },
463
+ /**
464
+ * Create a TCP port mapping exposing a Cube port on the host. `cubePort` is
465
+ * required; `whitelistIps` optionally restricts who can reach it.
466
+ */
467
+ create: async (spaceId, cubeId, body) => {
468
+ const { data, error, response } = await this.raw.POST("/spaces/{spaceId}/cubes/{cubeId}/tcp-mappings", {
469
+ params: { path: {
470
+ spaceId,
471
+ cubeId
472
+ } },
473
+ body
474
+ });
475
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
476
+ if (!data?.tcpMapping) throw krovaErrorFrom(response, { error: "Create TCP mapping response had no `tcpMapping`." });
477
+ return data.tcpMapping;
478
+ },
479
+ /** Delete a TCP port mapping. */
480
+ delete: async (spaceId, cubeId, mappingId) => {
481
+ const { data, error, response } = await this.raw.DELETE("/spaces/{spaceId}/cubes/{cubeId}/tcp-mappings/{mappingId}", { params: { path: {
482
+ spaceId,
483
+ cubeId,
484
+ mappingId
485
+ } } });
486
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
487
+ return data;
488
+ }
489
+ };
490
+ imports = {
491
+ /**
492
+ * Start importing a `.cube` archive into a new Cube. Returns the multipart
493
+ * upload target (`importId`, `uploadId`, presigned `parts`, …). Upload the
494
+ * archive to those URLs, then call {@link imports.complete}.
495
+ */
496
+ create: async (spaceId, body) => {
497
+ const { data, error, response } = await this.raw.POST("/spaces/{spaceId}/cubes/imports", {
498
+ params: { path: { spaceId } },
499
+ body
500
+ });
501
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
502
+ return data;
503
+ },
504
+ /** Get an in-progress or completed import by id. */
505
+ get: async (spaceId, importId) => {
506
+ const { data, error, response } = await this.raw.GET("/spaces/{spaceId}/cubes/imports/{importId}", { params: { path: {
507
+ spaceId,
508
+ importId
509
+ } } });
510
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
511
+ return data;
512
+ },
513
+ /**
514
+ * Finish an import after the archive has been uploaded — provisions the
515
+ * Cube. Pass the uploaded `parts` (partNumber + etag) and the resolved
516
+ * `config`.
517
+ */
518
+ complete: async (spaceId, importId, body) => {
519
+ const { data, error, response } = await this.raw.POST("/spaces/{spaceId}/cubes/imports/{importId}/complete", {
520
+ params: { path: {
521
+ spaceId,
522
+ importId
523
+ } },
524
+ body
525
+ });
526
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
527
+ return data;
528
+ },
529
+ /** Cancel an in-progress import. */
530
+ cancel: async (spaceId, importId) => {
531
+ const { data, error, response } = await this.raw.DELETE("/spaces/{spaceId}/cubes/imports/{importId}", { params: { path: {
532
+ spaceId,
533
+ importId
534
+ } } });
535
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
536
+ return data;
537
+ }
538
+ };
539
+ backups = {
540
+ /** Get a time-limited download URL for a backup `.cube` archive. */
541
+ download: async (spaceId, backupId) => {
542
+ const { data, error, response } = await this.raw.GET("/spaces/{spaceId}/backups/{backupId}/download", { params: { path: {
543
+ spaceId,
544
+ backupId
545
+ } } });
546
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
547
+ return data;
548
+ } };
549
+ catalog = {
550
+ /** List regions with available capacity. */
551
+ regions: async () => {
552
+ const { data, error, response } = await this.raw.GET("/regions");
553
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
554
+ if (data === void 0) throw krovaErrorFrom(response, { error: "Regions response was empty." });
555
+ return data;
556
+ },
557
+ /** List available OS images. */
558
+ images: async () => {
559
+ const { data, error, response } = await this.raw.GET("/images");
560
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
561
+ if (data === void 0) throw krovaErrorFrom(response, { error: "Images response was empty." });
562
+ return data;
563
+ },
564
+ /** Per-resource hourly rates and volume pricing tiers. */
565
+ pricing: async () => {
566
+ const { data, error, response } = await this.raw.GET("/pricing");
567
+ if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
568
+ if (data === void 0) throw krovaErrorFrom(response, { error: "Pricing response was empty." });
569
+ return data;
570
+ }
571
+ };
547
572
  };
548
-
573
+ //#endregion
549
574
  exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
550
575
  exports.KrovaClient = KrovaClient;
551
576
  exports.KrovaError = KrovaError;
552
577
  exports.krovaErrorFrom = krovaErrorFrom;
553
- //# sourceMappingURL=index.cjs.map
578
+
554
579
  //# sourceMappingURL=index.cjs.map