@aerol-ai/aerolvm-sdk 0.7.0 → 0.8.0
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/errors.d.ts +28 -0
- package/dist/errors.js +31 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/internal/client.d.ts +7 -0
- package/dist/internal/client.js +29 -8
- package/dist/types.d.ts +13 -0
- package/package.json +1 -1
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An HTTP error response from the AerolVM server. `message` is the server's
|
|
3
|
+
* message, or `request failed with status N` when the body carries none (a
|
|
4
|
+
* proxy's HTML error page, say): the same text the SDK threw before this
|
|
5
|
+
* class existed, so code that printed or matched it sees no change.
|
|
6
|
+
*
|
|
7
|
+
* Branch on `code`, not on `message`: the code is a stable identifier, the
|
|
8
|
+
* message is wording for a person.
|
|
9
|
+
*/
|
|
10
|
+
export declare class MicroVMApiError extends Error {
|
|
11
|
+
/** The HTTP status of the final attempt, such as 422 or 503. */
|
|
12
|
+
readonly status: number;
|
|
13
|
+
/**
|
|
14
|
+
* The server's stable error code, such as `"idempotency_key_reused"` or
|
|
15
|
+
* `"overloaded"`. Undefined when the response has none: many errors are
|
|
16
|
+
* only ever shown to a person, and a proxy's error page has no code.
|
|
17
|
+
*/
|
|
18
|
+
readonly code: string | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* How long the response's `Retry-After` header asked the caller to wait,
|
|
21
|
+
* in milliseconds. Undefined without a usable header.
|
|
22
|
+
*/
|
|
23
|
+
readonly retryAfterMs: number | undefined;
|
|
24
|
+
constructor(status: number, message: string, options?: {
|
|
25
|
+
code?: string;
|
|
26
|
+
retryAfterMs?: number;
|
|
27
|
+
});
|
|
28
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An HTTP error response from the AerolVM server. `message` is the server's
|
|
3
|
+
* message, or `request failed with status N` when the body carries none (a
|
|
4
|
+
* proxy's HTML error page, say): the same text the SDK threw before this
|
|
5
|
+
* class existed, so code that printed or matched it sees no change.
|
|
6
|
+
*
|
|
7
|
+
* Branch on `code`, not on `message`: the code is a stable identifier, the
|
|
8
|
+
* message is wording for a person.
|
|
9
|
+
*/
|
|
10
|
+
export class MicroVMApiError extends Error {
|
|
11
|
+
/** The HTTP status of the final attempt, such as 422 or 503. */
|
|
12
|
+
status;
|
|
13
|
+
/**
|
|
14
|
+
* The server's stable error code, such as `"idempotency_key_reused"` or
|
|
15
|
+
* `"overloaded"`. Undefined when the response has none: many errors are
|
|
16
|
+
* only ever shown to a person, and a proxy's error page has no code.
|
|
17
|
+
*/
|
|
18
|
+
code;
|
|
19
|
+
/**
|
|
20
|
+
* How long the response's `Retry-After` header asked the caller to wait,
|
|
21
|
+
* in milliseconds. Undefined without a usable header.
|
|
22
|
+
*/
|
|
23
|
+
retryAfterMs;
|
|
24
|
+
constructor(status, message, options = {}) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "MicroVMApiError";
|
|
27
|
+
this.status = status;
|
|
28
|
+
this.code = options.code;
|
|
29
|
+
this.retryAfterMs = options.retryAfterMs;
|
|
30
|
+
}
|
|
31
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { MicroVM } from "./MicroVM.js";
|
|
2
2
|
export { Sandbox } from "./Sandbox.js";
|
|
3
3
|
export { Image } from "./Image.js";
|
|
4
|
+
export { MicroVMApiError } from "./errors.js";
|
|
4
5
|
export type { MicroVMConfig, Tokens } from "./MicroVM.js";
|
|
5
6
|
export type { RetryConfig } from "./internal/client.js";
|
|
6
7
|
export type { AddCustomDomainOptions, APIToken, APITokenList, CreateAPITokenOptions, CreatedAPIToken, WhoAmI, WhoAmIPrincipal, AuditEvent, AuditOptions, AuditPage, EgressProfile, EgressProfileList, EgressProfileOptions, EgressProfileRef, EgressInject, EgressRule, ListEgressProfilesOptions, NetworkLearned, NetworkLearnedEntry, NetworkPolicy, NetworkPolicyCheckOptions, NetworkPolicyCheckResult, NetworkPolicyOptions, BinaryLike, BuildImageOptions, BuildImagePushOptions, BuildImageResult, CloneGeneration, CreateOptions, CreateSessionOptions, CreateTemplateOptions, CreateWasmModuleOptions, CustomDomain, CustomDomainDNSRecords, CustomDomainStatus, DNSRecord, DNSRecordType, ExecRequest, ExecResult, ExecExitInfo, ExposedPort, ExposePortOptions, ExposeProtocol, ExposeResult, Failover, FailoverPolicy, GetOptions, HealthStatus, IngressTarget, IngressTargetSource, Lifecycle, ListOptions, MountSpec, MountSpecRedacted, MountType, PlatformVolumeMount, RegisterSnapshotOptions, ListSnapshotsOptions, SnapshotList, RegistryAuth, ResizeOptions, Sandbox as SandboxData, SandboxSnapshot, SandboxStatus, Session, SessionAttachHandle, SessionAttachOptions, SessionStatus, Template, TemplatePushState, TemplateStatus, WasmModule, WasmModuleStatus, PushWasmModuleOptions, PushWasmModuleResult, UpdateLifecycleOptions, } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -81,6 +81,13 @@ export interface APIClientConfig {
|
|
|
81
81
|
*/
|
|
82
82
|
signal?: AbortSignal;
|
|
83
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* The header a sandbox create carries so the server can recognise a retry of
|
|
86
|
+
* it. Keep in sync with clustercreate.HeaderIdempotencyKey on the server.
|
|
87
|
+
*/
|
|
88
|
+
export declare const IDEMPOTENCY_KEY_HEADER = "Idempotency-Key";
|
|
89
|
+
/** A fresh create key: 128 random bits as 32 lowercase hex characters. */
|
|
90
|
+
export declare function newIdempotencyKey(): string;
|
|
84
91
|
/**
|
|
85
92
|
* Reads a `Retry-After` value in either form RFC 9110 allows: delta-seconds
|
|
86
93
|
* (`"120"`) or an HTTP date. Returns the wait in milliseconds (0 for a date
|
package/dist/internal/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { basename } from "node:path";
|
|
2
2
|
import { PATH_PREFIX as V1_PATH_PREFIX, ingressDNSPath as v1IngressDNSPath, sandboxCustomDomainDNSPath as v1SandboxCustomDomainDNSPath, sandboxCustomDomainPath as v1SandboxCustomDomainPath, sandboxCustomDomainsPath as v1SandboxCustomDomainsPath, } from "./api/v1/paths.js";
|
|
3
|
+
import { MicroVMApiError } from "../errors.js";
|
|
3
4
|
import { Image } from "../Image.js";
|
|
4
5
|
/**
|
|
5
6
|
* The request header that selects which of a credential's teams a request
|
|
@@ -67,6 +68,19 @@ const RETRYABLE_STATUS_CODES = new Set([421, 429, 502, 503, 504]);
|
|
|
67
68
|
// over its share, 503 when the node sheds load. A Retry-After on a 421, 502 or
|
|
68
69
|
// 504 is a hop's, not the server's, so those use the normal backoff.
|
|
69
70
|
const REFUSED_STATUS_CODES = new Set([429, 503]);
|
|
71
|
+
/**
|
|
72
|
+
* The header a sandbox create carries so the server can recognise a retry of
|
|
73
|
+
* it. Keep in sync with clustercreate.HeaderIdempotencyKey on the server.
|
|
74
|
+
*/
|
|
75
|
+
export const IDEMPOTENCY_KEY_HEADER = "Idempotency-Key";
|
|
76
|
+
/** A fresh create key: 128 random bits as 32 lowercase hex characters. */
|
|
77
|
+
export function newIdempotencyKey() {
|
|
78
|
+
// getRandomValues, unlike randomUUID, also works on a page served over
|
|
79
|
+
// plain http.
|
|
80
|
+
const bytes = new Uint8Array(16);
|
|
81
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
82
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
83
|
+
}
|
|
70
84
|
/** Default retry settings when the caller doesn't supply a RetryConfig. */
|
|
71
85
|
const DEFAULT_RETRY = {
|
|
72
86
|
maxRetries: 3,
|
|
@@ -262,7 +276,13 @@ export class APIClient {
|
|
|
262
276
|
}
|
|
263
277
|
async create(options) {
|
|
264
278
|
const resolved = await this.resolveImage(options);
|
|
265
|
-
|
|
279
|
+
// One key per call, sent unchanged on each of its automatic retries: a
|
|
280
|
+
// create retried after a timeout gets back the sandbox the first attempt
|
|
281
|
+
// made instead of making a second one.
|
|
282
|
+
const key = options.idempotencyKey?.trim() || newIdempotencyKey();
|
|
283
|
+
const response = await this.doJSON("POST", this.versioned("/sandboxes"), toApiCreateOptions(resolved), {
|
|
284
|
+
headers: { [IDEMPOTENCY_KEY_HEADER]: key },
|
|
285
|
+
});
|
|
266
286
|
return new SandboxResource(this, fromApiCreateSandboxResponse(response));
|
|
267
287
|
}
|
|
268
288
|
/**
|
|
@@ -764,12 +784,10 @@ export class APIClient {
|
|
|
764
784
|
return new SandboxResource(this, fromApiSandbox(sandbox));
|
|
765
785
|
}
|
|
766
786
|
async doJSON(method, path, body, options) {
|
|
767
|
-
const init = {};
|
|
787
|
+
const init = { headers: { ...options?.headers } };
|
|
768
788
|
if (body !== undefined) {
|
|
769
789
|
init.body = JSON.stringify(body);
|
|
770
|
-
init.headers = {
|
|
771
|
-
"Content-Type": "application/json",
|
|
772
|
-
};
|
|
790
|
+
init.headers = { ...init.headers, "Content-Type": "application/json" };
|
|
773
791
|
}
|
|
774
792
|
const response = await this.request(method, path, init, options);
|
|
775
793
|
if (!response.ok) {
|
|
@@ -1432,16 +1450,19 @@ function toBlob(data) {
|
|
|
1432
1450
|
return new Blob([Uint8Array.from(data)]);
|
|
1433
1451
|
}
|
|
1434
1452
|
async function decodeError(response) {
|
|
1453
|
+
const retryAfterMs = parseRetryAfter(response.headers.get("retry-after"), Date.now());
|
|
1435
1454
|
try {
|
|
1436
1455
|
const payload = (await response.json());
|
|
1437
1456
|
if (typeof payload.error === "string" && payload.error !== "") {
|
|
1438
|
-
|
|
1457
|
+
const code = typeof payload.code === "string" && payload.code !== "" ? payload.code : undefined;
|
|
1458
|
+
return new MicroVMApiError(response.status, payload.error, { code, retryAfterMs });
|
|
1439
1459
|
}
|
|
1440
1460
|
}
|
|
1441
1461
|
catch {
|
|
1442
|
-
//
|
|
1462
|
+
// Not JSON (a proxy's error page) or no body: fall through to the
|
|
1463
|
+
// status-based message, with no code.
|
|
1443
1464
|
}
|
|
1444
|
-
return new
|
|
1465
|
+
return new MicroVMApiError(response.status, `request failed with status ${response.status}`, { retryAfterMs });
|
|
1445
1466
|
}
|
|
1446
1467
|
const STREAM_PREFIX_STDOUT = 0x01;
|
|
1447
1468
|
const STREAM_PREFIX_STDERR = 0x02;
|
package/dist/types.d.ts
CHANGED
|
@@ -135,6 +135,19 @@ export interface GPUOptions {
|
|
|
135
135
|
}
|
|
136
136
|
import type { Image } from "./Image.js";
|
|
137
137
|
export interface CreateOptions {
|
|
138
|
+
/**
|
|
139
|
+
* Sent as the create's `Idempotency-Key` header, never in the body. A create
|
|
140
|
+
* sent again with the same key and the same options returns the sandbox the
|
|
141
|
+
* first one made instead of making a second; the same key with different
|
|
142
|
+
* options fails with a 422 `MicroVMApiError` whose `code` is
|
|
143
|
+
* `"idempotency_key_reused"`. Keys are scoped to the caller's account.
|
|
144
|
+
*
|
|
145
|
+
* Leave it unset and the SDK makes a random key for each `create` call and
|
|
146
|
+
* reuses it on that call's automatic retries, so a retry after a timeout
|
|
147
|
+
* cannot make a second sandbox. Set your own to make your own retries of
|
|
148
|
+
* the whole call safe too, such as after your process restarts.
|
|
149
|
+
*/
|
|
150
|
+
idempotencyKey?: string;
|
|
138
151
|
/**
|
|
139
152
|
* Optional name, unique among the caller's sandboxes (another account may
|
|
140
153
|
* use the same name). A repeat create with a name the caller already holds
|