withruntime 0.7.2 → 0.8.0

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,119 @@
1
+ import type { Runtime } from "../client.js";
2
+ import type { Snapshot as RuntimeSnapshot } from "../snapshots.js";
3
+ import { type RuntimeOptions, type RuntimeSandbox } from "./client.js";
4
+ import { type PaginatedList } from "./pagination.js";
5
+ export type SandboxSnapshotSource = {
6
+ readonly deleted?: boolean;
7
+ kind?: "sandbox";
8
+ name: string;
9
+ };
10
+ export type SandboxSnapshotSpec = {
11
+ image?: string;
12
+ memory?: number;
13
+ region?: string;
14
+ };
15
+ export type SandboxSnapshot = {
16
+ createdAt: string;
17
+ createdBy?: string;
18
+ readonly id: string;
19
+ name: string;
20
+ sandboxName?: string;
21
+ source?: SandboxSnapshotSource;
22
+ spec?: SandboxSnapshotSpec;
23
+ status: string;
24
+ workspace: string;
25
+ };
26
+ export type SandboxForkResponse = {
27
+ name?: string;
28
+ snapshotId?: string;
29
+ type?: "sandbox" | "application";
30
+ };
31
+ export type SandboxRestoreResponse = {
32
+ name: string;
33
+ snapshotId: string;
34
+ };
35
+ export type Env = {
36
+ name?: string;
37
+ secret?: boolean;
38
+ value?: string;
39
+ };
40
+ export type SnapshotCreateConfiguration = {
41
+ name?: string;
42
+ source: {
43
+ name: string;
44
+ kind?: "sandbox";
45
+ };
46
+ };
47
+ export type SnapshotForkOptions = {
48
+ targetType?: "sandbox" | "application";
49
+ port?: number;
50
+ traffic?: number;
51
+ customDomain?: string;
52
+ prefix?: string;
53
+ envs?: Env[];
54
+ };
55
+ export type SnapshotListQuery = {
56
+ cursor?: string;
57
+ limit?: number;
58
+ sort?: string;
59
+ q?: string;
60
+ anchor?: "end";
61
+ };
62
+ /** Paused sandboxes are kept this long when no TTL says otherwise. */
63
+ export declare const KEEP_DAYS = 365;
64
+ export declare function snapshotModel(snapshot: RuntimeSnapshot, sandboxName?: string): SandboxSnapshot;
65
+ /** Envs as a record; a nameless one is refused. */
66
+ export declare function envRecord(envs: Env[] | undefined): Record<string, string>;
67
+ /** Appends envs to a sandbox's env file: they win over the ones it has. */
68
+ export declare function appendEnvs(runtime: RuntimeSandbox, envs: Record<string, string>): Promise<void>;
69
+ /** A new sandbox named `name`, started from a snapshot, behaving as a Blaxel
70
+ * sandbox. */
71
+ export declare function startFromSnapshot(client: Runtime, snapshotId: string, name: string, labels: Record<string, string>, envs: Record<string, string>): Promise<RuntimeSandbox>;
72
+ declare function refuseApplication(options?: {
73
+ targetType?: string;
74
+ }): void;
75
+ /** A snapshot, as Blaxel's Snapshot class. */
76
+ export declare class Snapshot {
77
+ #private;
78
+ constructor(snapshot: SandboxSnapshot, client: Runtime);
79
+ get name(): string;
80
+ get id(): string;
81
+ get status(): string;
82
+ get workspace(): string;
83
+ get createdAt(): string;
84
+ get source(): SandboxSnapshotSource | undefined;
85
+ get spec(): SandboxSnapshotSpec | undefined;
86
+ /** Takes a snapshot of the sandbox named in `source`. */
87
+ static create(config: SnapshotCreateConfiguration, options?: RuntimeOptions): Promise<Snapshot>;
88
+ /** A snapshot by its id. */
89
+ static get(snapshotId: string, options?: RuntimeOptions): Promise<Snapshot>;
90
+ /** The account's snapshots, a page at a time; `for await` walks them all. */
91
+ static list(query?: SnapshotListQuery, options?: RuntimeOptions): Promise<PaginatedList<Snapshot>>;
92
+ static delete(snapshotId: string, options?: RuntimeOptions): Promise<void>;
93
+ delete(): Promise<void>;
94
+ /** A new sandbox named `targetName`, started from this snapshot. */
95
+ fork(targetName: string, options?: SnapshotForkOptions): Promise<SandboxForkResponse>;
96
+ }
97
+ /** What `sandbox.snapshots` needs from its sandbox. */
98
+ export interface SnapshotContext {
99
+ readonly sandboxName: string;
100
+ readonly sandboxId: string;
101
+ readonly client: Runtime;
102
+ run<T>(work: (runtime: RuntimeSandbox) => Promise<T>): Promise<T>;
103
+ labels(): Record<string, string>;
104
+ }
105
+ /** `sandbox.snapshots`: this sandbox's snapshots, by name. */
106
+ export declare class SandboxSnapshotsResource {
107
+ #private;
108
+ constructor(ctx: SnapshotContext);
109
+ get sandboxName(): string;
110
+ create(name?: string): Promise<Snapshot>;
111
+ list(): Promise<Snapshot[]>;
112
+ /** This sandbox's newest snapshot with that name, or with that id. */
113
+ get(snapshotName: string): Promise<Snapshot>;
114
+ /** The id of a snapshot named, or given by id, for a fork. */
115
+ idOf(nameOrId: string): Promise<string>;
116
+ delete(snapshotName: string): Promise<void>;
117
+ restore(_snapshotName: string): Promise<never>;
118
+ }
119
+ export { refuseApplication };
@@ -0,0 +1,197 @@
1
+ import { clientFor, } from "./client.js";
2
+ import { envFileCommand, envFileLines } from "./context.js";
3
+ import { guard, isNotFound, NotSupportedError, responseError } from "./errors.js";
4
+ import { findSandbox, LEASE_SECONDS, STANDBY, userLabels, whenNameFree } from "./lookup.js";
5
+ import { paginate } from "./pagination.js";
6
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
7
+ /** A Blaxel snapshot is kept until deleted: Runtime's longest. */
8
+ const SNAPSHOT_DAYS = 365;
9
+ /** Paused sandboxes are kept this long when no TTL says otherwise. */
10
+ export const KEEP_DAYS = 365;
11
+ export function snapshotModel(snapshot, sandboxName) {
12
+ return {
13
+ id: snapshot.id,
14
+ name: snapshot.name ?? snapshot.id,
15
+ createdAt: snapshot.createdAt,
16
+ status: snapshot.state.toUpperCase(),
17
+ workspace: "",
18
+ ...(sandboxName ? { sandboxName } : {}),
19
+ source: { kind: "sandbox", name: sandboxName ?? snapshot.sourceSandboxId },
20
+ spec: { memory: snapshot.shape?.memoryMiB, region: "us-was-1" },
21
+ };
22
+ }
23
+ /** Envs as a record; a nameless one is refused. */
24
+ export function envRecord(envs) {
25
+ const out = {};
26
+ for (const env of envs ?? []) {
27
+ if (!env.name)
28
+ throw responseError(400, "every env needs a name");
29
+ out[env.name] = env.value ?? "";
30
+ }
31
+ return out;
32
+ }
33
+ /** Appends envs to a sandbox's env file: they win over the ones it has. */
34
+ export async function appendEnvs(runtime, envs) {
35
+ if (!Object.keys(envs).length)
36
+ return;
37
+ const result = await guard(() => runtime.exec(envFileCommand(true), { stdin: envFileLines(envs) }));
38
+ if (result.exitCode !== 0)
39
+ throw responseError(500, `Setting the sandbox's envs failed: ${result.stderr.trim()}`);
40
+ }
41
+ /** A new sandbox named `name`, started from a snapshot, behaving as a Blaxel
42
+ * sandbox. */
43
+ export async function startFromSnapshot(client, snapshotId, name, labels, envs) {
44
+ const input = {
45
+ snapshot: snapshotId,
46
+ name,
47
+ labels,
48
+ ...STANDBY,
49
+ timeoutSeconds: LEASE_SECONDS,
50
+ onLeaseEnd: "pause",
51
+ };
52
+ const runtime = await guard(() => whenNameFree(client, () => client.sandboxes.create(input)));
53
+ try {
54
+ await Promise.all([
55
+ appendEnvs(runtime, envs),
56
+ runtime.info.funding === "paid" ? guard(() => runtime.setRetention(KEEP_DAYS)) : undefined,
57
+ ]);
58
+ }
59
+ catch (error) {
60
+ await runtime.stop({ wait: false }).catch(() => undefined);
61
+ throw error;
62
+ }
63
+ return runtime;
64
+ }
65
+ function refuseApplication(options = {}) {
66
+ if (options.targetType === "application")
67
+ throw new NotSupportedError("Forking into an application", "Fork into a sandbox and share its port with sandbox.previews.create.");
68
+ }
69
+ /** A snapshot, as Blaxel's Snapshot class. */
70
+ export class Snapshot {
71
+ #snapshot;
72
+ #client;
73
+ constructor(snapshot, client) {
74
+ this.#snapshot = snapshot;
75
+ this.#client = client;
76
+ }
77
+ get name() {
78
+ return this.#snapshot.name;
79
+ }
80
+ get id() {
81
+ return this.#snapshot.id;
82
+ }
83
+ get status() {
84
+ return this.#snapshot.status;
85
+ }
86
+ get workspace() {
87
+ return this.#snapshot.workspace;
88
+ }
89
+ get createdAt() {
90
+ return this.#snapshot.createdAt;
91
+ }
92
+ get source() {
93
+ return this.#snapshot.source;
94
+ }
95
+ get spec() {
96
+ return this.#snapshot.spec;
97
+ }
98
+ /** Takes a snapshot of the sandbox named in `source`. */
99
+ static async create(config, options = {}) {
100
+ if (config.source.kind !== undefined && config.source.kind !== "sandbox")
101
+ throw new NotSupportedError(`A snapshot of a ${String(config.source.kind)}`, "Snapshot a sandbox.");
102
+ const client = clientFor(options);
103
+ const runtime = await guard(() => findSandbox(client, config.source.name));
104
+ const taken = await guard(() => runtime.snapshot({
105
+ ...(config.name ? { name: config.name } : {}),
106
+ labels: userLabels(runtime.info.labels),
107
+ retentionDays: SNAPSHOT_DAYS,
108
+ }));
109
+ return new Snapshot(snapshotModel(taken, config.source.name), client);
110
+ }
111
+ /** A snapshot by its id. */
112
+ static async get(snapshotId, options = {}) {
113
+ const client = clientFor(options);
114
+ return new Snapshot(snapshotModel(await guard(() => client.snapshots.get(snapshotId))), client);
115
+ }
116
+ /** The account's snapshots, a page at a time; `for await` walks them all. */
117
+ static async list(query = {}, options = {}) {
118
+ for (const field of ["cursor", "q", "anchor"])
119
+ if (query[field] !== undefined)
120
+ throw new NotSupportedError(`Listing snapshots by ${field}`, "List them (oldest first) and filter the result yourself; nextPage() walks the pages.");
121
+ if (query.sort !== undefined && query.sort !== "createdAt:asc")
122
+ throw new NotSupportedError(`Listing snapshots sorted by ${query.sort}`, "Runtime lists them oldest first: sort the result yourself.");
123
+ const client = clientFor(options);
124
+ const page = await guard(() => client.snapshots.list(query.limit ? { limit: Math.min(query.limit, 100) } : {}));
125
+ return paginate(page, (one) => new Snapshot(snapshotModel(one), client));
126
+ }
127
+ static async delete(snapshotId, options = {}) {
128
+ const client = clientFor(options);
129
+ await guard(() => client.snapshots.delete(snapshotId));
130
+ }
131
+ async delete() {
132
+ await guard(() => this.#client.snapshots.delete(this.id));
133
+ }
134
+ /** A new sandbox named `targetName`, started from this snapshot. */
135
+ async fork(targetName, options = {}) {
136
+ refuseApplication(options);
137
+ await startFromSnapshot(this.#client, this.id, targetName, {}, envRecord(options.envs));
138
+ return { name: targetName, snapshotId: this.id, type: "sandbox" };
139
+ }
140
+ }
141
+ /** `sandbox.snapshots`: this sandbox's snapshots, by name. */
142
+ export class SandboxSnapshotsResource {
143
+ #ctx;
144
+ constructor(ctx) {
145
+ this.#ctx = ctx;
146
+ }
147
+ get sandboxName() {
148
+ return this.#ctx.sandboxName;
149
+ }
150
+ async create(name) {
151
+ const taken = await this.#ctx.run((runtime) => runtime.snapshot({
152
+ ...(name ? { name } : {}),
153
+ labels: userLabels(this.#ctx.labels()),
154
+ retentionDays: SNAPSHOT_DAYS,
155
+ }));
156
+ return new Snapshot(snapshotModel(taken, this.sandboxName), this.#ctx.client);
157
+ }
158
+ async list() {
159
+ const page = await guard(() => this.#ctx.client.snapshots.list({ sandboxId: this.#ctx.sandboxId }));
160
+ const all = await guard(() => page.toArray());
161
+ return all.map((one) => new Snapshot(snapshotModel(one, this.sandboxName), this.#ctx.client));
162
+ }
163
+ /** This sandbox's newest snapshot with that name, or with that id. */
164
+ async get(snapshotName) {
165
+ return new Snapshot(snapshotModel(await this.#find(snapshotName), this.sandboxName), this.#ctx.client);
166
+ }
167
+ async #find(nameOrId) {
168
+ const client = this.#ctx.client;
169
+ const page = await guard(() => client.snapshots.list({ sandboxId: this.#ctx.sandboxId, name: nameOrId }));
170
+ const named = page.data.filter((one) => one.name === nameOrId).at(-1);
171
+ if (named)
172
+ return named;
173
+ if (UUID.test(nameOrId))
174
+ try {
175
+ const byId = await guard(() => client.snapshots.get(nameOrId));
176
+ if (byId.sourceSandboxId === this.#ctx.sandboxId)
177
+ return byId;
178
+ }
179
+ catch (error) {
180
+ if (!isNotFound(error))
181
+ throw error;
182
+ }
183
+ throw responseError(404, `snapshot ${nameOrId} not found`);
184
+ }
185
+ /** The id of a snapshot named, or given by id, for a fork. */
186
+ async idOf(nameOrId) {
187
+ return (await this.#find(nameOrId)).id;
188
+ }
189
+ async delete(snapshotName) {
190
+ const found = await this.#find(snapshotName);
191
+ await guard(() => this.#ctx.client.snapshots.delete(found.id));
192
+ }
193
+ restore(_snapshotName) {
194
+ return Promise.reject(new NotSupportedError("Restoring a sandbox to its snapshot in place", "Start a new sandbox from it: sandbox.fork(newName, { snapshotId }) or snapshot.fork(newName). The new sandbox has its own name and preview addresses."));
195
+ }
196
+ }
197
+ export { refuseApplication };
@@ -0,0 +1,145 @@
1
+ import type { WithRuntime } from "./client.js";
2
+ import type { Env } from "./snapshot.js";
3
+ export type Status = "DELETING" | "TERMINATED" | "FAILED" | "DEACTIVATED" | "DEACTIVATING" | "UPLOADING" | "BUILDING" | "DEPLOYING" | "DEPLOYED" | "BUILT" | "ARCHIVING" | "ARCHIVED" | "UNARCHIVING";
4
+ export type Port = {
5
+ name?: string;
6
+ protocol?: "HTTP" | "TCP" | "UDP" | "TLS";
7
+ target: number;
8
+ };
9
+ export type EnvVar = {
10
+ name: string;
11
+ value: string;
12
+ };
13
+ export type ExpirationPolicy = {
14
+ action?: "delete";
15
+ type?: "ttl-idle" | "ttl-max-age" | "date";
16
+ value?: string;
17
+ };
18
+ export type SandboxLifecycle = {
19
+ expirationPolicies?: Array<ExpirationPolicy>;
20
+ terminatedRetention?: string;
21
+ };
22
+ export type SandboxNetwork = {
23
+ allowedDomains?: Array<string>;
24
+ egress?: unknown;
25
+ firewall?: unknown;
26
+ forbiddenDomains?: Array<string>;
27
+ proxy?: unknown;
28
+ subnet?: string;
29
+ };
30
+ export type VolumeBinding = {
31
+ name: string;
32
+ mountPath: string;
33
+ readOnly?: boolean;
34
+ type?: "persistent" | "ephemeral";
35
+ sizeMb?: number;
36
+ };
37
+ export type VolumeAttachment = {
38
+ mountPath?: string;
39
+ name?: string;
40
+ readOnly?: boolean;
41
+ sizeMb?: number;
42
+ type?: "persistent" | "ephemeral";
43
+ };
44
+ export type Metadata = {
45
+ name: string;
46
+ displayName?: string;
47
+ externalId?: string;
48
+ labels?: Record<string, string>;
49
+ createdAt?: string;
50
+ updatedAt?: string;
51
+ createdBy?: string;
52
+ updatedBy?: string;
53
+ readonly plan?: string;
54
+ readonly url?: string;
55
+ readonly workspace?: string;
56
+ };
57
+ export type SandboxRuntime = {
58
+ envs?: Array<Env>;
59
+ expires?: string;
60
+ extraArgs?: {
61
+ [key: string]: string;
62
+ };
63
+ image?: string;
64
+ memory?: number;
65
+ ports?: Array<Port>;
66
+ terminationGracePeriodSeconds?: number;
67
+ ttl?: string;
68
+ };
69
+ export type SandboxSpec = {
70
+ enabled?: boolean;
71
+ lifecycle?: SandboxLifecycle;
72
+ network?: SandboxNetwork;
73
+ region?: string;
74
+ runtime?: SandboxRuntime;
75
+ volumes?: Array<VolumeAttachment>;
76
+ vpc?: string;
77
+ };
78
+ export type CoreEvent = {
79
+ canaryRevision?: string;
80
+ message?: string;
81
+ revision?: string;
82
+ status?: string;
83
+ time?: string;
84
+ type?: string;
85
+ };
86
+ export type Sandbox = {
87
+ events?: Array<CoreEvent>;
88
+ readonly expiresIn?: number;
89
+ readonly lastUsedAt?: string;
90
+ metadata: Metadata;
91
+ spec: SandboxSpec;
92
+ state?: "RUNNING" | "STANDBY";
93
+ status?: Status;
94
+ };
95
+ export type SandboxConfiguration = {
96
+ forceUrl?: string;
97
+ headers?: Record<string, string>;
98
+ params?: Record<string, string>;
99
+ } & Sandbox;
100
+ export type SandboxCreateConfiguration = {
101
+ name?: string;
102
+ /** A Blaxel stock image (Runtime's stock image) or a ready Runtime image's name. */
103
+ image?: string;
104
+ /** MB; vCPUs follow as memory / 2048, at least 1. */
105
+ memory?: number;
106
+ ports?: (Port | Record<string, unknown>)[];
107
+ envs?: EnvVar[];
108
+ volumes?: (VolumeBinding | VolumeAttachment)[];
109
+ ttl?: string;
110
+ expires?: Date;
111
+ region?: string;
112
+ lifecycle?: SandboxLifecycle;
113
+ network?: SandboxNetwork;
114
+ snapshotEnabled?: boolean;
115
+ labels?: Record<string, string>;
116
+ extraArgs?: Record<string, string>;
117
+ externalId?: string;
118
+ /** Runtime-only: an explicit client, or fields for the create. */
119
+ withruntime?: WithRuntime;
120
+ };
121
+ export type SandboxUpdateMetadata = {
122
+ labels?: Record<string, string>;
123
+ displayName?: string;
124
+ externalId?: string;
125
+ };
126
+ export type SandboxUpdateNetwork = {
127
+ network?: SandboxNetwork;
128
+ };
129
+ export interface SessionCreateOptions {
130
+ expiresAt?: Date;
131
+ responseHeaders?: Record<string, string>;
132
+ requestHeaders?: Record<string, string>;
133
+ }
134
+ export interface SessionWithToken {
135
+ name: string;
136
+ url: string;
137
+ token: string;
138
+ expiresAt: Date;
139
+ }
140
+ /** Ports as Port objects, HTTP by default. */
141
+ export declare function normalizePorts(ports?: (Port | Record<string, unknown>)[]): Port[] | undefined;
142
+ /** Envs checked for a string name and value. */
143
+ export declare function normalizeEnvs(envs?: EnvVar[]): EnvVar[] | undefined;
144
+ /** Volume bindings as attachments. */
145
+ export declare function normalizeVolumes(volumes?: (VolumeBinding | VolumeAttachment)[]): VolumeAttachment[] | undefined;
@@ -0,0 +1,60 @@
1
+ /** Ports as Port objects, HTTP by default. */
2
+ export function normalizePorts(ports) {
3
+ if (!ports || ports.length === 0)
4
+ return undefined;
5
+ return ports.map((port) => {
6
+ if (typeof port !== "object" ||
7
+ port === null ||
8
+ !("name" in port || "target" in port || "protocol" in port))
9
+ throw new Error(`Invalid port type: ${typeof port}. Expected Port object or object with port properties.`);
10
+ if (typeof port.target !== "number")
11
+ throw new Error(`Port target must be a number: ${JSON.stringify(port)}`);
12
+ return {
13
+ ...(typeof port.name === "string" ? { name: port.name } : {}),
14
+ target: port.target,
15
+ protocol: (typeof port.protocol === "string" ? port.protocol : "HTTP"),
16
+ };
17
+ });
18
+ }
19
+ /** Envs checked for a string name and value. */
20
+ export function normalizeEnvs(envs) {
21
+ if (!envs || envs.length === 0)
22
+ return undefined;
23
+ return envs.map((env) => {
24
+ if (typeof env !== "object" || env === null)
25
+ throw new Error(`Invalid env type: ${typeof env}. Expected object with 'name' and 'value' keys.`);
26
+ if (!("name" in env) || !("value" in env))
27
+ throw new Error(`Environment variable object must have 'name' and 'value' keys: ${JSON.stringify(env)}`);
28
+ if (typeof env.name !== "string" || typeof env.value !== "string")
29
+ throw new Error(`Environment variable 'name' and 'value' must be strings: ${JSON.stringify(env)}`);
30
+ return { name: env.name, value: env.value };
31
+ });
32
+ }
33
+ /** Volume bindings as attachments. */
34
+ export function normalizeVolumes(volumes) {
35
+ if (!volumes || volumes.length === 0)
36
+ return undefined;
37
+ return volumes.map((volume) => {
38
+ if (typeof volume !== "object" || volume === null)
39
+ throw new Error(`Invalid volume type: ${typeof volume}. Expected object with 'name' and 'mountPath' keys.`);
40
+ if (!("name" in volume) || !("mountPath" in volume))
41
+ throw new Error(`Volume binding object must have 'name' and 'mountPath' keys: ${JSON.stringify(volume)}`);
42
+ if (typeof volume.name !== "string" || typeof volume.mountPath !== "string")
43
+ throw new Error(`Volume binding 'name' and 'mountPath' must be strings: ${JSON.stringify(volume)}`);
44
+ const out = {
45
+ name: volume.name,
46
+ mountPath: volume.mountPath,
47
+ readOnly: volume.readOnly ?? false,
48
+ };
49
+ if (volume.type)
50
+ out.type = volume.type;
51
+ if (out.type === "ephemeral") {
52
+ if (typeof volume.sizeMb !== "number" || !(volume.sizeMb > 0))
53
+ throw new Error(`Ephemeral volume '${volume.name}' must have a positive 'sizeMb': ${JSON.stringify(volume)}`);
54
+ out.sizeMb = volume.sizeMb;
55
+ }
56
+ else if (typeof volume.sizeMb === "number")
57
+ out.sizeMb = volume.sizeMb;
58
+ return out;
59
+ });
60
+ }
@@ -0,0 +1,22 @@
1
+ /** A stand-in for a Blaxel export Runtime has no counterpart for. Importing
2
+ * it works, so a module that only mentions it still loads; calling it,
3
+ * building it or reading anything from it throws NotSupportedError naming the
4
+ * alternative. */
5
+ export type Unsupported = {
6
+ (...args: unknown[]): never;
7
+ new (...args: unknown[]): never;
8
+ readonly [key: string]: (...args: unknown[]) => never;
9
+ };
10
+ export declare function unsupportedExport(name: string, alternative: string): Unsupported;
11
+ /** A sandbox part Runtime has no counterpart for (`sandbox.sessions`,
12
+ * `sandbox.codegen`, ...): every method answers a rejected promise with
13
+ * NotSupportedError naming the alternative. */
14
+ export declare function unsupportedPart(name: string, alternative: string): UnsupportedPart;
15
+ export type UnsupportedPart = {
16
+ readonly [method: string]: (...args: unknown[]) => Promise<never>;
17
+ };
18
+ export declare const SESSIONS = "Share a port with sandbox.previews.create({ metadata: { name }, spec: { port, public: false } }) and a token from preview.tokens.create(expiresAt); drive the sandbox itself through this SDK.";
19
+ export declare const CODEGEN = "Runtime has no hosted edit or rerank models: apply edits with sandbox.fs.write, and rank files with your own model.";
20
+ export declare const SCHEDULES = "Schedule the call from your own scheduler (cron, a queue) and run it with sandbox.process.exec.";
21
+ export declare const SYSTEM = "Runtime keeps each sandbox's agent current itself; there is nothing to upgrade.";
22
+ export declare const DRIVES = "Use a Runtime volume: create one with runtime.volumes.create({ name }) and pass volumes: [{ name, mountPath }] at create.";
@@ -0,0 +1,34 @@
1
+ import { NotSupportedError } from "./errors.js";
2
+ export function unsupportedExport(name, alternative) {
3
+ const fail = () => {
4
+ throw new NotSupportedError(`Blaxel's ${name}`, alternative);
5
+ };
6
+ return new Proxy(function unsupported() { }, {
7
+ apply: fail,
8
+ construct: fail,
9
+ get: (_target, property) => {
10
+ // Let the module system, inspectors and promise checks look at it
11
+ // without tripping: only a real use throws.
12
+ if (typeof property === "symbol" || property === "then" || property === "prototype")
13
+ return undefined;
14
+ return fail();
15
+ },
16
+ });
17
+ }
18
+ /** A sandbox part Runtime has no counterpart for (`sandbox.sessions`,
19
+ * `sandbox.codegen`, ...): every method answers a rejected promise with
20
+ * NotSupportedError naming the alternative. */
21
+ export function unsupportedPart(name, alternative) {
22
+ return new Proxy({}, {
23
+ get: (_target, property) => {
24
+ if (typeof property === "symbol" || property === "then")
25
+ return undefined;
26
+ return () => Promise.reject(new NotSupportedError(`Blaxel's ${name}.${String(property)}`, alternative));
27
+ },
28
+ });
29
+ }
30
+ export const SESSIONS = "Share a port with sandbox.previews.create({ metadata: { name }, spec: { port, public: false } }) and a token from preview.tokens.create(expiresAt); drive the sandbox itself through this SDK.";
31
+ export const CODEGEN = "Runtime has no hosted edit or rerank models: apply edits with sandbox.fs.write, and rank files with your own model.";
32
+ export const SCHEDULES = "Schedule the call from your own scheduler (cron, a queue) and run it with sandbox.process.exec.";
33
+ export const SYSTEM = "Runtime keeps each sandbox's agent current itself; there is nothing to upgrade.";
34
+ export const DRIVES = "Use a Runtime volume: create one with runtime.volumes.create({ name }) and pass volumes: [{ name, mountPath }] at create.";
package/dist/cli.js CHANGED
@@ -46,10 +46,11 @@ Account
46
46
  A new API key for CI, approved by an owner in the browser
47
47
  referrals Your referral link: you both get up to $500
48
48
  compare --from <provider> [--days 30] What your usage would cost at e2b, daytona, vercel,
49
- modal, cloudflare, fly, lambda-microvms, freestyle,
50
- prime and others, and what you save
51
- switch [--from <provider>] Moving from a rival: record it before your first top-up,
52
- and that top-up is matched, up to $100
49
+ modal, cloudflare, fly, blaxel, lambda-microvms,
50
+ freestyle, prime and others, and what you save
51
+ switch [--from <provider>] Moving from e2b, daytona, vercel, modal, cloudflare, fly
52
+ or blaxel: record it before your first top-up, and
53
+ that top-up is matched, up to $100
53
54
  ls Everything you run, every product
54
55
  feedback "<text>" [--kind bug|missing_feature|competitor_gap|migration_blocker|docs|pricing|praise|other]
55
56
  [--detail <text>] [--competitor <name>] [--request-id <req_...>]
@@ -1077,6 +1078,7 @@ const SWITCH_FROM = {
1077
1078
  cloudflare: "cloudflare",
1078
1079
  fly: "fly",
1079
1080
  "fly-machines": "fly",
1081
+ blaxel: "blaxel",
1080
1082
  };
1081
1083
  const RIVAL_NAMES = {
1082
1084
  e2b: "E2B",
@@ -1085,6 +1087,7 @@ const RIVAL_NAMES = {
1085
1087
  modal: "Modal",
1086
1088
  cloudflare: "Cloudflare Sandbox",
1087
1089
  fly: "Fly",
1090
+ blaxel: "Blaxel",
1088
1091
  };
1089
1092
  /** A date as the CLI writes one: 23 September 2026. */
1090
1093
  const longDate = (iso) => new Date(iso).toLocaleDateString("en-GB", {
@@ -1176,7 +1179,7 @@ async function switching(command, argv, env, out) {
1176
1179
  return 0;
1177
1180
  }
1178
1181
  if (!from)
1179
- throw usage(`Name the rival: ${me} compare --from e2b (or daytona, vercel, modal, cloudflare, fly, fly-machines, lambda-microvms, freestyle, prime...).`);
1182
+ throw usage(`Name the rival: ${me} compare --from e2b (or daytona, vercel, modal, cloudflare, fly, fly-machines, blaxel, lambda-microvms, freestyle, prime...).`);
1180
1183
  /* The server holds the list of rivals it has rates for, and names them all
1181
1184
  when it refuses one, so a rival added there needs no new CLI. */
1182
1185
  const days = integer(args, "days");
@@ -3,9 +3,9 @@ import type { RequestOptions, Transport } from "../transport.js";
3
3
  * Machines, priced only at the size Fly publishes a rate for here. The server
4
4
  * prices every rival its rates table carries and names them when it refuses
5
5
  * one, so any string is sent as it is. */
6
- export type CompareProvider = "e2b" | "daytona" | "vercel" | "modal" | "cloudflare" | "fly" | "fly-machines" | (string & {});
6
+ export type CompareProvider = "e2b" | "daytona" | "vercel" | "modal" | "cloudflare" | "fly" | "fly-machines" | "blaxel" | (string & {});
7
7
  /** The rivals a switch can be recorded from. */
8
- export type SwitchProvider = "e2b" | "daytona" | "vercel" | "modal" | "cloudflare" | "fly";
8
+ export type SwitchProvider = "e2b" | "daytona" | "vercel" | "modal" | "cloudflare" | "fly" | "blaxel";
9
9
  export type SwitchingSummary = {
10
10
  /** False while the programme is paused. */
11
11
  enabled: boolean;
@@ -1,5 +1,5 @@
1
1
  import { RuntimeError } from "./errors.js";
2
- export declare const VERSION = "0.7.2";
2
+ export declare const VERSION = "0.8.0";
3
3
  export declare const DEFAULT_BASE_URL = "https://api.withruntime.com";
4
4
  export type RequestOptions = {
5
5
  /** Sent as Idempotency-Key. Omit it: the SDK makes one per call and keeps
package/dist/transport.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ConnectionError, errorFor, RuntimeError, WAITS_FOR_ROOM } from "./errors.js";
2
2
  import { describeRoute, envFetch, openWebSocket } from "./proxy.js";
3
- export const VERSION = "0.7.2";
3
+ export const VERSION = "0.8.0";
4
4
  export const DEFAULT_BASE_URL = "https://api.withruntime.com";
5
5
  export function apiOrigin(value) {
6
6
  const url = new URL(value);