@watasu/sdk 0.1.4 → 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 CHANGED
@@ -26,4 +26,20 @@ await sbx.kill()
26
26
  `Sandbox.create` and `Sandbox.connect` return only after the Watasu API supplies
27
27
  a usable data-plane session. The SDK does not poll sandbox readiness.
28
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
+
29
45
  The SDK is ESM-first and ships TypeScript declarations.
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
@@ -28,6 +28,45 @@ export interface SandboxInfo {
28
28
  startedAt?: string;
29
29
  endAt?: string;
30
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
+ }
31
70
  /** Running Watasu sandbox with ready `files` and `commands` helpers. */
32
71
  export declare class Sandbox {
33
72
  /** Default template slug used when create is called without a template. */
@@ -62,6 +101,16 @@ export declare class Sandbox {
62
101
  connect(opts?: SandboxConnectOpts): Promise<this>;
63
102
  /** Destroy a sandbox by id. */
64
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;
65
114
  /** Destroy this sandbox. */
66
115
  kill(): Promise<boolean>;
67
116
  /** Check if this sandbox is in a runtime-active lifecycle state. */
@@ -74,15 +123,25 @@ export declare class Sandbox {
74
123
  static getInfo(sandboxId: string, opts?: ConnectionOpts): Promise<SandboxInfo>;
75
124
  /** Fetch the latest control-plane metadata for this sandbox. */
76
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>;
77
136
  /** List sandboxes visible to the configured API key. */
78
137
  static list(opts?: ConnectionOpts & {
79
138
  team?: string;
80
139
  }): Promise<SandboxInfo[]>;
81
140
  /** Return the public hostname for an exposed sandbox port. */
82
141
  getHost(port: number): string;
142
+ updateNetwork(..._args: unknown[]): never;
83
143
  pause(): never;
144
+ betaPause(): never;
84
145
  resume(): never;
85
- createSnapshot(): never;
86
- checkpoint(): never;
87
- restore(): never;
146
+ private configOptions;
88
147
  }
package/dist/sandbox.js CHANGED
@@ -3,6 +3,22 @@ import { ConnectionConfig, SESSION_OPERATION_REQUEST_TIMEOUT_MS } from './connec
3
3
  import { DataPlaneClient, ControlClient } from './transport.js';
4
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. */
@@ -102,6 +118,42 @@ export class Sandbox {
102
118
  await control.delete(`/sandboxes/${sandboxId}`);
103
119
  return true;
104
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
+ }
105
157
  /** Destroy this sandbox. */
106
158
  async kill() {
107
159
  await this.control.delete(`/sandboxes/${this.sandboxId}`);
@@ -146,6 +198,41 @@ export class Sandbox {
146
198
  const payload = await this.control.get(`/sandboxes/${this.sandboxId}`);
147
199
  return sandboxInfo(record(payload.sandbox ?? payload));
148
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
+ }
149
236
  /** List sandboxes visible to the configured API key. */
150
237
  static async list(opts = {}) {
151
238
  const control = new ControlClient(new ConnectionConfig(opts));
@@ -162,11 +249,18 @@ export class Sandbox {
162
249
  throw new SandboxError('port response did not include host or url');
163
250
  return `p${port}-${routeToken}.sandbox.${this.config.dataPlaneDomain}`;
164
251
  }
252
+ updateNetwork(..._args) { unsupported('Sandbox.updateNetwork'); }
165
253
  pause() { unsupported('Sandbox.pause'); }
254
+ betaPause() { unsupported('Sandbox.betaPause'); }
166
255
  resume() { unsupported('Sandbox.resume'); }
167
- createSnapshot() { unsupported('Sandbox.createSnapshot'); }
168
- checkpoint() { unsupported('Sandbox.checkpoint'); }
169
- 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
+ }
170
264
  }
171
265
  function dataPlaneFromSession(session, config) {
172
266
  const item = record(session);
@@ -199,6 +293,55 @@ function sandboxInfo(payload) {
199
293
  : typeof payload.deadline_at === 'string' ? payload.deadline_at : undefined,
200
294
  };
201
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,
310
+ };
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
+ }
202
345
  function templateSlug(value) {
203
346
  const template = record(value);
204
347
  return typeof template.slug === 'string' ? template.slug : undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@watasu/sdk",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "description": "TypeScript SDK for Watasu",
@@ -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
  }