@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/dist/index.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import createClient from 'openapi-fetch';
|
|
2
|
+
|
|
3
|
+
// src/client.ts
|
|
4
|
+
|
|
5
|
+
// src/error.ts
|
|
6
|
+
var KrovaError = class _KrovaError extends Error {
|
|
7
|
+
/** HTTP status code of the failing response. */
|
|
8
|
+
status;
|
|
9
|
+
/**
|
|
10
|
+
* A machine-readable error code, when the API surfaces one via the
|
|
11
|
+
* `X-Error-Code` response header. The documented error body only carries a
|
|
12
|
+
* human-readable `error` string, so this is best-effort.
|
|
13
|
+
*/
|
|
14
|
+
code;
|
|
15
|
+
/**
|
|
16
|
+
* The request id from the `X-Request-Id` response header, when present.
|
|
17
|
+
* Useful when contacting Krova Cloud support about a specific failure.
|
|
18
|
+
*/
|
|
19
|
+
requestId;
|
|
20
|
+
/** The parsed JSON error body, when the response had one. */
|
|
21
|
+
body;
|
|
22
|
+
/** The raw `Response` object, for callers that need headers/url/etc. */
|
|
23
|
+
response;
|
|
24
|
+
constructor(message, init) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "KrovaError";
|
|
27
|
+
this.status = init.status;
|
|
28
|
+
this.code = init.code;
|
|
29
|
+
this.requestId = init.requestId;
|
|
30
|
+
this.body = init.body;
|
|
31
|
+
this.response = init.response;
|
|
32
|
+
Object.setPrototypeOf(this, _KrovaError.prototype);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
function krovaErrorFrom(response, body) {
|
|
36
|
+
const message = typeof body?.error === "string" && body.error || response.statusText || `Request failed with status ${response.status}`;
|
|
37
|
+
return new KrovaError(message, {
|
|
38
|
+
status: response.status,
|
|
39
|
+
code: response.headers.get("x-error-code") ?? void 0,
|
|
40
|
+
requestId: response.headers.get("x-request-id") ?? void 0,
|
|
41
|
+
body,
|
|
42
|
+
response
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/client.ts
|
|
47
|
+
var DEFAULT_BASE_URL = "https://krova.cloud/api/v1";
|
|
48
|
+
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 503]);
|
|
49
|
+
var BASE_BACKOFF_MS = 500;
|
|
50
|
+
var MAX_BACKOFF_MS = 1e4;
|
|
51
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
52
|
+
function parseRetryAfterMs(headerValue) {
|
|
53
|
+
if (!headerValue) return null;
|
|
54
|
+
const seconds = Number(headerValue);
|
|
55
|
+
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
|
|
56
|
+
const dateMs = Date.parse(headerValue);
|
|
57
|
+
if (Number.isFinite(dateMs)) return Math.max(0, dateMs - Date.now());
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
function authMiddleware(apiKey, scheme) {
|
|
61
|
+
return {
|
|
62
|
+
onRequest({ request }) {
|
|
63
|
+
if (scheme === "bearer") {
|
|
64
|
+
request.headers.set("Authorization", `Bearer ${apiKey}`);
|
|
65
|
+
} else {
|
|
66
|
+
request.headers.set("X-API-KEY", apiKey);
|
|
67
|
+
}
|
|
68
|
+
return request;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function retryMiddleware(maxRetries, doFetch) {
|
|
73
|
+
return {
|
|
74
|
+
async onResponse({ request, response }) {
|
|
75
|
+
if (maxRetries <= 0 || !RETRYABLE_STATUSES.has(response.status)) {
|
|
76
|
+
return response;
|
|
77
|
+
}
|
|
78
|
+
let current = response;
|
|
79
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
80
|
+
if (!RETRYABLE_STATUSES.has(current.status)) break;
|
|
81
|
+
const retryAfterMs = parseRetryAfterMs(current.headers.get("retry-after"));
|
|
82
|
+
const backoff = Math.min(BASE_BACKOFF_MS * 2 ** (attempt - 1), MAX_BACKOFF_MS);
|
|
83
|
+
await sleep(retryAfterMs ?? backoff);
|
|
84
|
+
current = await doFetch(request.clone());
|
|
85
|
+
}
|
|
86
|
+
return current;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
var KrovaClient = class {
|
|
91
|
+
/**
|
|
92
|
+
* The underlying openapi-fetch client — a fully typed escape hatch to every
|
|
93
|
+
* path in the spec. Returns `{ data, error, response }` and never throws.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* const { data, error } = await krova.raw.GET(
|
|
98
|
+
* "/spaces/{spaceId}/cubes/{cubeId}",
|
|
99
|
+
* { params: { path: { spaceId, cubeId } } },
|
|
100
|
+
* );
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
raw;
|
|
104
|
+
/** The resolved base URL in use. */
|
|
105
|
+
baseUrl;
|
|
106
|
+
constructor(options) {
|
|
107
|
+
if (!options?.apiKey) {
|
|
108
|
+
throw new Error("KrovaClient: `apiKey` is required.");
|
|
109
|
+
}
|
|
110
|
+
this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
|
|
111
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
112
|
+
const maxRetries = options.maxRetries ?? 2;
|
|
113
|
+
this.raw = createClient({
|
|
114
|
+
baseUrl: this.baseUrl,
|
|
115
|
+
...options.fetch ? { fetch: options.fetch } : {}
|
|
116
|
+
});
|
|
117
|
+
this.raw.use(authMiddleware(options.apiKey, options.authScheme ?? "x-api-key"));
|
|
118
|
+
if (maxRetries > 0) {
|
|
119
|
+
this.raw.use(retryMiddleware(maxRetries, doFetch));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// Cubes
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
cubes = {
|
|
126
|
+
/** List Cubes in a Space. Returns the raw (paginated) response body. */
|
|
127
|
+
list: async (spaceId) => {
|
|
128
|
+
const { data, error, response } = await this.raw.GET("/spaces/{spaceId}/cubes", {
|
|
129
|
+
params: { path: { spaceId } }
|
|
130
|
+
});
|
|
131
|
+
if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
|
|
132
|
+
return data;
|
|
133
|
+
},
|
|
134
|
+
/**
|
|
135
|
+
* Create a Cube. Returns the created {@link Cube}.
|
|
136
|
+
*
|
|
137
|
+
* @param spaceId Target Space id.
|
|
138
|
+
* @param body Cube spec — `{ name, image, resources, sshPublicKey, ... }`.
|
|
139
|
+
* @param opts Optional `idempotencyKey` (max 255 chars, scoped per space).
|
|
140
|
+
*/
|
|
141
|
+
create: async (spaceId, body, opts) => {
|
|
142
|
+
const { data, error, response } = await this.raw.POST("/spaces/{spaceId}/cubes", {
|
|
143
|
+
params: {
|
|
144
|
+
path: { spaceId },
|
|
145
|
+
...opts?.idempotencyKey ? { header: { "Idempotency-Key": opts.idempotencyKey } } : {}
|
|
146
|
+
},
|
|
147
|
+
body
|
|
148
|
+
});
|
|
149
|
+
if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
|
|
150
|
+
const cube = data?.cube;
|
|
151
|
+
if (!cube) {
|
|
152
|
+
throw krovaErrorFrom(response, { error: "Create Cube response had no `cube`." });
|
|
153
|
+
}
|
|
154
|
+
return cube;
|
|
155
|
+
},
|
|
156
|
+
/** Get a single Cube. Returns the raw response body. */
|
|
157
|
+
get: async (spaceId, cubeId) => {
|
|
158
|
+
const { data, error, response } = await this.raw.GET(
|
|
159
|
+
"/spaces/{spaceId}/cubes/{cubeId}",
|
|
160
|
+
{ params: { path: { spaceId, cubeId } } }
|
|
161
|
+
);
|
|
162
|
+
if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
|
|
163
|
+
return data;
|
|
164
|
+
},
|
|
165
|
+
/**
|
|
166
|
+
* Update a Cube's SSH port.
|
|
167
|
+
*
|
|
168
|
+
* The Krova Cloud API exposes no general Cube-mutation endpoint; the only
|
|
169
|
+
* mutable Cube field over the API is its SSH port, via
|
|
170
|
+
* `PUT /spaces/{spaceId}/cubes/{cubeId}/ssh-port`. This helper maps to that
|
|
171
|
+
* endpoint. (Compute resize / rename are not part of the public API.)
|
|
172
|
+
*/
|
|
173
|
+
update: async (spaceId, cubeId, body) => {
|
|
174
|
+
const { data, error, response } = await this.raw.PUT(
|
|
175
|
+
"/spaces/{spaceId}/cubes/{cubeId}/ssh-port",
|
|
176
|
+
{ params: { path: { spaceId, cubeId } }, body }
|
|
177
|
+
);
|
|
178
|
+
if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
|
|
179
|
+
return data;
|
|
180
|
+
},
|
|
181
|
+
/** Delete a Cube (asynchronous — deletion is enqueued). */
|
|
182
|
+
delete: async (spaceId, cubeId) => {
|
|
183
|
+
const { data, error, response } = await this.raw.DELETE(
|
|
184
|
+
"/spaces/{spaceId}/cubes/{cubeId}",
|
|
185
|
+
{ params: { path: { spaceId, cubeId } } }
|
|
186
|
+
);
|
|
187
|
+
if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
|
|
188
|
+
return data;
|
|
189
|
+
},
|
|
190
|
+
/** Sleep a running Cube (asynchronous — sleep is enqueued). */
|
|
191
|
+
sleep: async (spaceId, cubeId) => {
|
|
192
|
+
const { data, error, response } = await this.raw.POST(
|
|
193
|
+
"/spaces/{spaceId}/cubes/{cubeId}/sleep",
|
|
194
|
+
{ params: { path: { spaceId, cubeId } } }
|
|
195
|
+
);
|
|
196
|
+
if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
|
|
197
|
+
return data;
|
|
198
|
+
},
|
|
199
|
+
/** Wake a sleeping Cube (asynchronous — wake is enqueued). */
|
|
200
|
+
wake: async (spaceId, cubeId) => {
|
|
201
|
+
const { data, error, response } = await this.raw.POST(
|
|
202
|
+
"/spaces/{spaceId}/cubes/{cubeId}/wake",
|
|
203
|
+
{ params: { path: { spaceId, cubeId } } }
|
|
204
|
+
);
|
|
205
|
+
if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
|
|
206
|
+
return data;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// Public catalog (no auth required by the API, but the key is harmless)
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
catalog = {
|
|
213
|
+
/** List regions with available capacity. */
|
|
214
|
+
regions: async () => {
|
|
215
|
+
const { data, error, response } = await this.raw.GET("/regions");
|
|
216
|
+
if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
|
|
217
|
+
return data;
|
|
218
|
+
},
|
|
219
|
+
/** List available OS images. */
|
|
220
|
+
images: async () => {
|
|
221
|
+
const { data, error, response } = await this.raw.GET("/images");
|
|
222
|
+
if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
|
|
223
|
+
return data;
|
|
224
|
+
},
|
|
225
|
+
/** Per-resource hourly rates and volume pricing tiers. */
|
|
226
|
+
pricing: async () => {
|
|
227
|
+
const { data, error, response } = await this.raw.GET("/pricing");
|
|
228
|
+
if (error !== void 0 || !response.ok) throw krovaErrorFrom(response, error);
|
|
229
|
+
return data;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export { DEFAULT_BASE_URL, KrovaClient, KrovaError, krovaErrorFrom };
|
|
235
|
+
//# sourceMappingURL=index.js.map
|
|
236
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/error.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.js","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"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@krovacloud/sdk",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Official TypeScript SDK for the Krova Cloud API — a typed client generated from the Krova Cloud OpenAPI spec.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"author": "Krova Inc.",
|
|
8
|
+
"homepage": "https://github.com/krovacloud/krova-js#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/krovacloud/krova-js.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/krovacloud/krova-js/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"krova",
|
|
18
|
+
"krova-cloud",
|
|
19
|
+
"firecracker",
|
|
20
|
+
"cube",
|
|
21
|
+
"microvm",
|
|
22
|
+
"cloud",
|
|
23
|
+
"sdk",
|
|
24
|
+
"typescript"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"main": "./dist/index.cjs",
|
|
30
|
+
"module": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"import": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"require": {
|
|
39
|
+
"types": "./dist/index.d.cts",
|
|
40
|
+
"default": "./dist/index.cjs"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"README.md",
|
|
47
|
+
"LICENSE",
|
|
48
|
+
"CHANGELOG.md"
|
|
49
|
+
],
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"openapi-fetch": "^0.17.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^22.20.0",
|
|
58
|
+
"openapi-typescript": "^7.13.0",
|
|
59
|
+
"tsup": "^8.5.1",
|
|
60
|
+
"tsx": "^4.22.4",
|
|
61
|
+
"typescript": "^5.9.3"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"gen": "openapi-typescript openapi.json -o src/generated/types.ts",
|
|
65
|
+
"build": "tsup",
|
|
66
|
+
"typecheck": "tsc --noEmit",
|
|
67
|
+
"test": "tsx --test tests/*.test.ts"
|
|
68
|
+
}
|
|
69
|
+
}
|