@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 +16 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/sandbox.d.ts +62 -3
- package/dist/sandbox.js +146 -3
- package/package.json +6 -1
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
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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.
|
|
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
|
}
|