@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/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, unsupported } from './errors.js';
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?.({ timestamp: new Date(), level: 'info', message: 'Build started' });
97
+ buildOptions.onBuildLogs?.(new LogEntryStart());
61
98
  const data = await TemplateBase.buildInBackground(template, name, buildOptions);
62
99
  await waitForBuildFinish(data, buildOptions);
63
- buildOptions.onBuildLogs?.({ timestamp: new Date(), level: 'info', message: 'Build finished' });
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.base = 'base';
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.base = 'base';
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
- this.base = this.base ?? 'base';
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
- this.base = this.base ?? 'base';
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
- this.base = this.base ?? 'base';
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.base = 'base';
160
- return this;
197
+ return this.fromTemplate('base');
161
198
  }
162
- fromImage(_baseImage, _credentials) {
163
- this.base = this.base ?? 'base';
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
- fromAWSRegistry(_image, _credentials) {
167
- unsupported('Template.fromAWSRegistry');
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
- fromGCPRegistry(_image, _credentials) {
170
- unsupported('Template.fromGCPRegistry');
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.base = this.base;
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 = ['FROM base'];
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 SandboxError(buildStatus.reason?.message ?? 'Template build failed');
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
- timestamp: timestamp ? new Date(timestamp) : undefined,
594
- level: stringValue(payload.level) ?? 'info',
595
- message: stringValue(payload.message) ?? '',
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 {
@@ -13,9 +13,9 @@ export type TerminalOpts = {
13
13
  terminalID?: string;
14
14
  cmd?: string;
15
15
  cwd?: string;
16
- rootDir?: string;
17
- envVars?: Record<string, string>;
18
- timeout?: number;
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().catch((error) => {
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
- rootDir: opts.rootDir,
63
- envVars: opts.envVars,
65
+ envs: opts.envs,
64
66
  size: opts.size,
65
- timeout: opts.timeout,
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);
@@ -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
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
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(() => clearTimeout(timeout));
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 type VolumeFileType = 'file' | 'directory' | 'symlink' | string;
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 interface VolumeConnectionConfig extends ConnectionOpts {
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, { requestTimeoutMs: opts.requestTimeoutMs });
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.30",
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"