withruntime 0.7.2 → 0.8.1

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.
@@ -0,0 +1,171 @@
1
+ import { RuntimeError } from "../errors.js";
2
+ /* Blaxel's error classes by the same names, so a
3
+ `catch (e) { if (e instanceof ResponseError) ... }` or an
4
+ `isGatewayError(e)` written for Blaxel keeps working. One made from a
5
+ Runtime answer also carries Runtime's own code (`runtimeCode`), `hint` and
6
+ `requestId`, and the original error as `cause`. */
7
+ const GATEWAY = new Set([502, 503, 504]);
8
+ /** An error answer. `status` is the HTTP status; `code` is the same number,
9
+ * as Blaxel's control-plane errors carry it (`e.code === 404`); `data` is
10
+ * `{ error, code, hint, requestId }`. */
11
+ export class ResponseError extends Error {
12
+ response;
13
+ error;
14
+ status;
15
+ statusText;
16
+ data;
17
+ code;
18
+ runtimeCode;
19
+ hint;
20
+ requestId;
21
+ constructor(response, data, error) {
22
+ const status = response.status || undefined;
23
+ const detail = data && typeof data === "object" && typeof data.error === "string"
24
+ ? data.error
25
+ : undefined;
26
+ super(`Sandbox request failed with status ${status ?? "unknown"}${detail ? `: ${detail}` : ""}`);
27
+ this.name = new.target.name;
28
+ this.response = response;
29
+ this.error = error;
30
+ this.data = data;
31
+ if (status !== undefined) {
32
+ this.status = status;
33
+ this.code = status;
34
+ }
35
+ if (response.statusText)
36
+ this.statusText = response.statusText;
37
+ }
38
+ }
39
+ /** A 502, 503 or 504: nothing usable came back. Safe to retry an idempotent
40
+ * call. */
41
+ export class SandboxGatewayError extends ResponseError {
42
+ }
43
+ /** True when `err` carries a gateway status (502, 503 or 504). */
44
+ export function isGatewayError(err) {
45
+ return (typeof err === "object" &&
46
+ err !== null &&
47
+ GATEWAY.has(err.status));
48
+ }
49
+ /** True when `err` is a gateway timeout (504). */
50
+ export function isGatewayTimeout(err) {
51
+ return typeof err === "object" && err !== null && err.status === 504;
52
+ }
53
+ /** No Runtime key was found. A Blaxel key (BL_API_KEY) is never sent. */
54
+ export class CredentialsError extends Error {
55
+ constructor(message) {
56
+ super(message);
57
+ this.name = new.target.name;
58
+ }
59
+ }
60
+ /** A call that Blaxel supports and Runtime does not, or not in the same way.
61
+ * Thrown before anything is done, never after doing something different.
62
+ * `feature` names what was asked for; `alternative` says what to use on
63
+ * Runtime instead. */
64
+ export class NotSupportedError extends Error {
65
+ code = "not_supported";
66
+ feature;
67
+ alternative;
68
+ constructor(feature, alternative, message) {
69
+ super(message ?? `${feature} is not supported on Runtime. ${alternative}`);
70
+ this.name = new.target.name;
71
+ this.feature = feature;
72
+ this.alternative = alternative;
73
+ }
74
+ }
75
+ /** A ResponseError of this status and message, made here rather than by
76
+ * Runtime: a process or preview that is not there, a wait that ran out. */
77
+ export function responseError(status, message) {
78
+ const data = { error: message };
79
+ const Kind = GATEWAY.has(status) ? SandboxGatewayError : ResponseError;
80
+ return new Kind(new Response(JSON.stringify(data), {
81
+ status,
82
+ headers: { "content-type": "application/json" },
83
+ }), data, data);
84
+ }
85
+ const LS = "List the directory with sandbox.fs.ls(path).";
86
+ /** Runtime's hints that name Runtime's calls, in the Blaxel calls a Blaxel
87
+ * program makes (the Python adapter uses the same words). */
88
+ const AGAIN = "Try again in a moment.";
89
+ const BLAXEL_HINTS = {
90
+ name_taken: "SandboxInstance.createIfNotExists({ name }) answers the sandbox that has the name.",
91
+ file_not_found: LS,
92
+ path_not_found: LS,
93
+ is_a_directory: "That path is a directory: list it with sandbox.fs.ls(path), or name a file in it.",
94
+ cwd_not_found: "Make the directory with sandbox.fs.mkdir(path), or pass an existing workingDir to sandbox.process.exec.",
95
+ sandbox_paused: "Call sandbox.unarchive(), then try again.",
96
+ not_running: "The sandbox is not running: call sandbox.unarchive() if it was archived, or make a new one with SandboxInstance.create if it was deleted.",
97
+ trial_busy: "The trial's sandboxes are all in use: delete one you no longer need (sandbox.delete()) or archive it (sandbox.archive()), then try again. Moving to paid credit is the account owner's decision.",
98
+ public_preview_not_allowed: "On the trial, share the port privately: sandbox.previews.create({ metadata: { name }, spec: { port, public: false } }) and a token from preview.tokens.create(expiresAt). A public preview needs a paid sandbox, which is the account owner's decision.",
99
+ busy: AGAIN,
100
+ guest_busy: AGAIN,
101
+ rate_limited: AGAIN,
102
+ unauthorized: "Set RUNTIME_API_KEY to a Runtime key (https://withruntime.com/account/keys), or run `npx withruntime login` once. A Blaxel key (BL_API_KEY) is never sent.",
103
+ };
104
+ /** A trial create over the trial's size, in Blaxel's field. */
105
+ const TRIAL_CAP = "A trial sandbox has at most 4096 MB of memory (2 vCPUs); pass memory 4096 or add credit.";
106
+ /** A Runtime SDK error as Blaxel's: ResponseError (SandboxGatewayError for a
107
+ * 502, 503 or 504), NotSupportedError for a product Runtime has switched off,
108
+ * CredentialsError when there is no Runtime key. Anything else passes through
109
+ * unchanged. */
110
+ export function translate(error) {
111
+ if (!(error instanceof RuntimeError))
112
+ return error;
113
+ if (error.code === "missing_api_key")
114
+ return Object.assign(new CredentialsError("No Runtime key found. A Blaxel key (BL_API_KEY) is never sent anywhere: set RUNTIME_API_KEY to a " +
115
+ "Runtime key (https://withruntime.com/account/keys), put a Runtime key (rtcloud_...) in BL_API_KEY, " +
116
+ "or run `npx withruntime login` once."), { cause: error });
117
+ if (error.status === 503 && error.code.endsWith("_unavailable"))
118
+ return new NotSupportedError(error.code.replace(/_unavailable$/, ""), error.hint ?? "", error.message);
119
+ if (error.status < 200 || error.status > 599)
120
+ return error;
121
+ const trialCap = error.code === "invalid_trial";
122
+ const hint = trialCap ? undefined : (BLAXEL_HINTS[error.code] ?? error.hint);
123
+ const message = [
124
+ trialCap ? TRIAL_CAP : error.message,
125
+ hint ? `Hint: ${hint}` : "",
126
+ error.requestId ? `Request: ${error.requestId}` : "",
127
+ ]
128
+ .filter(Boolean)
129
+ .join("\n");
130
+ const data = {
131
+ error: message,
132
+ code: error.code,
133
+ ...(hint ? { hint } : {}),
134
+ ...(error.requestId ? { requestId: error.requestId } : {}),
135
+ };
136
+ const Kind = GATEWAY.has(error.status) ? SandboxGatewayError : ResponseError;
137
+ const out = new Kind(new Response(JSON.stringify(data), {
138
+ status: error.status,
139
+ headers: { "content-type": "application/json" },
140
+ }), data, data);
141
+ out.runtimeCode = error.code;
142
+ if (hint)
143
+ out.hint = hint;
144
+ if (error.requestId)
145
+ out.requestId = error.requestId;
146
+ out.cause = error;
147
+ return out;
148
+ }
149
+ /** Runs `work`, turning any Runtime error into Blaxel's. */
150
+ export async function guard(work) {
151
+ try {
152
+ return await work();
153
+ }
154
+ catch (error) {
155
+ throw translate(error);
156
+ }
157
+ }
158
+ /** True for a 404 in either shape: Runtime's or Blaxel's. */
159
+ export function isNotFound(error) {
160
+ if (error instanceof RuntimeError)
161
+ return error.status === 404;
162
+ return error instanceof ResponseError && error.status === 404;
163
+ }
164
+ /** Runtime's error code on either shape. */
165
+ export function codeOf(error) {
166
+ if (error instanceof RuntimeError)
167
+ return error.code;
168
+ if (error instanceof ResponseError)
169
+ return error.runtimeCode;
170
+ return undefined;
171
+ }
@@ -0,0 +1,147 @@
1
+ import type { RuntimeSandbox } from "./client.js";
2
+ export type File = {
3
+ group: string;
4
+ lastModified: string;
5
+ name: string;
6
+ owner: string;
7
+ path: string;
8
+ permissions: string;
9
+ size: number;
10
+ };
11
+ export type FileWithContent = File & {
12
+ content: string;
13
+ };
14
+ export type Subdirectory = {
15
+ name: string;
16
+ path: string;
17
+ };
18
+ export type Directory = {
19
+ files: Array<File>;
20
+ name: string;
21
+ path: string;
22
+ subdirectories: Array<Subdirectory>;
23
+ };
24
+ export type SuccessResponse = {
25
+ message: string;
26
+ path?: string;
27
+ };
28
+ export type FindMatch = {
29
+ path: string;
30
+ type: string;
31
+ };
32
+ export type FindResponse = {
33
+ matches: Array<FindMatch>;
34
+ total: number;
35
+ };
36
+ export type FuzzySearchMatch = {
37
+ path: string;
38
+ score: number;
39
+ type: string;
40
+ };
41
+ export type FuzzySearchResponse = {
42
+ matches: Array<FuzzySearchMatch>;
43
+ total: number;
44
+ };
45
+ export type ContentSearchMatch = {
46
+ column: number;
47
+ context?: string;
48
+ line: number;
49
+ path: string;
50
+ text: string;
51
+ };
52
+ export type ContentSearchResponse = {
53
+ matches: Array<ContentSearchMatch>;
54
+ query: string;
55
+ total: number;
56
+ };
57
+ export type CopyResponse = {
58
+ message: string;
59
+ source: string;
60
+ destination: string;
61
+ };
62
+ export type WatchEvent = {
63
+ op: "CREATE" | "WRITE" | "REMOVE" | "RENAME" | "CHMOD";
64
+ path: string;
65
+ name: string;
66
+ content?: string;
67
+ };
68
+ export type SandboxFilesystemFile = {
69
+ path: string;
70
+ content: string;
71
+ };
72
+ export interface FilesystemSearchOptions {
73
+ maxResults?: number;
74
+ patterns?: string[];
75
+ excludeDirs?: string[];
76
+ excludeHidden?: boolean;
77
+ }
78
+ export interface FilesystemFindOptions {
79
+ type?: "file" | "directory";
80
+ patterns?: string[];
81
+ maxResults?: number;
82
+ excludeDirs?: string[];
83
+ excludeHidden?: boolean;
84
+ }
85
+ export interface FilesystemGrepOptions {
86
+ caseSensitive?: boolean;
87
+ contextLines?: number;
88
+ maxResults?: number;
89
+ filePattern?: string;
90
+ excludeDirs?: string[];
91
+ }
92
+ /** What `sandbox.fs` needs from its sandbox. */
93
+ export interface FsContext {
94
+ /** Runs `work` on the live sandbox, woken if it was paused. */
95
+ run<T>(work: (runtime: RuntimeSandbox) => Promise<T>): Promise<T>;
96
+ }
97
+ /** `sandbox.fs`. */
98
+ export declare class SandboxFileSystem {
99
+ #private;
100
+ constructor(ctx: FsContext);
101
+ /** Makes a directory and its parents. */
102
+ mkdir(path: string, permissions?: string): Promise<SuccessResponse>;
103
+ /** Writes a text file, making its directories. */
104
+ write(path: string, content: string): Promise<SuccessResponse>;
105
+ /** Writes bytes: a Buffer, Blob, File or Uint8Array, or a string naming a
106
+ * local file to upload. */
107
+ writeBinary(path: string, content: Buffer | Blob | Uint8Array | string): Promise<SuccessResponse>;
108
+ /** Writes files under `destinationPath` (the working directory by
109
+ * default) and answers the listing of it: one command for a small tree. */
110
+ writeTree(files: SandboxFilesystemFile[], destinationPath?: string | null): Promise<Directory | undefined>;
111
+ /** A text file's content. */
112
+ read(path: string): Promise<string>;
113
+ /** A file's bytes, as a Blob. */
114
+ readBinary(path: string): Promise<Blob>;
115
+ /** Copies a sandbox file to the local disk with `mode` (default 0o644). */
116
+ download(src: string, destinationPath: string, { mode }?: {
117
+ mode?: number;
118
+ }): Promise<void>;
119
+ /** Removes a file, or a directory (its contents too with `recursive`). */
120
+ rm(path: string, recursive?: boolean): Promise<SuccessResponse>;
121
+ /** A directory's files and subdirectories, as Blaxel lists them. */
122
+ ls(path: string): Promise<Directory>;
123
+ /** Paths under `path` fuzzily matching `query`, best first. */
124
+ search(query: string, path?: string, options?: FilesystemSearchOptions): Promise<FuzzySearchResponse>;
125
+ /** Files (or directories) under `path` whose name matches a pattern. */
126
+ find(path: string, options?: FilesystemFindOptions): Promise<FindResponse>;
127
+ /** Lines containing `query` (a plain string, case-insensitive by default)
128
+ * in the files under `path`. */
129
+ grep(query: string, path?: string, options?: FilesystemGrepOptions): Promise<ContentSearchResponse>;
130
+ /** Copies a file or directory (cp -r). */
131
+ cp(source: string, destination: string, _options?: {
132
+ maxWait?: number;
133
+ }): Promise<CopyResponse>;
134
+ /** Calls `callback` for each change in `path` (and below it for
135
+ * `path/**`) until `close()`. `withContent` adds a created or written
136
+ * file's text; `ignore` skips paths. */
137
+ watch(path: string, callback: (fileEvent: WatchEvent) => void | Promise<void>, options?: {
138
+ onError?: (error: Error) => void;
139
+ withContent?: boolean;
140
+ ignore?: string[];
141
+ }): {
142
+ close: () => void;
143
+ };
144
+ }
145
+ /** Blaxel's own retry helper: Runtime's SDK retries transient failures
146
+ * itself, so this runs `fn` once. */
147
+ export declare function retryOnTransient<T>(fn: () => Promise<T>): Promise<T>;