@watasu/sdk 0.1.30 → 0.1.50
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 +13 -7
- package/dist/codeInterpreter.d.ts +141 -16
- package/dist/codeInterpreter.js +74 -19
- package/dist/commands.d.ts +16 -8
- package/dist/commands.js +17 -16
- package/dist/connectionConfig.d.ts +25 -0
- package/dist/connectionConfig.js +49 -2
- package/dist/errors.d.ts +21 -0
- package/dist/errors.js +54 -4
- package/dist/filesystem.d.ts +20 -20
- package/dist/filesystem.js +51 -12
- package/dist/git.d.ts +6 -4
- package/dist/git.js +7 -3
- package/dist/index.d.ts +22 -10
- package/dist/index.js +7 -5
- package/dist/process.d.ts +1 -1
- package/dist/process.js +11 -7
- package/dist/processSocket.d.ts +17 -4
- package/dist/processSocket.js +112 -8
- package/dist/pty.d.ts +3 -3
- package/dist/pty.js +10 -8
- package/dist/sandbox.d.ts +44 -30
- package/dist/sandbox.js +103 -59
- package/dist/template.d.ts +39 -9
- package/dist/template.js +117 -31
- package/dist/terminal.d.ts +3 -3
- package/dist/terminal.js +7 -4
- package/dist/transport.d.ts +2 -0
- package/dist/transport.js +23 -6
- package/dist/volume.d.ts +15 -8
- package/dist/volume.js +28 -9
- package/package.json +2 -2
package/dist/template.js
CHANGED
|
@@ -1,8 +1,45 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { ConnectionConfig } from './connectionConfig.js';
|
|
4
|
-
import { InvalidArgumentError, NotFoundError, SandboxError,
|
|
4
|
+
import { InvalidArgumentError, NotFoundError, SandboxError, TemplateError } from './errors.js';
|
|
5
5
|
import { ControlClient, withQuery } from './transport.js';
|
|
6
|
+
export class LogEntry {
|
|
7
|
+
timestamp;
|
|
8
|
+
level;
|
|
9
|
+
message;
|
|
10
|
+
constructor(timestamp = new Date(), level = 'info', message = '') {
|
|
11
|
+
this.timestamp = timestamp;
|
|
12
|
+
this.level = level;
|
|
13
|
+
this.message = message;
|
|
14
|
+
}
|
|
15
|
+
toString() {
|
|
16
|
+
return `[${this.timestamp.toISOString()}] ${this.level}: ${this.message}`;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class LogEntryStart extends LogEntry {
|
|
20
|
+
constructor(timestamp = new Date(), message = 'Build started') {
|
|
21
|
+
super(timestamp, 'info', message);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export class LogEntryEnd extends LogEntry {
|
|
25
|
+
constructor(timestamp = new Date(), message = 'Build finished') {
|
|
26
|
+
super(timestamp, 'info', message);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function defaultBuildLogger(options = {}) {
|
|
30
|
+
const order = { debug: 10, info: 20, warn: 30, error: 40 };
|
|
31
|
+
const minLevel = options.minLevel ?? 'info';
|
|
32
|
+
return (entry) => {
|
|
33
|
+
if (order[entry.level] < order[minLevel])
|
|
34
|
+
return;
|
|
35
|
+
if (entry.level === 'error')
|
|
36
|
+
console.error(entry.toString());
|
|
37
|
+
else if (entry.level === 'warn')
|
|
38
|
+
console.warn(entry.toString());
|
|
39
|
+
else
|
|
40
|
+
console.log(entry.toString());
|
|
41
|
+
};
|
|
42
|
+
}
|
|
6
43
|
/** Ready-check command wrapper accepted by template builders. */
|
|
7
44
|
export class ReadyCmd {
|
|
8
45
|
cmd;
|
|
@@ -22,8 +59,6 @@ export function waitForPort(port) {
|
|
|
22
59
|
export function waitForURL(url, statusCode = 200) {
|
|
23
60
|
return new ReadyCmd(`curl -s -o /dev/null -w "%{http_code}" ${url} | grep -q "${statusCode}"`);
|
|
24
61
|
}
|
|
25
|
-
/** Alias for `waitForURL`. */
|
|
26
|
-
export const waitForUrl = waitForURL;
|
|
27
62
|
/** Return a ready check that waits for a process name. */
|
|
28
63
|
export function waitForProcess(processName) {
|
|
29
64
|
return new ReadyCmd(`pgrep ${processName} > /dev/null`);
|
|
@@ -40,6 +75,8 @@ export function waitForTimeout(timeout) {
|
|
|
40
75
|
/** Chainable template builder for Watasu package-spec template builds. */
|
|
41
76
|
export class TemplateBase {
|
|
42
77
|
base;
|
|
78
|
+
fromImageReference;
|
|
79
|
+
fromImageRegistry;
|
|
43
80
|
packages = {};
|
|
44
81
|
files = [];
|
|
45
82
|
setup = [];
|
|
@@ -57,10 +94,10 @@ export class TemplateBase {
|
|
|
57
94
|
}
|
|
58
95
|
static async build(template, nameOrOptions, options = {}) {
|
|
59
96
|
const { name, buildOptions } = normalizeBuildArguments(nameOrOptions, options);
|
|
60
|
-
buildOptions.onBuildLogs?.(
|
|
97
|
+
buildOptions.onBuildLogs?.(new LogEntryStart());
|
|
61
98
|
const data = await TemplateBase.buildInBackground(template, name, buildOptions);
|
|
62
99
|
await waitForBuildFinish(data, buildOptions);
|
|
63
|
-
buildOptions.onBuildLogs?.(
|
|
100
|
+
buildOptions.onBuildLogs?.(new LogEntryEnd());
|
|
64
101
|
return data;
|
|
65
102
|
}
|
|
66
103
|
static async buildInBackground(template, nameOrOptions, options = {}) {
|
|
@@ -135,42 +172,73 @@ export class TemplateBase {
|
|
|
135
172
|
static toDockerfile(template) {
|
|
136
173
|
return template.toDockerfile();
|
|
137
174
|
}
|
|
175
|
+
/** Request a Debian public base image. The Watasu API fails closed until OCI image import is enabled. */
|
|
138
176
|
fromDebianImage(_variant = 'stable') {
|
|
139
|
-
this.
|
|
140
|
-
return this;
|
|
177
|
+
return this.fromImage(`debian:${_variant}`);
|
|
141
178
|
}
|
|
179
|
+
/** Request an Ubuntu public base image. The Watasu API fails closed until OCI image import is enabled. */
|
|
142
180
|
fromUbuntuImage(_variant = 'latest') {
|
|
143
|
-
this.
|
|
144
|
-
return this;
|
|
181
|
+
return this.fromImage(`ubuntu:${_variant}`);
|
|
145
182
|
}
|
|
183
|
+
/** Request a Python public base image. The Watasu API fails closed until OCI image import is enabled. */
|
|
146
184
|
fromPythonImage(_version = '3') {
|
|
147
|
-
|
|
148
|
-
return this;
|
|
185
|
+
return this.fromImage(`python:${_version}`);
|
|
149
186
|
}
|
|
187
|
+
/** Request a Node.js public base image. The Watasu API fails closed until OCI image import is enabled. */
|
|
150
188
|
fromNodeImage(_variant = 'lts') {
|
|
151
|
-
|
|
152
|
-
return this;
|
|
189
|
+
return this.fromImage(`node:${_variant}`);
|
|
153
190
|
}
|
|
191
|
+
/** Request a Bun public base image. The Watasu API fails closed until OCI image import is enabled. */
|
|
154
192
|
fromBunImage(_variant = 'latest') {
|
|
155
|
-
|
|
156
|
-
return this;
|
|
193
|
+
return this.fromImage(`oven/bun:${_variant}`);
|
|
157
194
|
}
|
|
195
|
+
/** Start from the Watasu platform base template. */
|
|
158
196
|
fromBaseImage() {
|
|
159
|
-
this.
|
|
160
|
-
return this;
|
|
197
|
+
return this.fromTemplate('base');
|
|
161
198
|
}
|
|
162
|
-
|
|
163
|
-
|
|
199
|
+
/** Request a public container image base. The Watasu API fails closed until OCI image import is enabled. */
|
|
200
|
+
fromImage(baseImage, credentials) {
|
|
201
|
+
if (credentials && (!credentials.username || !credentials.password)) {
|
|
202
|
+
throw new InvalidArgumentError('Both username and password are required when providing registry credentials');
|
|
203
|
+
}
|
|
204
|
+
this.fromImageReference = baseImage;
|
|
205
|
+
this.base = undefined;
|
|
206
|
+
this.fromImageRegistry = credentials
|
|
207
|
+
? {
|
|
208
|
+
type: 'registry',
|
|
209
|
+
username: credentials.username,
|
|
210
|
+
password: credentials.password,
|
|
211
|
+
}
|
|
212
|
+
: undefined;
|
|
164
213
|
return this;
|
|
165
214
|
}
|
|
166
|
-
|
|
167
|
-
|
|
215
|
+
/** Request an AWS registry image base. The Watasu API fails closed until registry image import is enabled. */
|
|
216
|
+
fromAWSRegistry(image, credentials) {
|
|
217
|
+
this.fromImageReference = image;
|
|
218
|
+
this.base = undefined;
|
|
219
|
+
this.fromImageRegistry = {
|
|
220
|
+
type: 'aws',
|
|
221
|
+
aws_access_key_id: credentials.accessKeyId,
|
|
222
|
+
aws_secret_access_key: credentials.secretAccessKey,
|
|
223
|
+
aws_region: credentials.region,
|
|
224
|
+
};
|
|
225
|
+
return this;
|
|
168
226
|
}
|
|
169
|
-
|
|
170
|
-
|
|
227
|
+
/** Request a GCP registry image base. The Watasu API fails closed until registry image import is enabled. */
|
|
228
|
+
fromGCPRegistry(image, credentials) {
|
|
229
|
+
this.fromImageReference = image;
|
|
230
|
+
this.base = undefined;
|
|
231
|
+
this.fromImageRegistry = {
|
|
232
|
+
type: 'gcp',
|
|
233
|
+
service_account_json: credentials.serviceAccountJSON,
|
|
234
|
+
};
|
|
235
|
+
return this;
|
|
171
236
|
}
|
|
237
|
+
/** Start from a ready Watasu template slug, tag, or version id. */
|
|
172
238
|
fromTemplate(template) {
|
|
173
239
|
this.base = template;
|
|
240
|
+
this.fromImageReference = undefined;
|
|
241
|
+
this.fromImageRegistry = undefined;
|
|
174
242
|
return this;
|
|
175
243
|
}
|
|
176
244
|
fromDockerfile(dockerfileContentOrPath) {
|
|
@@ -286,6 +354,14 @@ export class TemplateBase {
|
|
|
286
354
|
this.readyCmd = readyCommandText(readyCommand);
|
|
287
355
|
return this;
|
|
288
356
|
}
|
|
357
|
+
betaDevContainerPrebuild(devcontainerDirectory) {
|
|
358
|
+
this.requireDevContainerTemplate('betaDevContainerPrebuild');
|
|
359
|
+
return this.runCmd(`devcontainer build --workspace-folder ${devcontainerDirectory}`, { user: 'root' });
|
|
360
|
+
}
|
|
361
|
+
betaSetDevContainerStart(devcontainerDirectory) {
|
|
362
|
+
this.requireDevContainerTemplate('betaSetDevContainerStart');
|
|
363
|
+
return this.setStartCmd(`sudo devcontainer up --workspace-folder ${devcontainerDirectory} && sudo /prepare-exec.sh ${devcontainerDirectory} | sudo tee /devcontainer.sh > /dev/null && sudo chmod +x /devcontainer.sh && sudo touch /devcontainer.up`, waitForFile('/devcontainer.up'));
|
|
364
|
+
}
|
|
289
365
|
setEnvs(envs) {
|
|
290
366
|
Object.assign(this.env, envs);
|
|
291
367
|
return this;
|
|
@@ -297,7 +373,11 @@ export class TemplateBase {
|
|
|
297
373
|
toBuildSpec() {
|
|
298
374
|
const spec = {};
|
|
299
375
|
if (this.base)
|
|
300
|
-
spec.
|
|
376
|
+
spec.from_template = this.base;
|
|
377
|
+
if (this.fromImageReference)
|
|
378
|
+
spec.from_image = this.fromImageReference;
|
|
379
|
+
if (this.fromImageRegistry)
|
|
380
|
+
spec.from_image_registry = this.fromImageRegistry;
|
|
301
381
|
if (Object.keys(this.packages).length > 0)
|
|
302
382
|
spec.packages = this.packages;
|
|
303
383
|
if (this.files.length > 0)
|
|
@@ -315,6 +395,11 @@ export class TemplateBase {
|
|
|
315
395
|
addPackages(manager, packages) {
|
|
316
396
|
this.packages[manager] = [...(this.packages[manager] ?? []), ...packages];
|
|
317
397
|
}
|
|
398
|
+
requireDevContainerTemplate(method) {
|
|
399
|
+
if (this.base !== 'devcontainer') {
|
|
400
|
+
throw new SandboxError(`${method} can only be used with the devcontainer template`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
318
403
|
addCopySource(source, dest, options, multipleSources) {
|
|
319
404
|
const sourcePath = this.resolveContextPath(source);
|
|
320
405
|
const stat = fs.statSync(sourcePath);
|
|
@@ -368,7 +453,7 @@ export class TemplateBase {
|
|
|
368
453
|
: commandText;
|
|
369
454
|
}
|
|
370
455
|
toDockerfile() {
|
|
371
|
-
const lines = [
|
|
456
|
+
const lines = [`FROM ${this.fromImageReference ?? this.base ?? 'base'}`];
|
|
372
457
|
for (const packageName of this.packages.apt ?? [])
|
|
373
458
|
lines.push(`RUN apt-get update && apt-get install -y ${packageName}`);
|
|
374
459
|
for (const packageName of this.packages.pip ?? [])
|
|
@@ -542,7 +627,7 @@ async function waitForBuildFinish(data, options) {
|
|
|
542
627
|
if (status === 'ready')
|
|
543
628
|
return;
|
|
544
629
|
if (status === 'error')
|
|
545
|
-
throw new
|
|
630
|
+
throw new TemplateError(buildStatus.reason?.message ?? 'Template build failed');
|
|
546
631
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
547
632
|
}
|
|
548
633
|
}
|
|
@@ -589,11 +674,12 @@ function buildStatusReason(payload) {
|
|
|
589
674
|
}
|
|
590
675
|
function logEntry(payload) {
|
|
591
676
|
const timestamp = stringValue(payload.timestamp);
|
|
592
|
-
return
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
677
|
+
return new LogEntry(timestamp ? new Date(timestamp) : new Date(), logEntryLevel(stringValue(payload.level)), stringValue(payload.message) ?? '');
|
|
678
|
+
}
|
|
679
|
+
function logEntryLevel(value) {
|
|
680
|
+
if (value === 'debug' || value === 'info' || value === 'warn' || value === 'error')
|
|
681
|
+
return value;
|
|
682
|
+
return 'info';
|
|
597
683
|
}
|
|
598
684
|
function templateTag(payload) {
|
|
599
685
|
return {
|
package/dist/terminal.d.ts
CHANGED
|
@@ -13,9 +13,9 @@ export type TerminalOpts = {
|
|
|
13
13
|
terminalID?: string;
|
|
14
14
|
cmd?: string;
|
|
15
15
|
cwd?: string;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
envs?: Record<string, string>;
|
|
17
|
+
timeoutMs?: number;
|
|
18
|
+
requestTimeoutMs?: number;
|
|
19
19
|
};
|
|
20
20
|
/** A running terminal session in a sandbox. */
|
|
21
21
|
export declare class Terminal {
|
package/dist/terminal.js
CHANGED
|
@@ -32,7 +32,10 @@ export class Terminal {
|
|
|
32
32
|
return this.waitPromise;
|
|
33
33
|
}
|
|
34
34
|
async waitOnce() {
|
|
35
|
-
await this.handle.wait().
|
|
35
|
+
await this.handle.wait().then((result) => {
|
|
36
|
+
if (this.output.data.length === 0 && result.stdout)
|
|
37
|
+
this.output.addData(result.stdout);
|
|
38
|
+
}).catch((error) => {
|
|
36
39
|
if (typeof error?.stdout === 'string')
|
|
37
40
|
this.output.addData(error.stdout);
|
|
38
41
|
else
|
|
@@ -59,10 +62,10 @@ export class TerminalManager {
|
|
|
59
62
|
const handle = await this.pty.create({
|
|
60
63
|
cmd: opts.cmd,
|
|
61
64
|
cwd: opts.cwd,
|
|
62
|
-
|
|
63
|
-
envVars: opts.envVars,
|
|
65
|
+
envs: opts.envs,
|
|
64
66
|
size: opts.size,
|
|
65
|
-
|
|
67
|
+
timeoutMs: opts.timeoutMs,
|
|
68
|
+
requestTimeoutMs: opts.requestTimeoutMs,
|
|
66
69
|
onData: async (bytes) => {
|
|
67
70
|
const data = new TextDecoder().decode(bytes);
|
|
68
71
|
output.addData(data);
|
package/dist/transport.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare class DataPlaneClient {
|
|
|
15
15
|
readonly token: string;
|
|
16
16
|
private readonly config;
|
|
17
17
|
constructor(baseUrl: string, token: string, config: ConnectionConfig);
|
|
18
|
+
get headers(): Record<string, string>;
|
|
18
19
|
getJson(path: string, opts?: RequestOpts): Promise<JsonRecord>;
|
|
19
20
|
postJson(path: string, opts?: RequestOpts): Promise<JsonRecord>;
|
|
20
21
|
deleteJson(path: string, opts?: RequestOpts): Promise<JsonRecord>;
|
|
@@ -29,6 +30,7 @@ export interface RequestOpts {
|
|
|
29
30
|
body?: BodyInit | Uint8Array;
|
|
30
31
|
headers?: Record<string, string>;
|
|
31
32
|
requestTimeoutMs?: number;
|
|
33
|
+
signal?: AbortSignal;
|
|
32
34
|
}
|
|
33
35
|
export declare function withQuery(path: string, params: Record<string, string | number | boolean | undefined>): string;
|
|
34
36
|
export {};
|
package/dist/transport.js
CHANGED
|
@@ -28,9 +28,10 @@ export class ControlClient {
|
|
|
28
28
|
headers: {
|
|
29
29
|
...this.config.authHeaders,
|
|
30
30
|
...(opts.json ? { 'content-type': 'application/json' } : {}),
|
|
31
|
+
...(opts.headers ?? {}),
|
|
31
32
|
},
|
|
32
33
|
body: opts.json ? JSON.stringify(opts.json) : undefined,
|
|
33
|
-
}, opts.requestTimeoutMs ?? this.config.requestTimeoutMs);
|
|
34
|
+
}, opts.requestTimeoutMs ?? this.config.requestTimeoutMs, opts.signal ?? this.config.signal);
|
|
34
35
|
return parseJsonResponse(response);
|
|
35
36
|
}
|
|
36
37
|
}
|
|
@@ -43,6 +44,9 @@ export class DataPlaneClient {
|
|
|
43
44
|
this.token = token;
|
|
44
45
|
this.config = config;
|
|
45
46
|
}
|
|
47
|
+
get headers() {
|
|
48
|
+
return this.config.headers;
|
|
49
|
+
}
|
|
46
50
|
getJson(path, opts = {}) {
|
|
47
51
|
return this.request(path, { ...opts, method: 'GET' });
|
|
48
52
|
}
|
|
@@ -68,12 +72,13 @@ export class DataPlaneClient {
|
|
|
68
72
|
const response = await fetchWithTimeout(joinUrl(this.baseUrl, path), {
|
|
69
73
|
method: opts.method,
|
|
70
74
|
headers: {
|
|
75
|
+
...this.config.headers,
|
|
71
76
|
Authorization: `Bearer ${this.token}`,
|
|
72
77
|
...(opts.json ? { 'content-type': 'application/json' } : {}),
|
|
73
78
|
...(opts.headers ?? {}),
|
|
74
79
|
},
|
|
75
80
|
body: opts.json ? JSON.stringify(opts.json) : opts.body,
|
|
76
|
-
}, opts.requestTimeoutMs ?? this.config.requestTimeoutMs);
|
|
81
|
+
}, opts.requestTimeoutMs ?? this.config.requestTimeoutMs, opts.signal ?? this.config.signal);
|
|
77
82
|
if (!response.ok) {
|
|
78
83
|
throw errorFromResponse(response.status, await readJsonOrText(response));
|
|
79
84
|
}
|
|
@@ -109,14 +114,26 @@ async function readJsonOrText(response) {
|
|
|
109
114
|
return { message: text };
|
|
110
115
|
}
|
|
111
116
|
}
|
|
112
|
-
function fetchWithTimeout(url, init, timeoutMs) {
|
|
117
|
+
function fetchWithTimeout(url, init, timeoutMs, signal) {
|
|
113
118
|
const controller = new AbortController();
|
|
114
|
-
|
|
119
|
+
let timedOut = false;
|
|
120
|
+
const abortFromCaller = () => controller.abort();
|
|
121
|
+
if (signal?.aborted)
|
|
122
|
+
controller.abort();
|
|
123
|
+
else
|
|
124
|
+
signal?.addEventListener('abort', abortFromCaller, { once: true });
|
|
125
|
+
const timeout = setTimeout(() => {
|
|
126
|
+
timedOut = true;
|
|
127
|
+
controller.abort();
|
|
128
|
+
}, timeoutMs);
|
|
115
129
|
return fetch(url, { ...init, signal: controller.signal }).catch((error) => {
|
|
116
|
-
if (error?.name === 'AbortError')
|
|
130
|
+
if (error?.name === 'AbortError' && timedOut)
|
|
117
131
|
throw new TimeoutError();
|
|
118
132
|
throw error;
|
|
119
|
-
}).finally(() =>
|
|
133
|
+
}).finally(() => {
|
|
134
|
+
clearTimeout(timeout);
|
|
135
|
+
signal?.removeEventListener('abort', abortFromCaller);
|
|
136
|
+
});
|
|
120
137
|
}
|
|
121
138
|
function joinUrl(base, path) {
|
|
122
139
|
const normalizedBase = base.replace(/\/+$/, '');
|
package/dist/volume.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { Blob } from 'node:buffer';
|
|
2
2
|
import { ConnectionConfig, type ConnectionOpts } from './connectionConfig.js';
|
|
3
3
|
import { ControlClient } from './transport.js';
|
|
4
|
-
export
|
|
4
|
+
export declare enum VolumeFileType {
|
|
5
|
+
UNKNOWN = "unknown",
|
|
6
|
+
FILE = "file",
|
|
7
|
+
DIRECTORY = "directory",
|
|
8
|
+
SYMLINK = "symlink"
|
|
9
|
+
}
|
|
5
10
|
export type VolumeReadFormat = 'text' | 'bytes' | 'blob' | 'stream';
|
|
6
11
|
export type VolumeWriteData = string | Uint8Array | ArrayBuffer | Blob;
|
|
7
12
|
/** Control-plane metadata for a persistent Watasu volume. */
|
|
@@ -23,7 +28,7 @@ export interface VolumeInfo {
|
|
|
23
28
|
export interface VolumeEntryStat {
|
|
24
29
|
path: string;
|
|
25
30
|
name: string;
|
|
26
|
-
type: VolumeFileType;
|
|
31
|
+
type: VolumeFileType | string;
|
|
27
32
|
size?: number;
|
|
28
33
|
mode?: number;
|
|
29
34
|
uid?: number;
|
|
@@ -36,8 +41,8 @@ export interface VolumeEntryStat {
|
|
|
36
41
|
export interface VolumeApiParams extends ConnectionOpts {
|
|
37
42
|
team?: string;
|
|
38
43
|
}
|
|
39
|
-
export
|
|
40
|
-
|
|
44
|
+
export type VolumeConnectionConfig = ConnectionOpts;
|
|
45
|
+
export declare const VolumeConnectionConfig: typeof ConnectionConfig;
|
|
41
46
|
export interface VolumeListOpts extends ConnectionOpts {
|
|
42
47
|
team?: string;
|
|
43
48
|
}
|
|
@@ -58,6 +63,12 @@ export interface VolumeMetadataOpts extends ConnectionOpts {
|
|
|
58
63
|
gid?: number;
|
|
59
64
|
mode?: number | string;
|
|
60
65
|
}
|
|
66
|
+
export type VolumeAndToken = VolumeInfo & {
|
|
67
|
+
token: string;
|
|
68
|
+
};
|
|
69
|
+
export type VolumeApiOpts = ConnectionOpts;
|
|
70
|
+
export type VolumeMetadataOptions = Omit<VolumeMetadataOpts, keyof ConnectionOpts>;
|
|
71
|
+
export type VolumeWriteOptions = Omit<VolumeWriteFileOpts, keyof ConnectionOpts>;
|
|
61
72
|
/** Persistent volume that can be mounted into sandboxes and edited while detached. */
|
|
62
73
|
export declare class Volume {
|
|
63
74
|
readonly volumeId: string;
|
|
@@ -83,8 +94,6 @@ export declare class Volume {
|
|
|
83
94
|
static list(opts?: VolumeListOpts): Promise<VolumeInfo[]>;
|
|
84
95
|
/** Destroy a volume by id or name. Returns false when it does not exist. */
|
|
85
96
|
static destroy(volumeId: string, opts?: VolumeConnectionConfig): Promise<boolean>;
|
|
86
|
-
/** Alias for `destroy`. */
|
|
87
|
-
static delete(volumeId: string, opts?: VolumeConnectionConfig): Promise<boolean>;
|
|
88
97
|
/** Fetch this volume's latest metadata. */
|
|
89
98
|
getInfo(): Promise<VolumeInfo>;
|
|
90
99
|
/** Fetch metadata for a path inside this volume. */
|
|
@@ -114,7 +123,5 @@ export declare class Volume {
|
|
|
114
123
|
remove(path: string, opts?: ConnectionOpts): Promise<boolean>;
|
|
115
124
|
/** Destroy this volume. Returns false when it no longer exists. */
|
|
116
125
|
destroy(opts?: ConnectionOpts): Promise<boolean>;
|
|
117
|
-
/** Alias for `destroy`. */
|
|
118
|
-
delete(opts?: ConnectionOpts): Promise<boolean>;
|
|
119
126
|
private configOptions;
|
|
120
127
|
}
|
package/dist/volume.js
CHANGED
|
@@ -3,6 +3,14 @@ import { ConnectionConfig } from './connectionConfig.js';
|
|
|
3
3
|
import { NotFoundError, SandboxError } from './errors.js';
|
|
4
4
|
import { base64DecodeBytes, base64DecodeText, base64Encode } from './processSocket.js';
|
|
5
5
|
import { ControlClient, withQuery } from './transport.js';
|
|
6
|
+
export var VolumeFileType;
|
|
7
|
+
(function (VolumeFileType) {
|
|
8
|
+
VolumeFileType["UNKNOWN"] = "unknown";
|
|
9
|
+
VolumeFileType["FILE"] = "file";
|
|
10
|
+
VolumeFileType["DIRECTORY"] = "directory";
|
|
11
|
+
VolumeFileType["SYMLINK"] = "symlink";
|
|
12
|
+
})(VolumeFileType || (VolumeFileType = {}));
|
|
13
|
+
export const VolumeConnectionConfig = ConnectionConfig;
|
|
6
14
|
/** Persistent volume that can be mounted into sandboxes and edited while detached. */
|
|
7
15
|
export class Volume {
|
|
8
16
|
volumeId;
|
|
@@ -26,6 +34,7 @@ export class Volume {
|
|
|
26
34
|
const payload = await control.post('/volumes', {
|
|
27
35
|
json: compactRecord({ name, team: opts.team }),
|
|
28
36
|
requestTimeoutMs: opts.requestTimeoutMs,
|
|
37
|
+
signal: opts.signal,
|
|
29
38
|
});
|
|
30
39
|
return volumeFromPayload(payload, config, control);
|
|
31
40
|
}
|
|
@@ -35,6 +44,7 @@ export class Volume {
|
|
|
35
44
|
const control = new ControlClient(config);
|
|
36
45
|
const payload = await control.get(`/volumes/${encodeURIComponent(volumeId)}`, {
|
|
37
46
|
requestTimeoutMs: opts.requestTimeoutMs,
|
|
47
|
+
signal: opts.signal,
|
|
38
48
|
});
|
|
39
49
|
return volumeFromPayload(payload, config, control);
|
|
40
50
|
}
|
|
@@ -44,6 +54,7 @@ export class Volume {
|
|
|
44
54
|
const control = new ControlClient(config);
|
|
45
55
|
const payload = await control.get(`/volumes/${encodeURIComponent(volumeId)}`, {
|
|
46
56
|
requestTimeoutMs: opts.requestTimeoutMs,
|
|
57
|
+
signal: opts.signal,
|
|
47
58
|
});
|
|
48
59
|
return volumeInfo(record(payload.volume ?? payload));
|
|
49
60
|
}
|
|
@@ -52,7 +63,10 @@ export class Volume {
|
|
|
52
63
|
const config = new ConnectionConfig(opts);
|
|
53
64
|
const control = new ControlClient(config);
|
|
54
65
|
const path = withQuery('/volumes', { team: opts.team });
|
|
55
|
-
const payload = await control.get(path, {
|
|
66
|
+
const payload = await control.get(path, {
|
|
67
|
+
requestTimeoutMs: opts.requestTimeoutMs,
|
|
68
|
+
signal: opts.signal,
|
|
69
|
+
});
|
|
56
70
|
const volumes = Array.isArray(payload.volumes) ? payload.volumes : [];
|
|
57
71
|
return volumes.map((item) => volumeInfo(record(item)));
|
|
58
72
|
}
|
|
@@ -63,6 +77,7 @@ export class Volume {
|
|
|
63
77
|
try {
|
|
64
78
|
await control.delete(`/volumes/${encodeURIComponent(volumeId)}`, {
|
|
65
79
|
requestTimeoutMs: opts.requestTimeoutMs,
|
|
80
|
+
signal: opts.signal,
|
|
66
81
|
});
|
|
67
82
|
return true;
|
|
68
83
|
}
|
|
@@ -72,16 +87,13 @@ export class Volume {
|
|
|
72
87
|
throw error;
|
|
73
88
|
}
|
|
74
89
|
}
|
|
75
|
-
/** Alias for `destroy`. */
|
|
76
|
-
static async delete(volumeId, opts = {}) {
|
|
77
|
-
return this.destroy(volumeId, opts);
|
|
78
|
-
}
|
|
79
90
|
async getInfo(path, opts = {}) {
|
|
80
91
|
if (path === undefined) {
|
|
81
92
|
return Volume.getInfo(this.volumeId, this.configOptions(opts));
|
|
82
93
|
}
|
|
83
94
|
const payload = await this.control.get(withQuery(`/volumes/${this.volumeId}/path`, { path }), {
|
|
84
95
|
requestTimeoutMs: opts.requestTimeoutMs,
|
|
96
|
+
signal: opts.signal,
|
|
85
97
|
});
|
|
86
98
|
return volumeEntry(record(payload.file ?? payload));
|
|
87
99
|
}
|
|
@@ -92,6 +104,7 @@ export class Volume {
|
|
|
92
104
|
depth: opts.depth,
|
|
93
105
|
}), {
|
|
94
106
|
requestTimeoutMs: opts.requestTimeoutMs,
|
|
107
|
+
signal: opts.signal,
|
|
95
108
|
});
|
|
96
109
|
const entries = Array.isArray(payload.entries) ? payload.entries : [];
|
|
97
110
|
return entries.map((item) => volumeEntry(record(item)));
|
|
@@ -101,6 +114,7 @@ export class Volume {
|
|
|
101
114
|
const payload = await this.control.post(`/volumes/${this.volumeId}/directories`, {
|
|
102
115
|
json: compactRecord({ path, ...metadataPayload(opts) }),
|
|
103
116
|
requestTimeoutMs: opts.requestTimeoutMs,
|
|
117
|
+
signal: opts.signal,
|
|
104
118
|
});
|
|
105
119
|
return volumeEntry(record(payload.file ?? payload));
|
|
106
120
|
}
|
|
@@ -121,12 +135,14 @@ export class Volume {
|
|
|
121
135
|
const payload = await this.control.patch(`/volumes/${this.volumeId}/path`, {
|
|
122
136
|
json: compactRecord({ path, ...metadataPayload(opts) }),
|
|
123
137
|
requestTimeoutMs: opts.requestTimeoutMs,
|
|
138
|
+
signal: opts.signal,
|
|
124
139
|
});
|
|
125
140
|
return volumeEntry(record(payload.file ?? payload));
|
|
126
141
|
}
|
|
127
142
|
async readFile(path, opts = {}) {
|
|
128
143
|
const payload = await this.control.get(withQuery(`/volumes/${this.volumeId}/files`, { path }), {
|
|
129
144
|
requestTimeoutMs: opts.requestTimeoutMs,
|
|
145
|
+
signal: opts.signal,
|
|
130
146
|
});
|
|
131
147
|
const file = record(payload.file ?? payload);
|
|
132
148
|
const content = file.content_b64 ?? file.contentBase64 ?? file.content ?? '';
|
|
@@ -154,6 +170,7 @@ export class Volume {
|
|
|
154
170
|
force: opts.force,
|
|
155
171
|
}),
|
|
156
172
|
requestTimeoutMs: opts.requestTimeoutMs,
|
|
173
|
+
signal: opts.signal,
|
|
157
174
|
});
|
|
158
175
|
return volumeEntry(record(payload.file ?? payload));
|
|
159
176
|
}
|
|
@@ -161,6 +178,7 @@ export class Volume {
|
|
|
161
178
|
async remove(path, opts = {}) {
|
|
162
179
|
await this.control.delete(withQuery(`/volumes/${this.volumeId}/path`, { path }), {
|
|
163
180
|
requestTimeoutMs: opts.requestTimeoutMs,
|
|
181
|
+
signal: opts.signal,
|
|
164
182
|
});
|
|
165
183
|
return true;
|
|
166
184
|
}
|
|
@@ -168,16 +186,17 @@ export class Volume {
|
|
|
168
186
|
async destroy(opts = {}) {
|
|
169
187
|
return Volume.destroy(this.volumeId, this.configOptions(opts));
|
|
170
188
|
}
|
|
171
|
-
/** Alias for `destroy`. */
|
|
172
|
-
async delete(opts = {}) {
|
|
173
|
-
return this.destroy(opts);
|
|
174
|
-
}
|
|
175
189
|
configOptions(opts = {}) {
|
|
176
190
|
return {
|
|
177
191
|
apiKey: this.config.apiKey,
|
|
178
192
|
apiUrl: this.config.apiUrl,
|
|
179
193
|
dataPlaneDomain: this.config.dataPlaneDomain,
|
|
180
194
|
requestTimeoutMs: this.config.requestTimeoutMs,
|
|
195
|
+
headers: this.config.headers,
|
|
196
|
+
apiHeaders: this.config.apiHeaders,
|
|
197
|
+
debug: this.config.debug,
|
|
198
|
+
signal: this.config.signal,
|
|
199
|
+
proxy: this.config.proxy,
|
|
181
200
|
...opts,
|
|
182
201
|
};
|
|
183
202
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@watasu/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.50",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"description": "TypeScript SDK for Watasu",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
],
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "tsc -p tsconfig.json",
|
|
26
|
-
"test": "npm run build && node --test test/*.mjs"
|
|
26
|
+
"test": "npm run build && tsc -p tsconfig.test.json && node --test test/*.mjs"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"ws": "^8.18.3"
|