@watasu/sdk 0.1.1 → 0.1.4
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 +29 -0
- package/dist/connectionConfig.d.ts +1 -0
- package/dist/connectionConfig.js +4 -1
- package/dist/sandbox.d.ts +6 -8
- package/dist/sandbox.js +55 -28
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
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/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,7 +21,8 @@ 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;
|
|
@@ -48,6 +43,7 @@ export declare class Sandbox {
|
|
|
48
43
|
};
|
|
49
44
|
private readonly config;
|
|
50
45
|
private readonly control;
|
|
46
|
+
private readonly envs;
|
|
51
47
|
private dataPlane;
|
|
52
48
|
private sandbox;
|
|
53
49
|
constructor(opts: {
|
|
@@ -68,6 +64,8 @@ export declare class Sandbox {
|
|
|
68
64
|
static kill(sandboxId: string, opts?: ConnectionOpts): Promise<boolean>;
|
|
69
65
|
/** Destroy this sandbox. */
|
|
70
66
|
kill(): Promise<boolean>;
|
|
67
|
+
/** Check if this sandbox is in a runtime-active lifecycle state. */
|
|
68
|
+
isRunning(opts?: Pick<ConnectionOpts, 'requestTimeoutMs'>): Promise<boolean>;
|
|
71
69
|
/** Set a sandbox's lifetime by id. */
|
|
72
70
|
static setTimeout(sandboxId: string, timeoutMs: number, opts?: ConnectionOpts): Promise<void>;
|
|
73
71
|
/** Set this sandbox's lifetime. */
|
|
@@ -81,7 +79,7 @@ export declare class Sandbox {
|
|
|
81
79
|
team?: string;
|
|
82
80
|
}): Promise<SandboxInfo[]>;
|
|
83
81
|
/** Return the public hostname for an exposed sandbox port. */
|
|
84
|
-
getHost(port: number):
|
|
82
|
+
getHost(port: number): string;
|
|
85
83
|
pause(): never;
|
|
86
84
|
resume(): never;
|
|
87
85
|
createSnapshot(): never;
|
package/dist/sandbox.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
6
|
/** Running Watasu sandbox with ready `files` and `commands` helpers. */
|
|
7
7
|
export class Sandbox {
|
|
@@ -14,17 +14,19 @@ export class Sandbox {
|
|
|
14
14
|
git = { clone: () => unsupported('sandbox.git') };
|
|
15
15
|
config;
|
|
16
16
|
control;
|
|
17
|
+
envs;
|
|
17
18
|
dataPlane;
|
|
18
19
|
sandbox;
|
|
19
20
|
constructor(opts) {
|
|
20
21
|
this.sandboxId = String(opts.sandboxId);
|
|
21
22
|
this.config = opts.connectionConfig;
|
|
22
23
|
this.control = opts.control ?? new ControlClient(this.config);
|
|
24
|
+
this.envs = opts.envs ?? {};
|
|
23
25
|
this.sandbox = opts.sandbox ?? {};
|
|
24
26
|
const dataPlane = dataPlaneFromSession(opts.session, this.config);
|
|
25
27
|
this.dataPlane = dataPlane;
|
|
26
28
|
this.files = new Filesystem(dataPlane);
|
|
27
|
-
this.commands = new Commands(dataPlane, this.config,
|
|
29
|
+
this.commands = new Commands(dataPlane, this.config, this.envs);
|
|
28
30
|
}
|
|
29
31
|
/** Create a sandbox and return it only after the API supplies a data-plane session. */
|
|
30
32
|
static async create(templateOrOpts, opts = {}) {
|
|
@@ -39,20 +41,16 @@ export class Sandbox {
|
|
|
39
41
|
const config = new ConnectionConfig(sandboxOpts);
|
|
40
42
|
const control = new ControlClient(config);
|
|
41
43
|
const sandboxPayload = {
|
|
42
|
-
template,
|
|
43
|
-
|
|
44
|
+
template_id: template,
|
|
45
|
+
timeout: Math.ceil((sandboxOpts.timeoutMs ?? 300_000) / 1000),
|
|
44
46
|
metadata: sandboxOpts.metadata ?? {},
|
|
47
|
+
env_vars: sandboxOpts.envs ?? {},
|
|
48
|
+
secure: sandboxOpts.secure ?? true,
|
|
45
49
|
allow_internet_access: sandboxOpts.allowInternetAccess ?? true,
|
|
46
50
|
};
|
|
47
|
-
putIfPresent(sandboxPayload, 'template_version_id', sandboxOpts.templateVersionId);
|
|
48
51
|
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
52
|
const response = await control.post('/sandboxes', {
|
|
55
|
-
json:
|
|
53
|
+
json: sandboxPayload,
|
|
56
54
|
requestTimeoutMs: sessionOperationRequestTimeout(config, sandboxOpts),
|
|
57
55
|
});
|
|
58
56
|
const sandbox = record(response.sandbox ?? response);
|
|
@@ -74,7 +72,7 @@ export class Sandbox {
|
|
|
74
72
|
const control = new ControlClient(config);
|
|
75
73
|
const info = await control.get(`/sandboxes/${sandboxId}`);
|
|
76
74
|
const response = await control.post(`/sandboxes/${sandboxId}/connect`, {
|
|
77
|
-
json:
|
|
75
|
+
json: opts.timeoutMs ? { timeout: Math.ceil(opts.timeoutMs / 1000) } : {},
|
|
78
76
|
requestTimeoutMs: sessionOperationRequestTimeout(config, opts),
|
|
79
77
|
});
|
|
80
78
|
return new Sandbox({
|
|
@@ -88,14 +86,14 @@ export class Sandbox {
|
|
|
88
86
|
/** Refresh this sandbox's data-plane session in place. */
|
|
89
87
|
async connect(opts = {}) {
|
|
90
88
|
const response = await this.control.post(`/sandboxes/${this.sandboxId}/connect`, {
|
|
91
|
-
json:
|
|
89
|
+
json: opts.timeoutMs ? { timeout: Math.ceil(opts.timeoutMs / 1000) } : {},
|
|
92
90
|
requestTimeoutMs: sessionOperationRequestTimeout(this.config, opts),
|
|
93
91
|
});
|
|
94
92
|
this.sandbox = record(response.sandbox ?? this.sandbox);
|
|
95
93
|
const dataPlane = dataPlaneFromSession(response.session, this.config);
|
|
96
94
|
this.dataPlane = dataPlane;
|
|
97
95
|
this.files = new Filesystem(dataPlane);
|
|
98
|
-
this.commands = new Commands(dataPlane, this.config);
|
|
96
|
+
this.commands = new Commands(dataPlane, this.config, this.envs);
|
|
99
97
|
return this;
|
|
100
98
|
}
|
|
101
99
|
/** Destroy a sandbox by id. */
|
|
@@ -109,17 +107,32 @@ export class Sandbox {
|
|
|
109
107
|
await this.control.delete(`/sandboxes/${this.sandboxId}`);
|
|
110
108
|
return true;
|
|
111
109
|
}
|
|
110
|
+
/** Check if this sandbox is in a runtime-active lifecycle state. */
|
|
111
|
+
async isRunning(opts = {}) {
|
|
112
|
+
try {
|
|
113
|
+
const payload = await this.control.get(`/sandboxes/${this.sandboxId}`, {
|
|
114
|
+
requestTimeoutMs: opts.requestTimeoutMs,
|
|
115
|
+
});
|
|
116
|
+
const item = record(payload.sandbox ?? payload);
|
|
117
|
+
return ['creating', 'ready', 'checkpointing', 'restoring', 'stopping'].includes(String(item.state ?? ''));
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
if (error instanceof NotFoundError)
|
|
121
|
+
return false;
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
112
125
|
/** Set a sandbox's lifetime by id. */
|
|
113
126
|
static async setTimeout(sandboxId, timeoutMs, opts = {}) {
|
|
114
127
|
const control = new ControlClient(new ConnectionConfig(opts));
|
|
115
|
-
await control.
|
|
116
|
-
json: {
|
|
128
|
+
await control.post(`/sandboxes/${sandboxId}/timeout`, {
|
|
129
|
+
json: { timeout: Math.ceil(timeoutMs / 1000) },
|
|
117
130
|
});
|
|
118
131
|
}
|
|
119
132
|
/** Set this sandbox's lifetime. */
|
|
120
133
|
async setTimeout(timeoutMs) {
|
|
121
|
-
await this.control.
|
|
122
|
-
json: {
|
|
134
|
+
await this.control.post(`/sandboxes/${this.sandboxId}/timeout`, {
|
|
135
|
+
json: { timeout: Math.ceil(timeoutMs / 1000) },
|
|
123
136
|
});
|
|
124
137
|
}
|
|
125
138
|
/** Fetch control-plane metadata for a sandbox by id. */
|
|
@@ -141,13 +154,10 @@ export class Sandbox {
|
|
|
141
154
|
return sandboxes.map((item) => sandboxInfo(record(item)));
|
|
142
155
|
}
|
|
143
156
|
/** 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;
|
|
157
|
+
getHost(port) {
|
|
158
|
+
const routeToken = this.sandbox.route_token ??
|
|
159
|
+
this.sandbox.routeToken ??
|
|
160
|
+
routeTokenFromDataPlaneUrl(this.dataPlane.baseUrl, this.config.dataPlaneDomain);
|
|
151
161
|
if (typeof routeToken !== 'string')
|
|
152
162
|
throw new SandboxError('port response did not include host or url');
|
|
153
163
|
return `p${port}-${routeToken}.sandbox.${this.config.dataPlaneDomain}`;
|
|
@@ -177,13 +187,22 @@ function sessionOperationRequestTimeout(config, opts) {
|
|
|
177
187
|
function sandboxInfo(payload) {
|
|
178
188
|
return {
|
|
179
189
|
sandboxId: String(payload.id ?? payload.sandbox_id ?? ''),
|
|
180
|
-
|
|
190
|
+
templateId: typeof payload.template_id === 'string' ? payload.template_id : templateSlug(payload.template),
|
|
191
|
+
name: typeof payload.name === 'string' ? payload.name : undefined,
|
|
181
192
|
state: typeof payload.state === 'string' ? payload.state : undefined,
|
|
182
193
|
metadata: recordOfStrings(payload.metadata),
|
|
183
|
-
startedAt: typeof payload.
|
|
184
|
-
|
|
194
|
+
startedAt: typeof payload.started_at === 'string'
|
|
195
|
+
? payload.started_at
|
|
196
|
+
: typeof payload.created_at === 'string' ? payload.created_at : undefined,
|
|
197
|
+
endAt: typeof payload.end_at === 'string'
|
|
198
|
+
? payload.end_at
|
|
199
|
+
: typeof payload.deadline_at === 'string' ? payload.deadline_at : undefined,
|
|
185
200
|
};
|
|
186
201
|
}
|
|
202
|
+
function templateSlug(value) {
|
|
203
|
+
const template = record(value);
|
|
204
|
+
return typeof template.slug === 'string' ? template.slug : undefined;
|
|
205
|
+
}
|
|
187
206
|
function putIfPresent(target, key, value) {
|
|
188
207
|
if (value !== undefined && value !== null)
|
|
189
208
|
target[key] = value;
|
|
@@ -201,3 +220,11 @@ function hostOnly(value) {
|
|
|
201
220
|
return new URL(value).host;
|
|
202
221
|
return value.split('/')[0];
|
|
203
222
|
}
|
|
223
|
+
function routeTokenFromDataPlaneUrl(value, dataPlaneDomain) {
|
|
224
|
+
const host = hostOnly(value);
|
|
225
|
+
const suffix = `.sandbox.${dataPlaneDomain}`;
|
|
226
|
+
if (!host.endsWith(suffix))
|
|
227
|
+
return undefined;
|
|
228
|
+
const token = host.slice(0, -suffix.length);
|
|
229
|
+
return token || undefined;
|
|
230
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@watasu/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
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
|
},
|