@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 +45 -0
- package/dist/connectionConfig.d.ts +1 -0
- package/dist/connectionConfig.js +4 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/sandbox.d.ts +68 -11
- package/dist/sandbox.js +201 -31
- package/package.json +7 -2
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;
|
package/dist/connectionConfig.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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):
|
|
141
|
+
getHost(port: number): string;
|
|
142
|
+
updateNetwork(..._args: unknown[]): never;
|
|
85
143
|
pause(): never;
|
|
144
|
+
betaPause(): never;
|
|
86
145
|
resume(): never;
|
|
87
|
-
|
|
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,
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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.
|
|
116
|
-
json: {
|
|
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.
|
|
122
|
-
json: {
|
|
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
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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.
|
|
184
|
-
|
|
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.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
|
-
"description": "TypeScript SDK for Watasu
|
|
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
|
}
|