@aerol-ai/aerolvm-sdk 0.1.5 → 0.1.7

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,3 +1,4 @@
1
+ import { type APIVersion } from "./internal/client.js";
1
2
  import { Sandbox } from "./Sandbox.js";
2
3
  import type { CreateOptions, CreateSessionOptions, ExecStreamHandle, ExecStreamOptions, HealthStatus, Lifecycle, MountSpecRedacted, ResizeOptions, Session, SessionAttachHandle, SessionAttachOptions } from "./types.js";
3
4
  type FetchLike = typeof fetch;
@@ -5,6 +6,13 @@ export interface MicroVMConfig {
5
6
  patToken?: string;
6
7
  apiUrl?: string;
7
8
  fetch?: FetchLike;
9
+ /**
10
+ * Wire version of the sandbox daemon API to call. Defaults to the SDK's
11
+ * pinned default ("v1" today). The SDK package version and the API wire
12
+ * version evolve independently — you can pin one without affecting the
13
+ * other.
14
+ */
15
+ apiVersion?: APIVersion;
8
16
  }
9
17
  export declare class MicroVM {
10
18
  readonly apiUrl: string;
package/dist/MicroVM.js CHANGED
@@ -18,6 +18,7 @@ export class MicroVM {
18
18
  baseURL: apiUrl,
19
19
  patToken,
20
20
  fetch: config.fetch,
21
+ apiVersion: config.apiVersion,
21
22
  });
22
23
  }
