@watasu/sdk 0.1.1 → 0.1.5

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/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # Watasu TypeScript SDK
2
+
3
+ TypeScript SDK for Watasu.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @watasu/sdk
9
+ ```
10
+
11
+ Set `WATASU_API_KEY` before using the SDK.
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import { Sandbox } from '@watasu/sdk'
17
+
18
+ const sbx = await Sandbox.create()
19
+ await sbx.files.write('/home/user/a.js', 'console.log(2 + 2)')
20
+ const result = await sbx.commands.run('node /home/user/a.js')
21
+ console.log(result.stdout)
22
+ console.log(await sbx.isRunning())
23
+ await sbx.kill()
24
+ ```
25
+
26
+ `Sandbox.create` and `Sandbox.connect` return only after the Watasu API supplies
27
+ a usable data-plane session. The SDK does not poll sandbox readiness.
28
+
29
+ ## Metrics And Snapshots
30
+
31
+ ```ts
32
+ import { Sandbox } from '@watasu/sdk'
33
+
34
+ const sbx = await Sandbox.create()
35
+ const metrics = await sbx.getMetrics()
36
+ const snapshot = await sbx.createSnapshot({ name: 'ready' })
37
+ const snapshots = await sbx.listSnapshots().nextItems()
38
+ const restored = await sbx.restore({ snapshotId: snapshot.snapshotId })
39
+ await sbx.kill()
40
+ ```
41
+
42
+ Watasu snapshots are backed by sandbox checkpoints. Use the returned
43
+ `snapshotId` when restoring from a checkpoint.
44
+
45
+ The SDK is ESM-first and ships TypeScript declarations.
@@ -3,6 +3,7 @@ export declare const SESSION_OPERATION_REQUEST_TIMEOUT_MS = 150000;
3
3
  /** Connection options accepted by Watasu SDK entrypoints. */
