@aerol-ai/aerolvm-sdk 0.3.2 → 0.5.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/MicroVM.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { type APIVersion } from "./internal/client.js";
1
+ import { type APIVersion, type RetryConfig } from "./internal/client.js";
2
2
  import { Sandbox } from "./Sandbox.js";
3
3
  import { Image } from "./Image.js";
4
- import type { BuildImageOptions, BuildImageResult, CreateOptions, CreateSessionOptions, ExecStreamHandle, ExecStreamOptions, HealthStatus, IngressTarget, Lifecycle, ListOptions, MountSpecRedacted, RegisterSnapshotOptions, ResizeOptions, SandboxSnapshot, Session, SessionAttachHandle, SessionAttachOptions } from "./types.js";
4
+ import type { BuildImageOptions, BuildImageResult, CreateOptions, CreateSessionOptions, CreateTemplateOptions, ExecStreamHandle, ExecStreamOptions, HealthStatus, IngressTarget, Lifecycle, ListOptions, MountSpecRedacted, RegisterSnapshotOptions, ResizeOptions, SandboxSnapshot, Session, SessionAttachHandle, SessionAttachOptions, Template } from "./types.js";
5
5
  type FetchLike = typeof fetch;
6
6
  export interface MicroVMConfig {
7
7
  patToken?: string;
@@ -14,6 +14,13 @@ export interface MicroVMConfig {
14
14
  * other.
15
15
  */
16
16
  apiVersion?: APIVersion;
17
+ /**
18
+ * Retry policy for transient transport errors (socket closed, connection
19
+ * reset) and retryable HTTP status codes (429, 502, 503, 504). The SDK
20
+ * retries up to 3 times with exponential backoff by default. Pass
21
+ * `{ maxRetries: 0 }` to disable.
22
+ */
23
+ retry?: RetryConfig;
17
24
  }
18
25
  export declare class MicroVM {
19
26
  readonly apiUrl: string;
@@ -51,6 +58,32 @@ export declare class MicroVM {
51
58
  sessionLog(sandboxID: string, sessionID: string): Promise<Uint8Array>;
52
59
  sessionRecording(sandboxID: string, sessionID: string): Promise<Uint8Array>;
53
60
  attachSession(sandboxID: string, sessionID: string, options?: SessionAttachOptions): SessionAttachHandle;
61
+ /**
62
+ * Register a Firecracker rootfs template. Returns immediately with a
63
+ * `status: "pending"` row; poll {@link MicroVM.getTemplate} until the
64
+ * status reaches `"ready"` (fast-boot available) or `"ready_no_snapshot"`
65
+ * (cold boot only — see {@link Template.snapshotError}).
66
+ *
67
+ * Idempotent when {@link CreateTemplateOptions.id} is supplied: a
68
+ * duplicate ID returns 409 so a retried CI step does not create two
69
+ * rows for the same logical template.
70
+ */
71
+ createTemplate(options: CreateTemplateOptions): Promise<Template>;
72
+ listTemplates(): Promise<Template[]>;
73
+ getTemplate(id: string): Promise<Template>;
74
+ deleteTemplate(id: string): Promise<void>;
75
+ /**
76
+ * Re-run the snapshot phase against an existing template. Idempotent
77
+ * under concurrent retry: the daemon's CAS collapses N parallel calls
78
+ * for the same ready template into one rebuild kick. Returns the row in
79
+ * its post-transition state (typically `unhealthy`) — poll
80
+ * {@link MicroVM.getTemplate} to observe the transition back to `ready`.
81
+ *
82
+ * Returns 412 (raised as an error) when the template is in a state
83
+ * where rebuild is not safe (build in flight) or not supported
84
+ * (`ready_no_snapshot` / `failed` — those need delete+recreate today).
85
+ */
86
+ rebuildTemplate(id: string): Promise<Template>;
54
87
  /**
55
88
  * Cluster-level DNS helpers. Exposed as a namespaced accessor (rather
56
89
  * than a flat `ingressDNS()` method) so call sites read like
package/dist/MicroVM.js CHANGED
@@ -19,6 +19,7 @@ export class MicroVM {
19
19
  patToken,
20
20
  fetch: config.fetch,
21
21
  apiVersion: config.apiVersion,
22
+ retry: config.retry,
22
23
  });
23
24
  }
24
25
  async create(options) {
@@ -109,6 +110,42 @@ export class MicroVM {
109
110
  attachSession(sandboxID, sessionID, options = {}) {
110
111
  return this.client.attachSession(sandboxID, sessionID, options);
111
112
  }
113
+ /**
114
+ * Register a Firecracker rootfs template. Returns immediately with a
115
+ * `status: "pending"` row; poll {@link MicroVM.getTemplate} until the
116
+ * status reaches `"ready"` (fast-boot available) or `"ready_no_snapshot"`
117
+ * (cold boot only — see {@link Template.snapshotError}).
118
+ *
119
+ * Idempotent when {@link CreateTemplateOptions.id} is supplied: a
120
+ * duplicate ID returns 409 so a retried CI step does not create two
121
+ * rows for the same logical template.
122
+ */
123
+ async createTemplate(options) {
124
+ return this.client.createTemplate(options);
125
+ }
126
+ async listTemplates() {
127
+ return this.client.listTemplates();
128
+ }
129
+ async getTemplate(id) {
130
+ return this.client.getTemplate(id);
131
+ }
132
+ async deleteTemplate(id) {
133
+ await this.client.deleteTemplate(id);
134
+ }
135
+ /**
136
+ * Re-run the snapshot phase against an existing template. Idempotent
137
+ * under concurrent retry: the daemon's CAS collapses N parallel calls
138
+ * for the same ready template into one rebuild kick. Returns the row in
139
+ * its post-transition state (typically `unhealthy`) — poll
140
+ * {@link MicroVM.getTemplate} to observe the transition back to `ready`.
141
+ *
142
+ * Returns 412 (raised as an error) when the template is in a state
143
+ * where rebuild is not safe (build in flight) or not supported
144
+ * (`ready_no_snapshot` / `failed` — those need delete+recreate today).
145
+ */
146
+ async rebuildTemplate(id) {
147
+ return this.client.rebuildTemplate(id);
148
+ }
112
149
  /**
113
150
  * Cluster-level DNS helpers. Exposed as a namespaced accessor (rather
114
151
  * than a flat `ingressDNS()` method) so call sites read like
package/dist/index.d.ts CHANGED
@@ -2,4 +2,5 @@ export { MicroVM } from "./MicroVM.js";
2
2
  export { Sandbox } from "./Sandbox.js";
3
3
  export { Image } from "./Image.js";
4
4
  export type { MicroVMConfig } from "./MicroVM.js";
5
- export type { BinaryLike, BuildImageOptions, BuildImagePushOptions, BuildImageResult, CreateOptions, CreateSessionOptions, CustomDomain, CustomDomainDNSRecords, CustomDomainStatus, DNSRecord, DNSRecordType, ExecRequest, ExecResult, ExecExitInfo, ExposedPort, ExposePortOptions, ExposeProtocol, ExposeResult, Failover, FailoverPolicy, HealthStatus, IngressTarget, IngressTargetSource, Lifecycle, MountSpec, MountSpecRedacted, MountType, RegisterSnapshotOptions, RegistryAuth, ResizeOptions, Sandbox as SandboxData, SandboxSnapshot, SandboxStatus, Session, SessionAttachHandle, SessionAttachOptions, SessionStatus, UpdateLifecycleOptions, } from "./types.js";
5
+ export type { RetryConfig } from "./internal/client.js";
6
+ export type { AddCustomDomainOptions, BinaryLike, BuildImageOptions, BuildImagePushOptions, BuildImageResult, CreateOptions, CreateSessionOptions, CreateTemplateOptions, CustomDomain, CustomDomainDNSRecords, CustomDomainStatus, DNSRecord, DNSRecordType, ExecRequest, ExecResult, ExecExitInfo, ExposedPort, ExposePortOptions, ExposeProtocol, ExposeResult, Failover, FailoverPolicy, HealthStatus, IngressTarget, IngressTargetSource, Lifecycle, MountSpec, MountSpecRedacted, MountType, RegisterSnapshotOptions, RegistryAuth, ResizeOptions, Sandbox as SandboxData, SandboxSnapshot, SandboxStatus, Session, SessionAttachHandle, SessionAttachOptions, SessionStatus, Template, TemplatePushState, TemplateStatus, UpdateLifecycleOptions, } from "./types.js";
@@ -1,5 +1,5 @@
1
1
  import { Image } from "../Image.js";
2
- import type { BinaryLike, BuildImageOptions, BuildImageResult, CreateOptions, CreateSessionOptions, CustomDomain, CustomDomainDNSRecords, ExecRequest, ExecResult, ExecStreamHandle, ExecStreamOptions, ExposedPort, ExposePortOptions, ExposeResult, Failover, HealthStatus, IngressTarget, Lifecycle, ListOptions, MountSpecRedacted, NetworkUsage, RegisterSnapshotOptions, SetNetworkLimitsOptions, ResizeOptions, Sandbox, SandboxSnapshot, Session, SessionAttachHandle, SessionAttachOptions } from "../types.js";
2
+ import type { AddCustomDomainOptions, BinaryLike, BuildImageOptions, BuildImageResult, CreateOptions, CreateSessionOptions, CreateTemplateOptions, CustomDomain, CustomDomainDNSRecords, ExecRequest, ExecResult, ExecStreamHandle, ExecStreamOptions, ExposedPort, ExposePortOptions, ExposeResult, Failover, HealthStatus, IngressTarget, Lifecycle, ListOptions, MountSpecRedacted, NetworkUsage, RegisterSnapshotOptions, SetNetworkLimitsOptions, ResizeOptions, Sandbox, SandboxSnapshot, Session, SessionAttachHandle, SessionAttachOptions, Template } from "../types.js";
3
3
  type FetchLike = typeof fetch;
4
4
  /**
5
5
  * Wire version of the v1-style sandbox endpoints (`/v1/sandboxes`, etc.) this
@@ -16,6 +16,27 @@ type FetchLike = typeof fetch;
16
16
  * string image.
17
17
  */
18
18
  export type APIVersion = "v1";
19
+ /**
20
+ * Retry configuration for transient transport and server errors.
21
+ * All fields are optional — the SDK ships sensible defaults.
22
+ */
23
+ export interface RetryConfig {
24
+ /**
25
+ * Maximum number of retry attempts after the initial request fails.
26
+ * Set to `0` to disable retry entirely. Defaults to `3`.
27
+ */
28
+ maxRetries?: number;
29
+ /**
30
+ * Initial backoff delay in milliseconds. Subsequent retries double this
31
+ * value (exponential backoff). Defaults to `200`.
32
+ */
33
+ baseDelayMs?: number;
34
+ /**
35
+ * Hard ceiling on the computed delay in milliseconds. Prevents unbounded
36
+ * waits when `maxRetries` is high. Defaults to `5000`.
37
+ */
38
+ maxDelayMs?: number;
39
+ }
19
40
  export interface APIClientConfig {
20
41
  baseURL: string;
21
42
  patToken?: string;
@@ -26,6 +47,12 @@ export interface APIClientConfig {
26
47
  * a future major SDK release.
27
48
  */
28
49
  apiVersion?: APIVersion;
50
+ /**
51
+ * Retry policy for transient transport errors (socket closed, connection
52
+ * reset) and retryable HTTP status codes (429, 502, 503, 504). Pass
53
+ * `{ maxRetries: 0 }` to disable retry entirely.
54
+ */
55
+ retry?: RetryConfig;
29
56
  }
30
57
  export declare class APIClient {
31
58
  readonly baseURL: string;
@@ -33,6 +60,7 @@ export declare class APIClient {
33
60
  private readonly patToken;
34
61
  private readonly fetchFn;
35
62
  private readonly versionPrefix;
63
+ private readonly retryConfig;
36
64
  constructor(config: APIClientConfig);
37
65
  /**
38
66
  * Build a versioned API path. Pass the suffix beginning with "/" (e.g.
@@ -99,7 +127,7 @@ export declare class APIClient {
99
127
  * with an already-registered hostname is idempotent and returns the
100
128
  * existing list.
101
129
  */
102
- addCustomDomain(id: string, hostname: string): Promise<CustomDomain[]>;
130
+ addCustomDomain(id: string, hostname: string, options?: AddCustomDomainOptions): Promise<CustomDomain[]>;
103
131
  listCustomDomains(id: string): Promise<CustomDomain[]>;
104
132
  removeCustomDomain(id: string, hostname: string): Promise<void>;
105
133
  /**
@@ -118,9 +146,25 @@ export declare class APIClient {
118
146
  mounts(id: string): Promise<MountSpecRedacted[]>;
119
147
  getNetworkUsage(id: string): Promise<NetworkUsage>;
120
148
  setNetworkLimits(id: string, options: SetNetworkLimitsOptions): Promise<NetworkUsage>;
149
+ createTemplate(options: CreateTemplateOptions): Promise<Template>;
150
+ listTemplates(): Promise<Template[]>;
151
+ getTemplate(id: string): Promise<Template>;
152
+ deleteTemplate(id: string): Promise<void>;
153
+ rebuildTemplate(id: string): Promise<Template>;
121
154
  private wrap;
122
155
  private doJSON;
123
156
  private doBytes;
157
+ /**
158
+ * Low-level HTTP request with automatic retry for transient failures.
159
+ *
160
+ * Transport-level errors (socket closed, connection reset, DNS blip) are
161
+ * always retried regardless of HTTP method — the request never reached the
162
+ * server so there is no idempotency concern.
163
+ *
164
+ * HTTP-level retryable codes (429, 502, 503, 504) are retried for ALL
165
+ * methods because every mutating endpoint in the daemon is already
166
+ * designed for idempotent retry (e.g. INSERT OR IGNORE + disambiguation).
167
+ */
124
168
  private request;
125
169
  }
126
170
  export declare class SandboxResource implements Sandbox {
@@ -175,7 +219,7 @@ export declare class SandboxResource implements Sandbox {
175
219
  * so the closures always see the current `id` even after `refresh()`.
176
220
  */
177
221
  get customDomains(): {
178
- add(hostname: string): Promise<CustomDomain[]>;
222
+ add(hostname: string, options?: AddCustomDomainOptions): Promise<CustomDomain[]>;
179
223
  remove(hostname: string): Promise<void>;
180
224
  list(): Promise<CustomDomain[]>;
181
225
  dns(): Promise<CustomDomainDNSRecords>;
@@ -5,18 +5,71 @@ const DEFAULT_API_VERSION = "v1";
5
5
  const PATH_PREFIXES = {
6
6
  v1: V1_PATH_PREFIX,
7
7
  };
8
+ /** HTTP status codes the retry loop considers transient. */
9
+ const RETRYABLE_STATUS_CODES = new Set([429, 502, 503, 504]);
10
+ /** Default retry settings when the caller doesn't supply a RetryConfig. */
11
+ const DEFAULT_RETRY = {
12
+ maxRetries: 3,
13
+ baseDelayMs: 200,
14
+ maxDelayMs: 5_000,
15
+ };
16
+ /**
17
+ * Returns `true` when the thrown `error` looks like a transport-level failure
18
+ * that vanishes on retry (connection reset, socket closed by peer, DNS
19
+ * blip). `undici` and Node core use several `cause.code` values; we check
20
+ * the full cause chain.
21
+ */
22
+ function isTransientTransportError(error) {
23
+ const codes = new Set([
24
+ "UND_ERR_SOCKET",
25
+ "ECONNRESET",
26
+ "ECONNREFUSED",
27
+ "ETIMEDOUT",
28
+ "EPIPE",
29
+ "EAI_AGAIN",
30
+ "UND_ERR_CONNECT_TIMEOUT",
31
+ "UND_ERR_BODY_TIMEOUT",
32
+ "UND_ERR_HEADERS_TIMEOUT",
33
+ ]);
34
+ let current = error;
35
+ for (let depth = 0; depth < 5 && current != null; depth++) {
36
+ if (current instanceof Error) {
37
+ const code = current.code;
38
+ if (code && codes.has(code))
39
+ return true;
40
+ if (current.message && /socket hang up|other side closed/i.test(current.message))
41
+ return true;
42
+ current = current.cause;
43
+ }
44
+ else {
45
+ break;
46
+ }
47
+ }
48
+ return false;
49
+ }
50
+ /** Sleep with ±25% jitter so concurrent callers don't thundering-herd. */
51
+ function jitteredDelay(baseMs) {
52
+ const jitter = 1 + (Math.random() - 0.5) * 0.5; // 0.75 – 1.25
53
+ return new Promise((resolve) => setTimeout(resolve, Math.round(baseMs * jitter)));
54
+ }
8
55
  export class APIClient {
9
56
  baseURL;
10
57
  apiVersion;
11
58
  patToken;
12
59
  fetchFn;
13
60
  versionPrefix;
61
+ retryConfig;
14
62
  constructor(config) {
15
63
  this.baseURL = config.baseURL.replace(/\/+$/, "");
16
64
  this.patToken = config.patToken ?? "";
17
65
  this.fetchFn = config.fetch ?? fetch;
18
66
  this.apiVersion = config.apiVersion ?? DEFAULT_API_VERSION;
19
67
  this.versionPrefix = PATH_PREFIXES[this.apiVersion];
68
+ this.retryConfig = {
69
+ maxRetries: config.retry?.maxRetries ?? DEFAULT_RETRY.maxRetries,
70
+ baseDelayMs: config.retry?.baseDelayMs ?? DEFAULT_RETRY.baseDelayMs,
71
+ maxDelayMs: config.retry?.maxDelayMs ?? DEFAULT_RETRY.maxDelayMs,
72
+ };
20
73
  }
21
74
  /**
22
75
  * Build a versioned API path. Pass the suffix beginning with "/" (e.g.
@@ -226,8 +279,12 @@ export class APIClient {
226
279
  * with an already-registered hostname is idempotent and returns the
227
280
  * existing list.
228
281
  */
229
- async addCustomDomain(id, hostname) {
230
- const response = await this.doJSON("POST", v1SandboxCustomDomainsPath(this.versionPrefix, id), { hostname });
282
+ async addCustomDomain(id, hostname, options) {
283
+ const body = { hostname };
284
+ if (options?.port !== undefined && options.port !== 0) {
285
+ body.target_port = options.port;
286
+ }
287
+ const response = await this.doJSON("POST", v1SandboxCustomDomainsPath(this.versionPrefix, id), body);
231
288
  return response.custom_domains.map(fromApiCustomDomain);
232
289
  }
233
290
  async listCustomDomains(id) {
@@ -271,6 +328,31 @@ export class APIClient {
271
328
  const response = await this.doJSON("PATCH", `${this.versionPrefix}/sandboxes/${id}/network/limits`, toApiSetNetworkLimitsOptions(options));
272
329
  return fromApiNetworkUsage(response);
273
330
  }
331
+ async createTemplate(options) {
332
+ const response = await this.doJSON("POST", this.versioned("/templates"), {
333
+ id: options.id,
334
+ image: options.image,
335
+ min_size_mib: options.minSizeMiB,
336
+ });
337
+ return fromApiTemplate(response);
338
+ }
339
+ async listTemplates() {
340
+ const response = await this.doJSON("GET", this.versioned("/templates"));
341
+ // The daemon returns `null` rather than `[]` when the table is empty;
342
+ // normalize so callers always get an array.
343
+ return (response ?? []).map(fromApiTemplate);
344
+ }
345
+ async getTemplate(id) {
346
+ const response = await this.doJSON("GET", `${this.versionPrefix}/templates/${id}`);
347
+ return fromApiTemplate(response);
348
+ }
349
+ async deleteTemplate(id) {
350
+ await this.doJSON("DELETE", `${this.versionPrefix}/templates/${id}`);
351
+ }
352
+ async rebuildTemplate(id) {
353
+ const response = await this.doJSON("POST", `${this.versionPrefix}/templates/${id}/rebuild`);
354
+ return fromApiTemplate(response);
355
+ }
274
356
  wrap(sandbox) {
275
357
  return new SandboxResource(this, fromApiSandbox(sandbox));
276
358
  }
@@ -298,16 +380,49 @@ export class APIClient {
298
380
  }
299
381
  return new Uint8Array(await response.arrayBuffer());
300
382
  }
301
- request(method, path, init = {}) {
383
+ /**
384
+ * Low-level HTTP request with automatic retry for transient failures.
385
+ *
386
+ * Transport-level errors (socket closed, connection reset, DNS blip) are
387
+ * always retried regardless of HTTP method — the request never reached the
388
+ * server so there is no idempotency concern.
389
+ *
390
+ * HTTP-level retryable codes (429, 502, 503, 504) are retried for ALL
391
+ * methods because every mutating endpoint in the daemon is already
392
+ * designed for idempotent retry (e.g. INSERT OR IGNORE + disambiguation).
393
+ */
394
+ async request(method, path, init = {}) {
302
395
  const headers = new Headers(init.headers);
303
396
  if (this.patToken !== "") {
304
397
  headers.set("Authorization", `Bearer ${this.patToken}`);
305
398
  }
306
- return this.fetchFn(`${this.baseURL}${path}`, {
307
- ...init,
308
- method,
309
- headers,
310
- });
399
+ const url = `${this.baseURL}${path}`;
400
+ const requestInit = { ...init, method, headers };
401
+ const { maxRetries, baseDelayMs, maxDelayMs } = this.retryConfig;
402
+ let lastError;
403
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
404
+ try {
405
+ const response = await this.fetchFn(url, requestInit);
406
+ // Retry on transient HTTP status codes.
407
+ if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < maxRetries) {
408
+ const delay = Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
409
+ await jitteredDelay(delay);
410
+ continue;
411
+ }
412
+ return response;
413
+ }
414
+ catch (error) {
415
+ lastError = error;
416
+ // Only retry transport-level failures; anything else propagates.
417
+ if (!isTransientTransportError(error) || attempt >= maxRetries) {
418
+ throw error;
419
+ }
420
+ const delay = Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
421
+ await jitteredDelay(delay);
422
+ }
423
+ }
424
+ // Should be unreachable — the loop always throws or returns.
425
+ throw lastError;
311
426
  }
312
427
  }
313
428
  export class SandboxResource {
@@ -378,7 +493,7 @@ export class SandboxResource {
378
493
  const client = this.client;
379
494
  const id = this.id;
380
495
  return {
381
- add: (hostname) => client.addCustomDomain(id, hostname),
496
+ add: (hostname, options) => client.addCustomDomain(id, hostname, options),
382
497
  remove: (hostname) => client.removeCustomDomain(id, hostname),
383
498
  list: () => client.listCustomDomains(id),
384
499
  dns: () => client.customDomainDNS(id),
@@ -540,6 +655,25 @@ function fromApiCreateSandboxResponse(response) {
540
655
  sshPrivateKey: response.ssh_private_key,
541
656
  };
542
657
  }
658
+ function fromApiTemplate(template) {
659
+ return {
660
+ id: template.id,
661
+ image: template.image,
662
+ status: template.status,
663
+ rootfsSizeBytes: template.rootfs_size_bytes,
664
+ minSizeMiB: template.min_size_mib,
665
+ lastError: template.last_error,
666
+ createdAt: template.created_at,
667
+ updatedAt: template.updated_at,
668
+ readyAt: template.ready_at,
669
+ snapshotSizeBytes: template.snapshot_size_bytes,
670
+ snapshotError: template.snapshot_error,
671
+ hasSnapshot: template.has_snapshot,
672
+ hasOverlay: template.has_overlay,
673
+ pushState: template.push_state,
674
+ pushError: template.push_error,
675
+ };
676
+ }
543
677
  function fromApiSandboxSnapshot(snapshot) {
544
678
  return {
545
679
  name: snapshot.name,
@@ -580,6 +714,7 @@ function fromApiCustomDomain(domain) {
580
714
  lastError: domain.last_error,
581
715
  createdAt: domain.created_at,
582
716
  updatedAt: domain.updated_at,
717
+ targetPort: domain.target_port,
583
718
  };
584
719
  }
585
720
  function fromApiExposedPort(port) {
package/dist/types.d.ts CHANGED
@@ -164,7 +164,7 @@ export interface CreateOptions {
164
164
  *
165
165
  * GPU access is not supported with `"gvisor"`.
166
166
  */
167
- runtime?: "docker" | "gvisor" | "kata";
167
+ runtime?: "docker" | "gvisor" | "kata" | "firecracker";
168
168
  /**
169
169
  * Attach GPU resources to the sandbox. Omit for CPU-only workloads.
170
170
  * Not compatible with runtime="gvisor".
@@ -240,6 +240,22 @@ export interface CustomDomain {
240
240
  lastError?: string;
241
241
  createdAt: string;
242
242
  updatedAt: string;
243
+ /**
244
+ * Target container port traffic to this hostname dials. `0` (or omitted)
245
+ * means the sandbox's toolbox port — the default that preserves the
246
+ * pre-target-port behavior. Set once at attach time; to change it, remove
247
+ * the binding and re-add it with the new port.
248
+ */
249
+ targetPort?: number;
250
+ }
251
+ /**
252
+ * Options for `sandbox.customDomains.add()`. `port` pins the container port
253
+ * traffic to this hostname dials; omit it to route to the sandbox's toolbox
254
+ * port (the default). Re-adding the same hostname with a different `port`
255
+ * returns 409 — detach first.
256
+ */
257
+ export interface AddCustomDomainOptions {
258
+ port?: number;
243
259
  }
244
260
  /**
245
261
  * Where the SDK derived the ingress address(es) from. Reflects how the
@@ -263,8 +279,13 @@ export interface IngressTarget {
263
279
  ips?: string[];
264
280
  source: IngressTargetSource;
265
281
  }
266
- /** DNS record types the daemon emits for custom-domain wiring. */
267
- export type DNSRecordType = "CNAME" | "A" | "AAAA";
282
+ /**
283
+ * DNS record types the daemon emits for custom-domain wiring. `ANAME` and
284
+ * `ALIAS` only appear for an apex domain on a hostname ingress, where they are
285
+ * mutually-exclusive flattening alternatives to `CNAME` (add the one your
286
+ * provider supports — see each record's `notes`).
287
+ */
288
+ export type DNSRecordType = "CNAME" | "A" | "AAAA" | "ANAME" | "ALIAS";
268
289
  /**
269
290
  * One ready-to-paste DNS record a user adds at their DNS provider to make a
270
291
  * custom domain reach the cluster. `name` is the leftmost label (or "@" for
@@ -359,7 +380,7 @@ export interface Sandbox {
359
380
  * Container runtime this sandbox is running under. Empty string indicates
360
381
  * a pre-migration row that resolves to the host default at start time.
361
382
  */
362
- runtime: "" | "docker" | "gvisor" | "kata";
383
+ runtime: "" | "docker" | "gvisor" | "kata" | "firecracker";
363
384
  /** GPU configuration this sandbox was created with. Absent means no GPU. */
364
385
  gpus?: GPUOptions;
365
386
  }
@@ -454,3 +475,63 @@ export interface HealthStatus {
454
475
  version: string;
455
476
  }
456
477
  export type BinaryLike = Uint8Array | ArrayBuffer | Blob | string;
478
+ /**
479
+ * Lifecycle states reported by a Firecracker rootfs template. See
480
+ * `plans/snapshot-clone-fast-boot.md` for the state machine.
481
+ *
482
+ * - `pending`, `building_rootfs`, `snapshotting`: the build pipeline is in
483
+ * flight; no sandbox creation against this template will succeed yet.
484
+ * - `ready`: cold boot + fast snapshot clone both available.
485
+ * - `ready_no_snapshot`: cold boot works; snapshot phase failed and
486
+ * sandboxes will not get the fast-boot path until a rebuild succeeds.
487
+ * - `unhealthy`: snapshot was detected as corrupt; an async rebuild is in
488
+ * flight (or pending the next daemon restart).
489
+ * - `failed`: terminal state from a failed initial build; the row must be
490
+ * deleted and recreated.
491
+ */
492
+ export type TemplateStatus = "pending" | "building_rootfs" | "snapshotting" | "ready" | "ready_no_snapshot" | "failed" | "unhealthy";
493
+ /**
494
+ * Background-push state for the template's artifacts (rootfs.ext4 +
495
+ * snapshot.*). "active" means push succeeded or push is disabled; "pending"
496
+ * / "pushing" are in-flight states; "error" carries `pushError` for the
497
+ * last failure.
498
+ */
499
+ export type TemplatePushState = "active" | "pending" | "pushing" | "error";
500
+ /**
501
+ * A Firecracker rootfs template. Created once from an OCI image and
502
+ * boot-shared across many sandboxes. Use {@link MicroVM.createTemplate} to
503
+ * register one, then pass its {@link Template.id} on subsequent sandbox
504
+ * `create` calls (when the sandbox runtime is `firecracker`).
505
+ */
506
+ export interface Template {
507
+ id: string;
508
+ image: string;
509
+ status: TemplateStatus;
510
+ rootfsSizeBytes?: number;
511
+ /** Optional floor for the ext4 image size, in MiB. */
512
+ minSizeMiB?: number;
513
+ /** Last build-pipeline error, populated when `status` is `failed`. */
514
+ lastError?: string;
515
+ createdAt: string;
516
+ updatedAt: string;
517
+ /** First time the template reached `ready` / `ready_no_snapshot`. */
518
+ readyAt?: string;
519
+ snapshotSizeBytes?: number;
520
+ /** Populated when `status` is `ready_no_snapshot` — why the snapshot phase failed. */
521
+ snapshotError?: string;
522
+ /** True when the template has a usable snapshot for fast-boot. */
523
+ hasSnapshot: boolean;
524
+ /** True when the template was built with a per-sandbox writable overlay placeholder. */
525
+ hasOverlay: boolean;
526
+ pushState?: TemplatePushState;
527
+ /** Populated when `pushState` is `error` — the last push failure. */
528
+ pushError?: string;
529
+ }
530
+ export interface CreateTemplateOptions {
531
+ /** Optional explicit ID. Empty means the daemon generates one. Supplying an explicit ID lets retries be idempotent — a duplicate ID returns 409. */
532
+ id?: string;
533
+ /** skopeo-style image reference, e.g. `"docker://python:3.11"`. */
534
+ image: string;
535
+ /** Optional floor for the ext4 image size, in MiB. */
536
+ minSizeMiB?: number;
537
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aerol-ai/aerolvm-sdk",
3
- "version": "0.3.2",
3
+ "version": "0.5.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -23,6 +23,6 @@
23
23
  "devDependencies": {
24
24
  "@types/node": "^25.9.1",
25
25
  "typescript": "^6.0.3",
26
- "tsx": "^4.22.3"
26
+ "tsx": "^4.22.4"
27
27
  }
28
28
  }