23
24
  async create(options) {
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { MicroVM } from "./MicroVM.js";
2
2
  export { Sandbox } from "./Sandbox.js";
3
3
  export type { MicroVMConfig } from "./MicroVM.js";
4
- export type { BinaryLike, CreateOptions, CreateSessionOptions, ExecRequest, ExecResult, ExecExitInfo, ExposedPort, HealthStatus, Lifecycle, MountSpec, MountSpecRedacted, MountType, RegistryAuth, ResizeOptions, Sandbox as SandboxData, SandboxStatus, Session, SessionAttachHandle, SessionAttachOptions, SessionStatus, UpdateLifecycleOptions, } from "./types.js";
4
+ export type { BinaryLike, CreateOptions, CreateSessionOptions, ExecRequest, ExecResult, ExecExitInfo, ExposedPort, ExposePortOptions, ExposeProtocol, ExposeResult, HealthStatus, Lifecycle, MountSpec, MountSpecRedacted, MountType, RegistryAuth, ResizeOptions, Sandbox as SandboxData, SandboxStatus, Session, SessionAttachHandle, SessionAttachOptions, SessionStatus, UpdateLifecycleOptions, } from "./types.js";
@@ -0,0 +1 @@
1
+ export declare const PATH_PREFIX = "/v1";
@@ -0,0 +1,7 @@
1
+ // v1 path-prefix constant. The TypeScript SDK uses this to build URLs against
2
+ // the v1 surface of the sandbox daemon. When v2 lands, a sibling
3
+ // internal/api/v2/paths.ts will export its own PATH_PREFIX, and the client
4
+ // constructor's apiVersion option selects which one to use.
5
+ //
6
+ // Keep this in sync with pkg/api/v1/dto.go::PathPrefix on the server.
7
+ export const PATH_PREFIX = "/v1";
@@ -1,15 +1,37 @@
1
- import type { BinaryLike, CreateOptions, CreateSessionOptions, ExecRequest, ExecResult, ExecStreamHandle, ExecStreamOptions, ExposedPort, HealthStatus, Lifecycle, MountSpecRedacted, ResizeOptions, Sandbox, Session, SessionAttachHandle, SessionAttachOptions } from "../types.js";
1
+ import type { BinaryLike, CreateOptions, CreateSessionOptions, ExecRequest, ExecResult, ExecStreamHandle, ExecStreamOptions, ExposedPort, ExposePortOptions, ExposeResult, HealthStatus, Lifecycle, MountSpecRedacted, ResizeOptions, Sandbox, Session, SessionAttachHandle, SessionAttachOptions } from "../types.js";
2
2
  type FetchLike = typeof fetch;
3
+ /**
4
+ * Wire version of the sandbox daemon API this client speaks. The SDK and the
5
+ * server version independently — bumping the SDK package does not move the
6
+ * wire version. Today only "v1" exists; "v2" will be added when a wire-level
7
+ * breaking change is needed on the server.
8
+ */
9
+ export type APIVersion = "v1";
3
10
  export interface APIClientConfig {
4
11
  baseURL: string;
5
12
  patToken?: string;
6
13
  fetch?: FetchLike;
14
+ /**
15
+ * Wire version to call. Defaults to "v1". Pin this if you need a specific
16
+ * version — otherwise the SDK will track its own default, which may move in
17
+ * a future major SDK release.
18
+ */
19
+ apiVersion?: APIVersion;
7
20
  }
8
21
  export declare class APIClient {
9
22
  readonly baseURL: string;
23
+ readonly apiVersion: APIVersion;
10
24
  private readonly patToken;
11
25
  private readonly fetchFn;
26
+ private readonly versionPrefix;
12
27
  constructor(config: APIClientConfig);
28
+ /**
29
+ * Build a versioned API path. Pass the suffix beginning with "/" (e.g.
30
+ * "/sandboxes") and the active version's prefix is prepended. Use this for
31
+ * every versioned API call so v2 (when it lands) can be selected by the
32
+ * apiVersion option without touching call sites.
33
+ */
34
+ private versioned;
13
35
  create(options: CreateOptions): Promise<SandboxResource>;
14
36
  list(): Promise<SandboxResource[]>;
15
37
  get(id: string): Promise<SandboxResource>;
@@ -31,20 +53,20 @@ export declare class APIClient {
31
53
  attachSession(id: string, sessionID: string, options?: SessionAttachOptions): SessionAttachHandle;
32
54
  uploadFile(id: string, targetPath: string, data: BinaryLike): Promise<void>;
33
55
  downloadFile(id: string, targetPath: string): Promise<Uint8Array>;
34
- exposePort(id: string, port: number): Promise<string>;
35
- /**
36
- * Publish a raw TCP port through caddy-l4. The returned URL is in
37
- * `tcp://<host>:<port>` form and works directly with `psql`, `redis-cli`,
38
- * `mysql`, `mongosh`, and any other native-protocol client.
39
- */
40
- exposeTCPPort(id: string, port: number): Promise<string>;
41
56
  /**
42
- * Publish a TCP port behind the shared TLS-SNI multiplexer. Returns
43
- * `tls://<id>-<port>.<domain>:<l4-port>`. Requires the daemon to have a
44
- * domain configured AND `SB_L4_TLS_LISTEN` set.
57
+ * Publish a sandbox container port. The chosen wire protocol is selected by
58
+ * `options.protocol` (defaults to `"http"`):
59
+ *
60
+ * - `"http"`: Caddy HTTP reverse proxy at `https://<id>-<port>.<domain>`.
61
+ * - `"tcp"`: raw caddy-l4 listener on a parent-host port — pair with
62
+ * native protocol clients (psql, redis-cli, mysql, mongosh).
63
+ * - `"tls"`: caddy-l4 TLS-SNI route on the shared listener. Requires the
64
+ * daemon to have a domain configured AND `SB_L4_TLS_LISTEN` set.
65
+ *
66
+ * Returns a discriminated result keyed on `protocol`. Only the `"tcp"`
67
+ * variant carries `host` / `hostPort` — everything else is in `url`.
45
68
  */
46
- exposeTLSPort(id: string, port: number): Promise<string>;
47
- private exposePortWithProtocol;
69
+ exposePort(id: string, port: number, options?: ExposePortOptions): Promise<ExposeResult>;
48
70
  unexposePort(id: string, port: number): Promise<void>;
49
71
  reconcile(): Promise<void>;
50
72
  health(): Promise<HealthStatus>;
@@ -94,9 +116,7 @@ export declare class SandboxResource implements Sandbox {
94
116
  attachSession(sessionID: string, options?: SessionAttachOptions): SessionAttachHandle;
95
117
  uploadFile(targetPath: string, data: BinaryLike): Promise<void>;
96
118
  downloadFile(targetPath: string): Promise<Uint8Array>;
97
- exposePort(port: number): Promise<string>;
98
- exposeTCPPort(port: number): Promise<string>;
99
- exposeTLSPort(port: number): Promise<string>;
119
+ exposePort(port: number, options?: ExposePortOptions): Promise<ExposeResult>;
100
120
  unexposePort(port: number): Promise<void>;
101
121
  start(): Promise<this>;
102
122
  stop(): Promise<this>;
@@ -1,133 +1,145 @@
1
1
  import { basename } from "node:path";
2
+ import { PATH_PREFIX as V1_PATH_PREFIX } from "./api/v1/paths.js";
3
+ const DEFAULT_API_VERSION = "v1";
4
+ const PATH_PREFIXES = {
5
+ v1: V1_PATH_PREFIX,
6
+ };
2
7
  export class APIClient {
3
8
  baseURL;
9
+ apiVersion;
4
10
  patToken;
5
11
  fetchFn;
12
+ versionPrefix;
6
13
  constructor(config) {
7
14
  this.baseURL = config.baseURL.replace(/\/+$/, "");
8
15
  this.patToken = config.patToken ?? "";
9
16
  this.fetchFn = config.fetch ?? fetch;
17
+ this.apiVersion = config.apiVersion ?? DEFAULT_API_VERSION;
18
+ this.versionPrefix = PATH_PREFIXES[this.apiVersion];
19
+ }
20
+ /**
21
+ * Build a versioned API path. Pass the suffix beginning with "/" (e.g.
22
+ * "/sandboxes") and the active version's prefix is prepended. Use this for
23
+ * every versioned API call so v2 (when it lands) can be selected by the
24
+ * apiVersion option without touching call sites.
25
+ */
26
+ versioned(suffix) {
27
+ return `${this.versionPrefix}${suffix}`;
10
28
  }
11
29
  async create(options) {
12
- const response = await this.doJSON("POST", "/v1/sandboxes", toApiCreateOptions(options));
30
+ const response = await this.doJSON("POST", this.versioned("/sandboxes"), toApiCreateOptions(options));
13
31
  return new SandboxResource(this, fromApiCreateSandboxResponse(response));
14
32
  }
15
33
  async list() {
16
- const response = await this.doJSON("GET", "/v1/sandboxes");
34
+ const response = await this.doJSON("GET", this.versioned("/sandboxes"));
17
35
  return response.map((item) => this.wrap(item));
18
36
  }
19
37
  async get(id) {
20
- const response = await this.doJSON("GET", `/v1/sandboxes/${id}`);
38
+ const response = await this.doJSON("GET", `${this.versionPrefix}/sandboxes/${id}`);
21
39
  return this.wrap(response);
22
40
  }
23
41
  async start(id) {
24
- const response = await this.doJSON("POST", `/v1/sandboxes/${id}/start`);
42
+ const response = await this.doJSON("POST", `${this.versionPrefix}/sandboxes/${id}/start`);
25
43
  return this.wrap(response);
26
44
  }
27
45
  async stop(id) {
28
- const response = await this.doJSON("POST", `/v1/sandboxes/${id}/stop`);
46
+ const response = await this.doJSON("POST", `${this.versionPrefix}/sandboxes/${id}/stop`);
29
47
  return this.wrap(response);
30
48
  }
31
49
  async destroy(id) {
32
- await this.doJSON("DELETE", `/v1/sandboxes/${id}`);
50
+ await this.doJSON("DELETE", `${this.versionPrefix}/sandboxes/${id}`);
33
51
  }
34
52
  async resize(id, options) {
35
- const response = await this.doJSON("POST", `/v1/sandboxes/${id}/resize`, toApiResizeOptions(options));
53
+ const response = await this.doJSON("POST", `${this.versionPrefix}/sandboxes/${id}/resize`, toApiResizeOptions(options));
36
54
  return this.wrap(response);
37
55
  }
38
56
  async updateLifecycle(id, lifecycle) {
39
- const response = await this.doJSON("PUT", `/v1/sandboxes/${id}/lifecycle`, toApiLifecycle(lifecycle));
57
+ const response = await this.doJSON("PUT", `${this.versionPrefix}/sandboxes/${id}/lifecycle`, toApiLifecycle(lifecycle));
40
58
  return this.wrap(response);
41
59
  }
42
60
  async exec(id, request) {
43
- const response = await this.doJSON("POST", `/v1/sandboxes/${id}/toolbox/process/execute`, toApiExecRequest(request));
61
+ const response = await this.doJSON("POST", `${this.versionPrefix}/sandboxes/${id}/toolbox/process/execute`, toApiExecRequest(request));
44
62
  return fromApiExecResult(response);
45
63
  }
46
64
  execStream(id, options) {
47
- return openExecStream(this.baseURL, this.patToken, id, options);
65
+ return openExecStream(this.baseURL, this.versionPrefix, this.patToken, id, options);
48
66
  }
49
67
  async createSession(id, options) {
50
- const response = await this.doJSON("POST", `/v1/sandboxes/${id}/sessions`, toApiCreateSessionOptions(options));
68
+ const response = await this.doJSON("POST", `${this.versionPrefix}/sandboxes/${id}/sessions`, toApiCreateSessionOptions(options));
51
69
  return fromApiSession(response);
52
70
  }
53
71
  async listSessions(id) {
54
- const response = await this.doJSON("GET", `/v1/sandboxes/${id}/sessions`);
72
+ const response = await this.doJSON("GET", `${this.versionPrefix}/sandboxes/${id}/sessions`);
55
73
  return response.sessions.map(fromApiSession);
56
74
  }
57
75
  async getSession(id, sessionID) {
58
- const response = await this.doJSON("GET", `/v1/sandboxes/${id}/sessions/${sessionID}`);
76
+ const response = await this.doJSON("GET", `${this.versionPrefix}/sandboxes/${id}/sessions/${sessionID}`);
59
77
  return fromApiSession(response);
60
78
  }
61
79
  async deleteSession(id, sessionID) {
62
- await this.doJSON("DELETE", `/v1/sandboxes/${id}/sessions/${sessionID}`);
80
+ await this.doJSON("DELETE", `${this.versionPrefix}/sandboxes/${id}/sessions/${sessionID}`);
63
81
  }
64
82
  async signalSession(id, sessionID, signal) {
65
- await this.doJSON("POST", `/v1/sandboxes/${id}/sessions/${sessionID}/signal`, { signal });
83
+ await this.doJSON("POST", `${this.versionPrefix}/sandboxes/${id}/sessions/${sessionID}/signal`, { signal });
66
84
  }
67
85
  async resizeSession(id, sessionID, cols, rows) {
68
- await this.doJSON("POST", `/v1/sandboxes/${id}/sessions/${sessionID}/resize`, { cols, rows });
86
+ await this.doJSON("POST", `${this.versionPrefix}/sandboxes/${id}/sessions/${sessionID}/resize`, { cols, rows });
69
87
  }
70
88
  async sessionLog(id, sessionID) {
71
- return this.doBytes(`/v1/sandboxes/${id}/sessions/${sessionID}/log`);
89
+ return this.doBytes(`${this.versionPrefix}/sandboxes/${id}/sessions/${sessionID}/log`);
72
90
  }
73
91
  async sessionRecording(id, sessionID) {
74
- return this.doBytes(`/v1/sandboxes/${id}/sessions/${sessionID}/recording`);
92
+ return this.doBytes(`${this.versionPrefix}/sandboxes/${id}/sessions/${sessionID}/recording`);
75
93
  }
76
94
  attachSession(id, sessionID, options = {}) {
77
- return openSessionAttach(this.baseURL, this.patToken, id, sessionID, options);
95
+ return openSessionAttach(this.baseURL, this.versionPrefix, this.patToken, id, sessionID, options);
78
96
  }
79
97
  async uploadFile(id, targetPath, data) {
80
98
  const form = new FormData();
81
99
  form.set("path", targetPath);
82
100
  form.set("file", toBlob(data), basename(targetPath));
83
- const response = await this.request("POST", `/v1/sandboxes/${id}/toolbox/files/upload`, { body: form });
101
+ const response = await this.request("POST", `${this.versionPrefix}/sandboxes/${id}/toolbox/files/upload`, { body: form });
84
102
  if (!response.ok) {
85
103
  throw await decodeError(response);
86
104
  }
87
105
  }
88
106
  async downloadFile(id, targetPath) {
89
- const response = await this.request("GET", `/v1/sandboxes/${id}/toolbox/files/download?path=${encodeURIComponent(targetPath)}`);
107
+ const response = await this.request("GET", `${this.versionPrefix}/sandboxes/${id}/toolbox/files/download?path=${encodeURIComponent(targetPath)}`);
90
108
  if (!response.ok) {
91
109
  throw await decodeError(response);
92
110
  }
93
111
  return new Uint8Array(await response.arrayBuffer());
94
112
  }
95
- async exposePort(id, port) {
96
- return this.exposePortWithProtocol(id, port);
97
- }
98
- /**
99
- * Publish a raw TCP port through caddy-l4. The returned URL is in
100
- * `tcp://<host>:<port>` form and works directly with `psql`, `redis-cli`,
101
- * `mysql`, `mongosh`, and any other native-protocol client.
102
- */
103
- async exposeTCPPort(id, port) {
104
- return this.exposePortWithProtocol(id, port, "tcp");
105
- }
106
113
  /**
107
- * Publish a TCP port behind the shared TLS-SNI multiplexer. Returns
108
- * `tls://<id>-<port>.<domain>:<l4-port>`. Requires the daemon to have a
109
- * domain configured AND `SB_L4_TLS_LISTEN` set.
114
+ * Publish a sandbox container port. The chosen wire protocol is selected by
115
+ * `options.protocol` (defaults to `"http"`):
116
+ *
117
+ * - `"http"`: Caddy HTTP reverse proxy at `https://<id>-<port>.<domain>`.
118
+ * - `"tcp"`: raw caddy-l4 listener on a parent-host port — pair with
119
+ * native protocol clients (psql, redis-cli, mysql, mongosh).
120
+ * - `"tls"`: caddy-l4 TLS-SNI route on the shared listener. Requires the
121
+ * daemon to have a domain configured AND `SB_L4_TLS_LISTEN` set.
122
+ *
123
+ * Returns a discriminated result keyed on `protocol`. Only the `"tcp"`
124
+ * variant carries `host` / `hostPort` — everything else is in `url`.
110
125
  */
111
- async exposeTLSPort(id, port) {
112
- return this.exposePortWithProtocol(id, port, "tls");
113
- }
114
- async exposePortWithProtocol(id, port, protocol) {
115
- const body = protocol ? { protocol } : undefined;
116
- const response = await this.doJSON("POST", `/v1/sandboxes/${id}/ports/${port}`, body);
117
- return response.public_url;
126
+ async exposePort(id, port, options = {}) {
127
+ const body = options.protocol ? { protocol: options.protocol } : undefined;
128
+ const response = await this.doJSON("POST", `${this.versionPrefix}/sandboxes/${id}/ports/${port}`, body);
129
+ return fromApiExposePortResponse(response);
118
130
  }
119
131
  async unexposePort(id, port) {
120
- await this.doJSON("DELETE", `/v1/sandboxes/${id}/ports/${port}`);
132
+ await this.doJSON("DELETE", `${this.versionPrefix}/sandboxes/${id}/ports/${port}`);
121
133
  }
122
134
  async reconcile() {
123
- await this.doJSON("POST", "/v1/admin/reconcile");
135
+ await this.doJSON("POST", this.versioned("/admin/reconcile"));
124
136
  }
125
137
  async health() {
126
138
  const response = await this.doJSON("GET", "/health");
127
139
  return fromApiHealthStatus(response);
128
140
  }
129
141
  async mounts(id) {
130
- const response = await this.doJSON("GET", `/v1/sandboxes/${id}/mounts`);
142
+ const response = await this.doJSON("GET", `${this.versionPrefix}/sandboxes/${id}/mounts`);
131
143
  return response.mounts.map(fromApiMountSpecRedacted);
132
144
  }
133
145
  wrap(sandbox) {
@@ -219,14 +231,8 @@ export class SandboxResource {
219
231
  async downloadFile(targetPath) {
220
232
  return this.client.downloadFile(this.id, targetPath);
221
233
  }
222
- async exposePort(port) {
223
- return this.client.exposePort(this.id, port);
224
- }
225
- async exposeTCPPort(port) {
226
- return this.client.exposeTCPPort(this.id, port);
227
- }
228
- async exposeTLSPort(port) {
229
- return this.client.exposeTLSPort(this.id, port);
234
+ async exposePort(port, options) {
235
+ return this.client.exposePort(this.id, port, options);
230
236
  }
231
237
  async unexposePort(port) {
232
238
  await this.client.unexposePort(this.id, port);
@@ -379,6 +385,21 @@ function fromApiExposedPort(port) {
379
385
  createdAt: port.created_at,
380
386
  };
381
387
  }
388
+ function fromApiExposePortResponse(response) {
389
+ switch (response.protocol) {
390
+ case "tcp":
391
+ return {
392
+ protocol: "tcp",
393
+ url: response.public_url,
394
+ host: response.host ?? "",
395
+ hostPort: response.host_port ?? 0,
396
+ };
397
+ case "tls":
398
+ return { protocol: "tls", url: response.public_url };
399
+ case "http":
400
+ return { protocol: "http", url: response.public_url };
401
+ }
402
+ }
382
403
  function fromApiExecResult(result) {
383
404
  return {
384
405
  stdout: result.stdout,
@@ -497,8 +518,14 @@ function describeWSClose(event) {
497
518
  parts.push(`wasClean=${e.wasClean}`);
498
519
  return parts.join(" ");
499
520
  }
500
- function openExecStream(baseURL, patToken, sandboxID, options) {
501
- const wsURL = baseURL.replace(/^http/, "ws") + `/v1/sandboxes/${encodeURIComponent(sandboxID)}/toolbox/process/exec/stream`;
521
+ function toWebSocketBinaryFrame(data) {
522
+ const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
523
+ const frame = new Uint8Array(bytes.byteLength);
524
+ frame.set(bytes);
525
+ return frame;
526
+ }
527
+ function openExecStream(baseURL, versionPrefix, patToken, sandboxID, options) {
528
+ const wsURL = baseURL.replace(/^http/, "ws") + `${versionPrefix}/sandboxes/${encodeURIComponent(sandboxID)}/toolbox/process/exec/stream`;
502
529
  const WS = globalThis.WebSocket;
503
530
  if (!WS) {
504
531
  throw new Error("WebSocket is not available in this runtime — Node 22+ or a browser is required");
@@ -587,8 +614,7 @@ function openExecStream(baseURL, patToken, sandboxID, options) {
587
614
  failWith(detail !== "" ? `websocket error: ${detail}` : "websocket error");
588
615
  });
589
616
  const sendBinary = (data) => {
590
- const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
591
- ws.send(bytes);
617
+ ws.send(toWebSocketBinaryFrame(data));
592
618
  };
593
619
  return {
594
620
  write: sendBinary,
@@ -604,8 +630,8 @@ function openExecStream(baseURL, patToken, sandboxID, options) {
604
630
  done,
605
631
  };
606
632
  }
607
- function openSessionAttach(baseURL, patToken, sandboxID, sessionID, options) {
608
- const wsURL = baseURL.replace(/^http/, "ws") + `/v1/sandboxes/${encodeURIComponent(sandboxID)}/sessions/${encodeURIComponent(sessionID)}/attach`;
633
+ function openSessionAttach(baseURL, versionPrefix, patToken, sandboxID, sessionID, options) {
634
+ const wsURL = baseURL.replace(/^http/, "ws") + `${versionPrefix}/sandboxes/${encodeURIComponent(sandboxID)}/sessions/${encodeURIComponent(sessionID)}/attach`;
609
635
  const WS = globalThis.WebSocket;
610
636
  if (!WS) {
611
637
  throw new Error("WebSocket is not available in this runtime — Node 22+ or a browser is required");
@@ -687,8 +713,7 @@ function openSessionAttach(baseURL, patToken, sandboxID, sessionID, options) {
687
713
  failWith(detail !== "" ? `websocket error: ${detail}` : "websocket error");
688
714
  });
689
715
  const sendBinary = (data) => {
690
- const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
691
- ws.send(bytes);
716
+ ws.send(toWebSocketBinaryFrame(data));
692
717
  };
693
718
  return {
694
719
  write: sendBinary,
package/dist/types.d.ts CHANGED
@@ -129,6 +129,35 @@ export interface ExposedPort {
129
129
  publicURL: string;
130
130
  createdAt: string;
131
131
  }
132
+ /**
133
+ * Wire protocol an exposure publishes through:
134
+ * - "http": Caddy HTTP reverse proxy at https://<id>-<port>.<domain>.
135
+ * - "tcp": raw caddy-l4 listener on a parent-host port.
136
+ * - "tls": caddy-l4 TLS-SNI route on the shared :443 listener.
137
+ */
138
+ export type ExposeProtocol = "http" | "tcp" | "tls";
139
+ export interface ExposePortOptions {
140
+ /** Defaults to "http" when omitted. */
141
+ protocol?: ExposeProtocol;
142
+ }
143
+ /**
144
+ * Discriminated result from `exposePort`. Branch on `protocol` to access the
145
+ * fields that are meaningful for the chosen wire protocol — only the raw-TCP
146
+ * variant carries `host` and `hostPort`, which are what native protocol
147
+ * clients (psql, redis-cli, mysql, mongosh, …) need to dial.
148
+ */
149
+ export type ExposeResult = {
150
+ protocol: "http";
151
+ url: string;
152
+ } | {
153
+ protocol: "tcp";
154
+ url: string;
155
+ host: string;
156
+ hostPort: number;
157
+ } | {
158
+ protocol: "tls";
159
+ url: string;
160
+ };
132
161
  export interface Sandbox {
133
162
  id: string;
134
163
  image: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aerol-ai/aerolvm-sdk",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -20,8 +20,8 @@
20
20
  "test": "tsc -p tsconfig.test.json --outDir dist-test && node --test dist-test/*.test.js"
21
21
  },
22
22
  "devDependencies": {
23
- "@types/node": "^22.15.17",
24
- "typescript": "^5.8.3",
23
+ "@types/node": "^25.6.2",
24
+ "typescript": "^6.0.3",
25
25
  "tsx": "^4.21.0"
26
26
  }
27
27
  }