4
4
  export interface ConnectionOpts {
5
5
  apiKey?: string;
6
+ accessToken?: string;
6
7
  domain?: string;
7
8
  apiUrl?: string;
8
9
  dataPlaneDomain?: string;
@@ -11,7 +11,10 @@ export class ConnectionConfig {
11
11
  proxy;
12
12
  constructor(opts = {}) {
13
13
  const env = typeof process !== 'undefined' ? process.env : {};
14
- this.apiKey = opts.apiKey ?? env.WATASU_API_KEY;
14
+ this.apiKey =
15
+ opts.apiKey ??
16
+ opts.accessToken ??
17
+ env.WATASU_API_KEY;
15
18
  this.domain = opts.domain ?? env.WATASU_DOMAIN ?? 'watasu.io';
16
19
  this.apiUrl =
17
20
  opts.apiUrl ?? env.WATASU_API_URL ?? `https://api.${this.domain}/v1`;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { ApiError, AuthenticationError, FileNotFoundError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, NotImplementedError, RateLimitError, SandboxError, TimeoutError, } from './errors.js';
2
2
  export { ConnectionConfig, KEEPALIVE_PING_INTERVAL_SEC } from './connectionConfig.js';
3
- export { Sandbox } from './sandbox.js';
4
- export type { SandboxCreateOpts, SandboxConnectOpts, SandboxInfo } from './sandbox.js';
3
+ export { Sandbox, SnapshotPaginator } from './sandbox.js';
4
+ export type { CreateSnapshotOpts, RestoreSnapshotOpts, SandboxCreateOpts, SandboxConnectOpts, SandboxInfo, SandboxMetrics, SnapshotInfo, } from './sandbox.js';
5
5
  export { CommandExitError, CommandHandle, Commands } from './commands.js';
6
6
  export type { CommandResult, CommandStartOpts, ProcessInfo } from './commands.js';
7
7
  export { FileType, Filesystem } from './filesystem.js';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { ApiError, AuthenticationError, FileNotFoundError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, NotImplementedError, RateLimitError, SandboxError, TimeoutError, } from './errors.js';
2
2
  export { ConnectionConfig, KEEPALIVE_PING_INTERVAL_SEC } from './connectionConfig.js';
3
- export { Sandbox } from './sandbox.js';
3
+ export { Sandbox, SnapshotPaginator } from './sandbox.js';
4
4
  export { CommandExitError, CommandHandle, Commands } from './commands.js';
5
5
  export { FileType, Filesystem } from './filesystem.js';
6
6
  export { ProcessSocket, base64DecodeText, base64Encode } from './processSocket.js';
package/dist/sandbox.d.ts CHANGED
@@ -11,13 +11,7 @@ export interface SandboxCreateOpts extends ConnectionOpts {
11
11
  envs?: Record<string, string>;
12
12
  secure?: boolean;
13
13
  allowInternetAccess?: boolean;
14
- templateVersionId?: number | string;
15
14
  team?: string;
16
- cpu?: number;
17
- memoryMb?: number;
18
- networkClass?: string;
19
- allowPackageRegistryAccess?: boolean;
20
- exposedPorts?: unknown[];
21
15
  mcp?: unknown;
22
16
  volumeMounts?: unknown;
23
17
  }
@@ -27,12 +21,52 @@ export interface SandboxConnectOpts extends ConnectionOpts {
27
21
  }
28
22
  export interface SandboxInfo {
29
23
  sandboxId: string;
30
- templateVersionId?: number;
24
+ templateId?: string;
25
+ name?: string;
31
26
  state?: string;
32
27
  metadata: Record<string, string>;
33
28
  startedAt?: string;
34
29
  endAt?: string;
35
30
  }
31
+ export interface SandboxMetrics {
32
+ sandboxId?: string;
33
+ state?: string;
34
+ node?: string;
35
+ backend?: string;
36
+ cpuCount?: number;
37
+ memoryMb?: number;
38
+ raw: Record<string, unknown>;
39
+ }
40
+ export interface SnapshotInfo {
41
+ snapshotId: string;
42
+ sandboxId?: string;
43
+ name?: string;
44
+ status?: string;
45
+ sizeBytes?: number;
46
+ createdAt?: string;
47
+ expiresAt?: string;
48
+ raw: Record<string, unknown>;
49
+ }
50
+ export interface CreateSnapshotOpts extends ConnectionOpts {
51
+ name?: string;
52
+ metadata?: Record<string, string>;
53
+ expiresAt?: string;
54
+ quiesceMode?: string;
55
+ }
56
+ export interface RestoreSnapshotOpts extends ConnectionOpts {
57
+ checkpointId?: string | number;
58
+ snapshotId?: string | number;
59
+ timeout?: number;
60
+ timeoutMs?: number;
61
+ }
62
+ export declare class SnapshotPaginator {
63
+ private readonly loadItems;
64
+ private consumed;
65
+ hasNext: boolean;
66
+ nextToken: string | undefined;
67
+ constructor(loadItems: () => Promise<SnapshotInfo[]>);
68
+ nextItems(): Promise<SnapshotInfo[]>;
69
+ }
36
70
  /** Running Watasu sandbox with ready `files` and `commands` helpers. */
37
71
  export declare class Sandbox {
38
72
  /** Default template slug used when create is called without a template. */
@@ -48,6 +82,7 @@ export declare class Sandbox {
48
82
  };
49
83
  private readonly config;
50
84
  private readonly control;
85
+ private readonly envs;
51
86
  private dataPlane;
52
87
  private sandbox;
53
88
  constructor(opts: {
@@ -66,8 +101,20 @@ export declare class Sandbox {
66
101
  connect(opts?: SandboxConnectOpts): Promise<this>;
67
102
  /** Destroy a sandbox by id. */
68
103
  static kill(sandboxId: string, opts?: ConnectionOpts): Promise<boolean>;
104
+ /** Fetch sandbox metrics by id. */
105
+ static getMetrics(sandboxId: string, opts?: ConnectionOpts): Promise<SandboxMetrics[]>;
106
+ /** Deprecated alias for `getInfo`. */
107
+ static getFullInfo(sandboxId: string, opts?: ConnectionOpts): Promise<SandboxInfo>;
108
+ /** Create a Watasu checkpoint using snapshot naming. */
109
+ static createSnapshot(sandboxId: string, opts?: CreateSnapshotOpts): Promise<SnapshotInfo>;
110
+ /** List checkpoints for one sandbox using snapshot naming. */
111
+ static listSnapshots(sandboxId: string, opts?: ConnectionOpts): SnapshotPaginator;
112
+ /** Snapshot deletion is not backed by a Watasu checkpoint delete API yet. */
113
+ static deleteSnapshot(..._args: unknown[]): never;
69
114
  /** Destroy this sandbox. */
70
115
  kill(): Promise<boolean>;
116
+ /** Check if this sandbox is in a runtime-active lifecycle state. */
117
+ isRunning(opts?: Pick<ConnectionOpts, 'requestTimeoutMs'>): Promise<boolean>;
71
118
  /** Set a sandbox's lifetime by id. */
72
119
  static setTimeout(sandboxId: string, timeoutMs: number, opts?: ConnectionOpts): Promise<void>;
73
120
  /** Set this sandbox's lifetime. */
@@ -76,15 +123,25 @@ export declare class Sandbox {
76
123
  static getInfo(sandboxId: string, opts?: ConnectionOpts): Promise<SandboxInfo>;
77
124
  /** Fetch the latest control-plane metadata for this sandbox. */
78
125
  getInfo(): Promise<SandboxInfo>;
126
+ /** Fetch latest sandbox metrics. */
127
+ getMetrics(opts?: ConnectionOpts): Promise<SandboxMetrics[]>;
128
+ /** Create a Watasu checkpoint using snapshot naming. */
129
+ createSnapshot(opts?: CreateSnapshotOpts): Promise<SnapshotInfo>;
130
+ /** Watasu-native alias for `createSnapshot`. */
131
+ checkpoint(opts?: CreateSnapshotOpts): Promise<SnapshotInfo>;
132
+ /** List checkpoints for this sandbox using snapshot naming. */
133
+ listSnapshots(opts?: ConnectionOpts): SnapshotPaginator;
134
+ /** Restore a checkpoint into a new sandbox and return its control-plane info. */
135
+ restore(opts?: RestoreSnapshotOpts | string | number): Promise<SandboxInfo>;
79
136
  /** List sandboxes visible to the configured API key. */
80
137
  static list(opts?: ConnectionOpts & {
81
138
  team?: string;
82
139
  }): Promise<SandboxInfo[]>;
83
140
  /** Return the public hostname for an exposed sandbox port. */
84
- getHost(port: number): Promise<string>;
141
+ getHost(port: number): string;
142
+ updateNetwork(..._args: unknown[]): never;
85
143
  pause(): never;
144
+ betaPause(): never;
86
145
  resume(): never;
87
- createSnapshot(): never;
88
- checkpoint(): never;
89
- restore(): never;
146
+ private configOptions;
90
147
  }
package/dist/sandbox.js CHANGED
@@ -1,8 +1,24 @@
1
1
  import { Commands } from './commands.js';
2
2
  import { ConnectionConfig, SESSION_OPERATION_REQUEST_TIMEOUT_MS } from './connectionConfig.js';
3
3
  import { DataPlaneClient, ControlClient } from './transport.js';
4
- import { SandboxError, unsupported } from './errors.js';
4
+ import { NotFoundError, SandboxError, unsupported } from './errors.js';
5
5
  import { Filesystem } from './filesystem.js';
6
+ export class SnapshotPaginator {
7
+ loadItems;
8
+ consumed = false;
9
+ hasNext = true;
10
+ nextToken;
11
+ constructor(loadItems) {
12
+ this.loadItems = loadItems;
13
+ }
14
+ async nextItems() {
15
+ if (this.consumed)
16
+ throw new SandboxError('No more snapshots to fetch');
17
+ this.consumed = true;
18
+ this.hasNext = false;
19
+ return this.loadItems();
20
+ }
21
+ }
6
22
  /** Running Watasu sandbox with ready `files` and `commands` helpers. */
7
23
  export class Sandbox {
8
24
  /** Default template slug used when create is called without a template. */
@@ -14,17 +30,19 @@ export class Sandbox {
14
30
  git = { clone: () => unsupported('sandbox.git') };
15
31
  config;
16
32
  control;
33
+ envs;
17
34
  dataPlane;
18
35
  sandbox;
19
36
  constructor(opts) {
20
37
  this.sandboxId = String(opts.sandboxId);
21
38
  this.config = opts.connectionConfig;
22
39
  this.control = opts.control ?? new ControlClient(this.config);
40
+ this.envs = opts.envs ?? {};
23
41
  this.sandbox = opts.sandbox ?? {};
24
42
  const dataPlane = dataPlaneFromSession(opts.session, this.config);
25
43
  this.dataPlane = dataPlane;
26
44
  this.files = new Filesystem(dataPlane);
27
- this.commands = new Commands(dataPlane, this.config, opts.envs ?? {});
45
+ this.commands = new Commands(dataPlane, this.config, this.envs);
28
46
  }
29
47
  /** Create a sandbox and return it only after the API supplies a data-plane session. */
30
48
  static async create(templateOrOpts, opts = {}) {
@@ -39,20 +57,16 @@ export class Sandbox {
39
57
  const config = new ConnectionConfig(sandboxOpts);
40
58
  const control = new ControlClient(config);
41
59
  const sandboxPayload = {
42
- template,
43
- timeout_seconds: Math.ceil((sandboxOpts.timeoutMs ?? 300_000) / 1000),
60
+ template_id: template,
61
+ timeout: Math.ceil((sandboxOpts.timeoutMs ?? 300_000) / 1000),
44
62
  metadata: sandboxOpts.metadata ?? {},
63
+ env_vars: sandboxOpts.envs ?? {},
64
+ secure: sandboxOpts.secure ?? true,
45
65
  allow_internet_access: sandboxOpts.allowInternetAccess ?? true,
46
66
  };
47
- putIfPresent(sandboxPayload, 'template_version_id', sandboxOpts.templateVersionId);
48
67
  putIfPresent(sandboxPayload, 'team', sandboxOpts.team);
49
- putIfPresent(sandboxPayload, 'cpu', sandboxOpts.cpu);
50
- putIfPresent(sandboxPayload, 'memory_mb', sandboxOpts.memoryMb);
51
- putIfPresent(sandboxPayload, 'network_class', sandboxOpts.networkClass);
52
- putIfPresent(sandboxPayload, 'allow_package_registry_access', sandboxOpts.allowPackageRegistryAccess);
53
- putIfPresent(sandboxPayload, 'exposed_ports', sandboxOpts.exposedPorts);
54
68
  const response = await control.post('/sandboxes', {
55
- json: { sandbox: sandboxPayload },
69
+ json: sandboxPayload,
56
70
  requestTimeoutMs: sessionOperationRequestTimeout(config, sandboxOpts),
57
71
  });
58
72
  const sandbox = record(response.sandbox ?? response);
@@ -74,7 +88,7 @@ export class Sandbox {
74
88
  const control = new ControlClient(config);
75
89
  const info = await control.get(`/sandboxes/${sandboxId}`);
76
90
  const response = await control.post(`/sandboxes/${sandboxId}/connect`, {
77
- json: { connect: opts.timeoutMs ? { timeout_seconds: Math.ceil(opts.timeoutMs / 1000) } : {} },
91
+ json: opts.timeoutMs ? { timeout: Math.ceil(opts.timeoutMs / 1000) } : {},
78
92
  requestTimeoutMs: sessionOperationRequestTimeout(config, opts),
79
93
  });
80
94
  return new Sandbox({
@@ -88,14 +102,14 @@ export class Sandbox {
88
102
  /** Refresh this sandbox's data-plane session in place. */
89
103
  async connect(opts = {}) {
90
104
  const response = await this.control.post(`/sandboxes/${this.sandboxId}/connect`, {
91
- json: { connect: opts.timeoutMs ? { timeout_seconds: Math.ceil(opts.timeoutMs / 1000) } : {} },
105
+ json: opts.timeoutMs ? { timeout: Math.ceil(opts.timeoutMs / 1000) } : {},
92
106
  requestTimeoutMs: sessionOperationRequestTimeout(this.config, opts),
93
107
  });
94
108
  this.sandbox = record(response.sandbox ?? this.sandbox);
95
109
  const dataPlane = dataPlaneFromSession(response.session, this.config);
96
110
  this.dataPlane = dataPlane;
97
111
  this.files = new Filesystem(dataPlane);
98
- this.commands = new Commands(dataPlane, this.config);
112
+ this.commands = new Commands(dataPlane, this.config, this.envs);
99
113
  return this;
100
114
  }
101
115
  /** Destroy a sandbox by id. */
@@ -104,22 +118,73 @@ export class Sandbox {
104
118
  await control.delete(`/sandboxes/${sandboxId}`);
105
119
  return true;
106
120
  }
121
+ /** Fetch sandbox metrics by id. */
122
+ static async getMetrics(sandboxId, opts = {}) {
123
+ const control = new ControlClient(new ConnectionConfig(opts));
124
+ const payload = await control.get(`/sandboxes/${sandboxId}/metrics`, {
125
+ requestTimeoutMs: opts.requestTimeoutMs,
126
+ });
127
+ return metricsList(payload.metrics ?? payload);
128
+ }
129
+ /** Deprecated alias for `getInfo`. */
130
+ static async getFullInfo(sandboxId, opts = {}) {
131
+ return this.getInfo(sandboxId, opts);
132
+ }
133
+ /** Create a Watasu checkpoint using snapshot naming. */
134
+ static async createSnapshot(sandboxId, opts = {}) {
135
+ const control = new ControlClient(new ConnectionConfig(opts));
136
+ const payload = await control.post(`/sandboxes/${sandboxId}/checkpoints`, {
137
+ json: snapshotPayload(opts),
138
+ requestTimeoutMs: opts.requestTimeoutMs,
139
+ });
140
+ return snapshotInfo(record(payload.sandbox_checkpoint ?? payload.snapshot ?? payload));
141
+ }
142
+ /** List checkpoints for one sandbox using snapshot naming. */
143
+ static listSnapshots(sandboxId, opts = {}) {
144
+ return new SnapshotPaginator(async () => {
145
+ const control = new ControlClient(new ConnectionConfig(opts));
146
+ const payload = await control.get(`/sandboxes/${sandboxId}/checkpoints`, {
147
+ requestTimeoutMs: opts.requestTimeoutMs,
148
+ });
149
+ const snapshots = Array.isArray(payload.sandbox_checkpoints) ? payload.sandbox_checkpoints : [];
150
+ return snapshots.map((item) => snapshotInfo(record(item)));
151
+ });
152
+ }
153
+ /** Snapshot deletion is not backed by a Watasu checkpoint delete API yet. */
154
+ static deleteSnapshot(..._args) {
155
+ unsupported('Sandbox.deleteSnapshot');
156
+ }
107
157
  /** Destroy this sandbox. */
108
158
  async kill() {
109
159
  await this.control.delete(`/sandboxes/${this.sandboxId}`);
110
160
  return true;
111
161
  }
162
+ /** Check if this sandbox is in a runtime-active lifecycle state. */
163
+ async isRunning(opts = {}) {
164
+ try {
165
+ const payload = await this.control.get(`/sandboxes/${this.sandboxId}`, {
166
+ requestTimeoutMs: opts.requestTimeoutMs,
167
+ });
168
+ const item = record(payload.sandbox ?? payload);
169
+ return ['creating', 'ready', 'checkpointing', 'restoring', 'stopping'].includes(String(item.state ?? ''));
170
+ }
171
+ catch (error) {
172
+ if (error instanceof NotFoundError)
173
+ return false;
174
+ throw error;
175
+ }
176
+ }
112
177
  /** Set a sandbox's lifetime by id. */
113
178
  static async setTimeout(sandboxId, timeoutMs, opts = {}) {
114
179
  const control = new ControlClient(new ConnectionConfig(opts));
115
- await control.patch(`/sandboxes/${sandboxId}`, {
116
- json: { sandbox: { timeout_seconds: Math.ceil(timeoutMs / 1000) } },
180
+ await control.post(`/sandboxes/${sandboxId}/timeout`, {
181
+ json: { timeout: Math.ceil(timeoutMs / 1000) },
117
182
  });
118
183
  }
119
184
  /** Set this sandbox's lifetime. */
120
185
  async setTimeout(timeoutMs) {
121
- await this.control.patch(`/sandboxes/${this.sandboxId}`, {
122
- json: { sandbox: { timeout_seconds: Math.ceil(timeoutMs / 1000) } },
186
+ await this.control.post(`/sandboxes/${this.sandboxId}/timeout`, {
187
+ json: { timeout: Math.ceil(timeoutMs / 1000) },
123
188
  });
124
189
  }
125
190
  /** Fetch control-plane metadata for a sandbox by id. */
@@ -133,6 +198,41 @@ export class Sandbox {
133
198
  const payload = await this.control.get(`/sandboxes/${this.sandboxId}`);
134
199
  return sandboxInfo(record(payload.sandbox ?? payload));
135
200
  }
201
+ /** Fetch latest sandbox metrics. */
202
+ async getMetrics(opts = {}) {
203
+ return Sandbox.getMetrics(this.sandboxId, { ...this.configOptions(), ...opts });
204
+ }
205
+ /** Create a Watasu checkpoint using snapshot naming. */
206
+ async createSnapshot(opts = {}) {
207
+ return Sandbox.createSnapshot(this.sandboxId, { ...this.configOptions(), ...opts });
208
+ }
209
+ /** Watasu-native alias for `createSnapshot`. */
210
+ async checkpoint(opts = {}) {
211
+ return this.createSnapshot(opts);
212
+ }
213
+ /** List checkpoints for this sandbox using snapshot naming. */
214
+ listSnapshots(opts = {}) {
215
+ return Sandbox.listSnapshots(this.sandboxId, { ...this.configOptions(), ...opts });
216
+ }
217
+ /** Restore a checkpoint into a new sandbox and return its control-plane info. */
218
+ async restore(opts = {}) {
219
+ const restoreOpts = typeof opts === 'string' || typeof opts === 'number'
220
+ ? { checkpointId: opts }
221
+ : opts;
222
+ const checkpointId = restoreOpts.checkpointId ?? restoreOpts.snapshotId;
223
+ if (checkpointId === undefined)
224
+ throw new SandboxError('checkpointId or snapshotId is required');
225
+ const payload = { checkpoint_id: checkpointId };
226
+ if (restoreOpts.timeout !== undefined)
227
+ payload.timeout_seconds = restoreOpts.timeout;
228
+ if (restoreOpts.timeoutMs !== undefined)
229
+ payload.timeout_seconds = Math.ceil(restoreOpts.timeoutMs / 1000);
230
+ const response = await this.control.post(`/sandboxes/${this.sandboxId}/restore`, {
231
+ json: payload,
232
+ requestTimeoutMs: restoreOpts.requestTimeoutMs,
233
+ });
234
+ return sandboxInfo(record(response.sandbox ?? response));
235
+ }
136
236
  /** List sandboxes visible to the configured API key. */
137
237
  static async list(opts = {}) {
138
238
  const control = new ControlClient(new ConnectionConfig(opts));
@@ -141,22 +241,26 @@ export class Sandbox {
141
241
  return sandboxes.map((item) => sandboxInfo(record(item)));
142
242
  }
143
243
  /** Return the public hostname for an exposed sandbox port. */
144
- async getHost(port) {
145
- const payload = await this.control.get(`/sandboxes/${this.sandboxId}/ports/${port}`);
146
- const portInfo = record(payload.sandbox_port ?? payload.port ?? payload);
147
- const value = portInfo.host ?? portInfo.url;
148
- if (typeof value === 'string')
149
- return hostOnly(value);
150
- const routeToken = this.sandbox.route_token;
244
+ getHost(port) {
245
+ const routeToken = this.sandbox.route_token ??
246
+ this.sandbox.routeToken ??
247
+ routeTokenFromDataPlaneUrl(this.dataPlane.baseUrl, this.config.dataPlaneDomain);
151
248
  if (typeof routeToken !== 'string')
152
249
  throw new SandboxError('port response did not include host or url');
153
250
  return `p${port}-${routeToken}.sandbox.${this.config.dataPlaneDomain}`;
154
251
  }
252
+ updateNetwork(..._args) { unsupported('Sandbox.updateNetwork'); }
155
253
  pause() { unsupported('Sandbox.pause'); }
254
+ betaPause() { unsupported('Sandbox.betaPause'); }
156
255
  resume() { unsupported('Sandbox.resume'); }
157
- createSnapshot() { unsupported('Sandbox.createSnapshot'); }
158
- checkpoint() { unsupported('Sandbox.checkpoint'); }
159
- restore() { unsupported('Sandbox.restore'); }
256
+ configOptions() {
257
+ return {
258
+ apiKey: this.config.apiKey,
259
+ apiUrl: this.config.apiUrl,
260
+ dataPlaneDomain: this.config.dataPlaneDomain,
261
+ requestTimeoutMs: this.config.requestTimeoutMs,
262
+ };
263
+ }
160
264
  }
161
265
  function dataPlaneFromSession(session, config) {
162
266
  const item = record(session);
@@ -177,13 +281,71 @@ function sessionOperationRequestTimeout(config, opts) {
177
281
  function sandboxInfo(payload) {
178
282
  return {
179
283
  sandboxId: String(payload.id ?? payload.sandbox_id ?? ''),
180
- templateVersionId: typeof payload.template_version_id === 'number' ? payload.template_version_id : undefined,
284
+ templateId: typeof payload.template_id === 'string' ? payload.template_id : templateSlug(payload.template),
285
+ name: typeof payload.name === 'string' ? payload.name : undefined,
181
286
  state: typeof payload.state === 'string' ? payload.state : undefined,
182
287
  metadata: recordOfStrings(payload.metadata),
183
- startedAt: typeof payload.created_at === 'string' ? payload.created_at : undefined,
184
- endAt: typeof payload.deadline_at === 'string' ? payload.deadline_at : undefined,
288
+ startedAt: typeof payload.started_at === 'string'
289
+ ? payload.started_at
290
+ : typeof payload.created_at === 'string' ? payload.created_at : undefined,
291
+ endAt: typeof payload.end_at === 'string'
292
+ ? payload.end_at
293
+ : typeof payload.deadline_at === 'string' ? payload.deadline_at : undefined,
294
+ };
295
+ }
296
+ function metricsList(value) {
297
+ if (Array.isArray(value))
298
+ return value.map((item) => metricsInfo(record(item)));
299
+ return [metricsInfo(record(value))];
300
+ }
301
+ function metricsInfo(value) {
302
+ return {
303
+ sandboxId: stringValue(value.sandbox_id ?? value.sandboxId),
304
+ state: stringValue(value.state),
305
+ node: stringValue(value.node),
306
+ backend: stringValue(value.backend),
307
+ cpuCount: numberValue(value.cpu_count ?? value.cpuCount),
308
+ memoryMb: numberValue(value.memory_mb ?? value.memoryMb),
309
+ raw: value,
185
310
  };
186
311
  }
312
+ function snapshotPayload(opts) {
313
+ const payload = {};
314
+ putIfPresent(payload, 'name', opts.name);
315
+ putIfPresent(payload, 'metadata', opts.metadata);
316
+ putIfPresent(payload, 'expires_at', opts.expiresAt);
317
+ putIfPresent(payload, 'quiesce_mode', opts.quiesceMode);
318
+ return payload;
319
+ }
320
+ function snapshotInfo(value) {
321
+ const id = value.snapshot_id ?? value.snapshotId ?? value.checkpoint_id ?? value.checkpointId ?? value.id;
322
+ if (id === undefined)
323
+ throw new SandboxError('snapshot response did not include id');
324
+ return {
325
+ snapshotId: String(id),
326
+ sandboxId: stringValue(value.sandbox_id ?? value.sandboxId),
327
+ name: stringValue(value.name),
328
+ status: stringValue(value.status),
329
+ sizeBytes: numberValue(value.size_bytes ?? value.sizeBytes),
330
+ createdAt: stringValue(value.created_at ?? value.createdAt),
331
+ expiresAt: stringValue(value.expires_at ?? value.expiresAt),
332
+ raw: value,
333
+ };
334
+ }
335
+ function stringValue(value) {
336
+ if (typeof value === 'string')
337
+ return value;
338
+ if (typeof value === 'number')
339
+ return String(value);
340
+ return undefined;
341
+ }
342
+ function numberValue(value) {
343
+ return typeof value === 'number' ? value : undefined;
344
+ }
345
+ function templateSlug(value) {
346
+ const template = record(value);
347
+ return typeof template.slug === 'string' ? template.slug : undefined;
348
+ }
187
349
  function putIfPresent(target, key, value) {
188
350
  if (value !== undefined && value !== null)
189
351
  target[key] = value;
@@ -201,3 +363,11 @@ function hostOnly(value) {
201
363
  return new URL(value).host;
202
364
  return value.split('/')[0];
203
365
  }
366
+ function routeTokenFromDataPlaneUrl(value, dataPlaneDomain) {
367
+ const host = hostOnly(value);
368
+ const suffix = `.sandbox.${dataPlaneDomain}`;
369
+ if (!host.endsWith(suffix))
370
+ return undefined;
371
+ const token = host.slice(0, -suffix.length);
372
+ return token || undefined;
373
+ }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@watasu/sdk",
3
- "version": "0.1.1",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "license": "MIT OR Apache-2.0",
6
- "description": "TypeScript SDK for Watasu sandboxes",
6
+ "description": "TypeScript SDK for Watasu",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -28,5 +28,10 @@
28
28
  "@types/node": "^24.0.3",
29
29
  "@types/ws": "^8.18.1",
30
30
  "typescript": "^5.7.0"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/watasuio/sdk.git",
35
+ "directory": "ts"
31
36
  }
32
37
  }