@aerol-ai/aerolvm-sdk 0.2.3 → 0.3.2

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
1
  import { type APIVersion } 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, Lifecycle, ListOptions, MountSpecRedacted, RegisterSnapshotOptions, ResizeOptions, SandboxSnapshot, Session, SessionAttachHandle, SessionAttachOptions } from "./types.js";
4
+ import type { BuildImageOptions, BuildImageResult, CreateOptions, CreateSessionOptions, ExecStreamHandle, ExecStreamOptions, HealthStatus, IngressTarget, Lifecycle, ListOptions, MountSpecRedacted, RegisterSnapshotOptions, ResizeOptions, SandboxSnapshot, Session, SessionAttachHandle, SessionAttachOptions } from "./types.js";
5
5
  type FetchLike = typeof fetch;
6
6
  export interface MicroVMConfig {
7
7
  patToken?: string;
@@ -51,6 +51,16 @@ export declare class MicroVM {
51
51
  sessionLog(sandboxID: string, sessionID: string): Promise<Uint8Array>;
52
52
  sessionRecording(sandboxID: string, sessionID: string): Promise<Uint8Array>;
53
53
  attachSession(sandboxID: string, sessionID: string, options?: SessionAttachOptions): SessionAttachHandle;
54
+ /**
55
+ * Cluster-level DNS helpers. Exposed as a namespaced accessor (rather
56
+ * than a flat `ingressDNS()` method) so call sites read like
57
+ * `microvm.dns.target()`, mirroring the per-sandbox `sandbox.customDomains`
58
+ * pattern. A fresh object is returned per access so the closures always
59
+ * see the current client even if MicroVM is later extended.
60
+ */
61
+ get dns(): {
62
+ target(): Promise<IngressTarget>;
63
+ };
54
64
  private wrap;
55
65
  }
56
66
  export {};
package/dist/MicroVM.js CHANGED
@@ -109,6 +109,19 @@ export class MicroVM {
109
109
  attachSession(sandboxID, sessionID, options = {}) {
110
110
  return this.client.attachSession(sandboxID, sessionID, options);
111
111
  }
112
+ /**
113
+ * Cluster-level DNS helpers. Exposed as a namespaced accessor (rather
114
+ * than a flat `ingressDNS()` method) so call sites read like
115
+ * `microvm.dns.target()`, mirroring the per-sandbox `sandbox.customDomains`
116
+ * pattern. A fresh object is returned per access so the closures always
117
+ * see the current client even if MicroVM is later extended.
118
+ */
119
+ get dns() {
120
+ const client = this.client;
121
+ return {
122
+ target: () => client.ingressDNS(),
123
+ };
124
+ }
112
125
  wrap(sandbox) {
113
126
  return new Sandbox(this.client, sandbox);
114
127
  }
package/dist/index.d.ts CHANGED
@@ -2,4 +2,4 @@ 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, ExecRequest, ExecResult, ExecExitInfo, ExposedPort, ExposePortOptions, ExposeProtocol, ExposeResult, Failover, FailoverPolicy, HealthStatus, Lifecycle, MountSpec, MountSpecRedacted, MountType, RegisterSnapshotOptions, RegistryAuth, ResizeOptions, Sandbox as SandboxData, SandboxSnapshot, SandboxStatus, Session, SessionAttachHandle, SessionAttachOptions, SessionStatus, UpdateLifecycleOptions, } from "./types.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";
@@ -1 +1,28 @@
1
1
  export declare const PATH_PREFIX = "/v1";
2
+ /**
3
+ * Collection URL for a sandbox's custom-domain bindings. Used for both POST
4
+ * (add) and GET (list). The DELETE variant lives at
5
+ * {@link sandboxCustomDomainPath}.
6
+ *
7
+ * `id` is interpolated verbatim — sandbox IDs are server-generated opaque
8
+ * tokens and already URL-safe.
9
+ */
10
+ export declare function sandboxCustomDomainsPath(prefix: string, id: string): string;
11
+ /**
12
+ * URL for a single custom-domain binding. The hostname is percent-encoded so
13
+ * IDN punycode (`xn--...`) and wildcard labels round-trip safely through
14
+ * `DELETE /v1/sandboxes/{id}/custom-domains/{hostname}`.
15
+ */
16
+ export declare function sandboxCustomDomainPath(prefix: string, id: string, hostname: string): string;
17
+ /**
18
+ * URL for the cluster's published ingress address(es), used by
19
+ * `microvm.dns.target()` to render the CNAME/A target a user must point
20
+ * custom-domain DNS at.
21
+ */
22
+ export declare function ingressDNSPath(prefix: string): string;
23
+ /**
24
+ * URL for the ready-to-paste DNS records of one sandbox's custom-domain
25
+ * bindings. Sibling of {@link sandboxCustomDomainsPath} — same `id` rules
26
+ * apply.
27
+ */
28
+ export declare function sandboxCustomDomainDNSPath(prefix: string, id: string): string;
@@ -5,3 +5,38 @@
5
5
  //
6
6
  // Keep this in sync with pkg/api/v1/dto.go::PathPrefix on the server.
7
7
  export const PATH_PREFIX = "/v1";
8
+ /**
9
+ * Collection URL for a sandbox's custom-domain bindings. Used for both POST
10
+ * (add) and GET (list). The DELETE variant lives at
11
+ * {@link sandboxCustomDomainPath}.
12
+ *
13
+ * `id` is interpolated verbatim — sandbox IDs are server-generated opaque
14
+ * tokens and already URL-safe.
15
+ */
16
+ export function sandboxCustomDomainsPath(prefix, id) {
17
+ return `${prefix}/sandboxes/${id}/custom-domains`;
18
+ }
19
+ /**
20
+ * URL for a single custom-domain binding. The hostname is percent-encoded so
21
+ * IDN punycode (`xn--...`) and wildcard labels round-trip safely through
22
+ * `DELETE /v1/sandboxes/{id}/custom-domains/{hostname}`.
23
+ */
24
+ export function sandboxCustomDomainPath(prefix, id, hostname) {
25
+ return `${prefix}/sandboxes/${id}/custom-domains/${encodeURIComponent(hostname)}`;
26
+ }
27
+ /**
28
+ * URL for the cluster's published ingress address(es), used by
29
+ * `microvm.dns.target()` to render the CNAME/A target a user must point
30
+ * custom-domain DNS at.
31
+ */
32
+ export function ingressDNSPath(prefix) {
33
+ return `${prefix}/ingress/dns`;
34
+ }
35
+ /**
36
+ * URL for the ready-to-paste DNS records of one sandbox's custom-domain
37
+ * bindings. Sibling of {@link sandboxCustomDomainsPath} — same `id` rules
38
+ * apply.
39
+ */
40
+ export function sandboxCustomDomainDNSPath(prefix, id) {
41
+ return `${prefix}/sandboxes/${id}/custom-domains/dns`;
42
+ }
@@ -1,5 +1,5 @@
1
1
  import { Image } from "../Image.js";
2
- import type { BinaryLike, BuildImageOptions, BuildImageResult, CreateOptions, CreateSessionOptions, ExecRequest, ExecResult, ExecStreamHandle, ExecStreamOptions, ExposedPort, ExposePortOptions, ExposeResult, Failover, HealthStatus, Lifecycle, ListOptions, MountSpecRedacted, NetworkUsage, RegisterSnapshotOptions, SetNetworkLimitsOptions, ResizeOptions, Sandbox, SandboxSnapshot, Session, SessionAttachHandle, SessionAttachOptions } from "../types.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";
3
3
  type FetchLike = typeof fetch;
4
4
  /**
5
5
  * Wire version of the v1-style sandbox endpoints (`/v1/sandboxes`, etc.) this
@@ -93,6 +93,26 @@ export declare class APIClient {
93
93
  */
94
94
  exposePort(id: string, port: number, options?: ExposePortOptions): Promise<ExposeResult>;
95
95
  unexposePort(id: string, port: number): Promise<void>;
96
+ /**
97
+ * Bind a custom hostname to a sandbox. The server lowercases the hostname
98
+ * and returns the post-add list of bindings (sorted by hostname). Calling
99
+ * with an already-registered hostname is idempotent and returns the
100
+ * existing list.
101
+ */
102
+ addCustomDomain(id: string, hostname: string): Promise<CustomDomain[]>;
103
+ listCustomDomains(id: string): Promise<CustomDomain[]>;
104
+ removeCustomDomain(id: string, hostname: string): Promise<void>;
105
+ /**
106
+ * Fetch the cluster's published ingress address(es) — the CNAME/A target
107
+ * a user must point custom-domain DNS at. Field names already match the
108
+ * SDK's camelCase shape on the wire, so no translation is needed.
109
+ */
110
+ ingressDNS(): Promise<IngressTarget>;
111
+ /**
112
+ * Fetch the ready-to-paste DNS records for one sandbox's custom-domain
113
+ * bindings, along with the {@link IngressTarget} they were composed from.
114
+ */
115
+ customDomainDNS(id: string): Promise<CustomDomainDNSRecords>;
96
116
  reconcile(): Promise<void>;
97
117
  health(): Promise<HealthStatus>;
98
118
  mounts(id: string): Promise<MountSpecRedacted[]>;
@@ -146,6 +166,20 @@ export declare class SandboxResource implements Sandbox {
146
166
  downloadFile(targetPath: string): Promise<Uint8Array>;
147
167
  exposePort(port: number, options?: ExposePortOptions): Promise<ExposeResult>;
148
168
  unexposePort(port: number): Promise<void>;
169
+ /**
170
+ * Per-sandbox custom-hostname bindings. Exposed as a namespaced accessor
171
+ * (rather than `addCustomDomain`/`removeCustomDomain`/`listCustomDomains`
172
+ * methods on the resource) so call sites read like
173
+ * `sandbox.customDomains.add("api.acme.com")`, mirroring the noun in the
174
+ * `/v1/.../custom-domains` URL space. A fresh object is returned per access
175
+ * so the closures always see the current `id` even after `refresh()`.
176
+ */
177
+ get customDomains(): {
178
+ add(hostname: string): Promise<CustomDomain[]>;
179
+ remove(hostname: string): Promise<void>;
180
+ list(): Promise<CustomDomain[]>;
181
+ dns(): Promise<CustomDomainDNSRecords>;
182
+ };
149
183
  createSnapshot(name: string): Promise<SandboxSnapshot>;
150
184
  start(): Promise<this>;
151
185
  stop(): Promise<this>;
@@ -1,5 +1,5 @@
1
1
  import { basename } from "node:path";
2
- import { PATH_PREFIX as V1_PATH_PREFIX } from "./api/v1/paths.js";
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
3
  import { Image } from "../Image.js";
4
4
  const DEFAULT_API_VERSION = "v1";
5
5
  const PATH_PREFIXES = {
@@ -220,6 +220,38 @@ export class APIClient {
220
220
  async unexposePort(id, port) {
221
221
  await this.doJSON("DELETE", `${this.versionPrefix}/sandboxes/${id}/ports/${port}`);
222
222
  }
223
+ /**
224
+ * Bind a custom hostname to a sandbox. The server lowercases the hostname
225
+ * and returns the post-add list of bindings (sorted by hostname). Calling
226
+ * with an already-registered hostname is idempotent and returns the
227
+ * existing list.
228
+ */
229
+ async addCustomDomain(id, hostname) {
230
+ const response = await this.doJSON("POST", v1SandboxCustomDomainsPath(this.versionPrefix, id), { hostname });
231
+ return response.custom_domains.map(fromApiCustomDomain);
232
+ }
233
+ async listCustomDomains(id) {
234
+ const response = await this.doJSON("GET", v1SandboxCustomDomainsPath(this.versionPrefix, id));
235
+ return response.custom_domains.map(fromApiCustomDomain);
236
+ }
237
+ async removeCustomDomain(id, hostname) {
238
+ await this.doJSON("DELETE", v1SandboxCustomDomainPath(this.versionPrefix, id, hostname));
239
+ }
240
+ /**
241
+ * Fetch the cluster's published ingress address(es) — the CNAME/A target
242
+ * a user must point custom-domain DNS at. Field names already match the
243
+ * SDK's camelCase shape on the wire, so no translation is needed.
244
+ */
245
+ async ingressDNS() {
246
+ return this.doJSON("GET", v1IngressDNSPath(this.versionPrefix));
247
+ }
248
+ /**
249
+ * Fetch the ready-to-paste DNS records for one sandbox's custom-domain
250
+ * bindings, along with the {@link IngressTarget} they were composed from.
251
+ */
252
+ async customDomainDNS(id) {
253
+ return this.doJSON("GET", v1SandboxCustomDomainDNSPath(this.versionPrefix, id));
254
+ }
223
255
  async reconcile() {
224
256
  await this.doJSON("POST", this.versioned("/admin/reconcile"));
225
257
  }
@@ -334,6 +366,24 @@ export class SandboxResource {
334
366
  async unexposePort(port) {
335
367
  await this.client.unexposePort(this.id, port);
336
368
  }
369
+ /**
370
+ * Per-sandbox custom-hostname bindings. Exposed as a namespaced accessor
371
+ * (rather than `addCustomDomain`/`removeCustomDomain`/`listCustomDomains`
372
+ * methods on the resource) so call sites read like
373
+ * `sandbox.customDomains.add("api.acme.com")`, mirroring the noun in the
374
+ * `/v1/.../custom-domains` URL space. A fresh object is returned per access
375
+ * so the closures always see the current `id` even after `refresh()`.
376
+ */
377
+ get customDomains() {
378
+ const client = this.client;
379
+ const id = this.id;
380
+ return {
381
+ add: (hostname) => client.addCustomDomain(id, hostname),
382
+ remove: (hostname) => client.removeCustomDomain(id, hostname),
383
+ list: () => client.listCustomDomains(id),
384
+ dns: () => client.customDomainDNS(id),
385
+ };
386
+ }
337
387
  async createSnapshot(name) {
338
388
  return this.client.createSnapshot(this.id, name);
339
389
  }
@@ -390,6 +440,7 @@ function toApiCreateOptions(options) {
390
440
  lifecycle: options.lifecycle ? toApiLifecycle(options.lifecycle) : undefined,
391
441
  failover: options.failover ? toApiFailover(options.failover) : undefined,
392
442
  runtime: options.runtime,
443
+ custom_domains: options.customDomains,
393
444
  };
394
445
  }
395
446
  function toApiRegisterSnapshotOptions(options) {
@@ -522,6 +573,15 @@ function fromApiSession(session) {
522
573
  attached: session.attached,
523
574
  };
524
575
  }
576
+ function fromApiCustomDomain(domain) {
577
+ return {
578
+ hostname: domain.hostname,
579
+ status: domain.status,
580
+ lastError: domain.last_error,
581
+ createdAt: domain.created_at,
582
+ updatedAt: domain.updated_at,
583
+ };
584
+ }
525
585
  function fromApiExposedPort(port) {
526
586
  return {
527
587
  sandboxID: port.sandbox_id,
package/dist/types.d.ts CHANGED
@@ -170,6 +170,13 @@ export interface CreateOptions {
170
170
  * Not compatible with runtime="gvisor".
171
171
  */
172
172
  gpus?: GPUOptions;
173
+ /**
174
+ * Custom hostnames to bind to this sandbox at create-time. Each entry is
175
+ * lowercased server-side and progresses through the standard custom-domain
176
+ * lifecycle (pending_dns → issuing → ready). Equivalent to calling
177
+ * `sandbox.customDomains.add(host)` for each entry after create.
178
+ */
179
+ customDomains?: string[];
173
180
  }
174
181
  export interface ResizeOptions {
175
182
  /** Number of CPU cores. Fractional values supported (e.g. 0.5). */
@@ -219,6 +226,69 @@ export interface ExposedPort {
219
226
  publicURL: string;
220
227
  createdAt: string;
221
228
  }
229
+ /**
230
+ * Lifecycle states for a per-sandbox custom hostname.
231
+ * - "pending_dns": hostname registered; awaiting DNS to point at the daemon.
232
+ * - "issuing": DNS resolves; Caddy is acquiring an ACME certificate.
233
+ * - "ready": certificate issued; traffic served end-to-end on the host.
234
+ * - "failed": ACME or DNS validation failed; see `lastError`.
235
+ */
236
+ export type CustomDomainStatus = "pending_dns" | "issuing" | "ready" | "failed";
237
+ export interface CustomDomain {
238
+ hostname: string;
239
+ status: CustomDomainStatus;
240
+ lastError?: string;
241
+ createdAt: string;
242
+ updatedAt: string;
243
+ }
244
+ /**
245
+ * Where the SDK derived the ingress address(es) from. Reflects how the
246
+ * operator deployed the cluster:
247
+ * - "hostname": the cluster gossips a stable hostname (CNAME target).
248
+ * - "ips": one or more raw ingress IPs (one A/AAAA record per IP).
249
+ * - "mixed": some nodes gossip a hostname, others raw IPs — prefer
250
+ * the hostname but render both so apex domains still work.
251
+ * - "unknown": no ingress node has gossiped a public address yet; the
252
+ * target is unusable and callers should surface that, not
253
+ * guess.
254
+ */
255
+ export type IngressTargetSource = "hostname" | "ips" | "mixed" | "unknown";
256
+ /**
257
+ * The cluster's published ingress address(es) — the value that DNS for a
258
+ * custom domain must ultimately resolve to. Returned by `microvm.dns.target()`
259
+ * and embedded in {@link CustomDomainDNSRecords}.
260
+ */
261
+ export interface IngressTarget {
262
+ hostname?: string;
263
+ ips?: string[];
264
+ source: IngressTargetSource;
265
+ }
266
+ /** DNS record types the daemon emits for custom-domain wiring. */
267
+ export type DNSRecordType = "CNAME" | "A" | "AAAA";
268
+ /**
269
+ * One ready-to-paste DNS record a user adds at their DNS provider to make a
270
+ * custom domain reach the cluster. `name` is the leftmost label (or "@" for
271
+ * apex) the way DNS UIs accept it. `notes` carries provider-specific gotchas
272
+ * the daemon pre-renders (e.g. Cloudflare "DNS only, gray cloud") when
273
+ * applicable.
274
+ */
275
+ export interface DNSRecord {
276
+ hostname: string;
277
+ type: DNSRecordType;
278
+ name: string;
279
+ value: string;
280
+ notes?: string;
281
+ }
282
+ /**
283
+ * Response from `sandbox.customDomains.dns()`. `records` is the flat
284
+ * ready-to-paste list (one row per custom domain × per ingress address);
285
+ * `target` is the raw aggregation the records were composed from, included
286
+ * so callers can render their own UI without a second round trip.
287
+ */
288
+ export interface CustomDomainDNSRecords {
289
+ records: DNSRecord[];
290
+ target: IngressTarget;
291
+ }
222
292
  /**
223
293
  * Wire protocol an exposure publishes through:
224
294
  * - "http": Caddy HTTP reverse proxy at https://<id>-<port>.<domain>.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aerol-ai/aerolvm-sdk",
3
- "version": "0.2.3",
3
+ "version": "0.3.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -21,7 +21,7 @@
21
21
  "test": "tsc -p tsconfig.test.json --outDir dist-test && node --test dist-test/*.test.js dist-test/internal/*.test.js"
22
22
  },
23
23
  "devDependencies": {
24
- "@types/node": "^25.8.0",
24
+ "@types/node": "^25.9.1",
25
25
  "typescript": "^6.0.3",
26
26
  "tsx": "^4.22.3"
27
27
  }