withruntime 0.12.0 → 0.14.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.
- package/README.md +3 -3
- package/README.src.md +2 -2
- package/dist/billing-cli.js +8 -4
- package/dist/blaxel/sandbox.d.ts +1 -1
- package/dist/blaxel/sandbox.js +1 -1
- package/dist/cli.d.ts +17 -2
- package/dist/cli.js +242 -69
- package/dist/credentials.d.ts +3 -0
- package/dist/credentials.js +15 -0
- package/dist/daytona/daytona.d.ts +1 -1
- package/dist/daytona/daytona.js +1 -1
- package/dist/e2b/sandbox.d.ts +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/machine.d.ts +15 -1
- package/dist/machine.js +26 -11
- package/dist/mcp.js +144 -20
- package/dist/products/api-keys.d.ts +73 -0
- package/dist/products/api-keys.js +23 -0
- package/dist/products/apps.d.ts +12 -31
- package/dist/products/apps.js +8 -113
- package/dist/products/balancers.d.ts +87 -0
- package/dist/products/balancers.js +8 -0
- package/dist/products/builds.d.ts +30 -0
- package/dist/products/builds.js +6 -0
- package/dist/products/index.d.ts +2 -0
- package/dist/products/index.js +2 -0
- package/dist/products/jobs.d.ts +2 -2
- package/dist/products/jobs.js +1 -1
- package/dist/products/observability.d.ts +7 -2
- package/dist/products/resources.d.ts +54 -0
- package/dist/products/resources.js +122 -0
- package/dist/products/secrets.d.ts +22 -0
- package/dist/products/secrets.js +8 -0
- package/dist/products/volumes.d.ts +3 -0
- package/dist/sandbox.d.ts +4 -0
- package/dist/sandbox.js +27 -2
- package/dist/transport.d.ts +4 -1
- package/dist/transport.js +25 -4
- package/dist/types.d.ts +9 -5
- package/dist/vercel/sandbox.d.ts +1 -1
- package/dist/vercel/sandbox.js +1 -1
- package/dist/wallet.d.ts +4 -1
- package/dist/wallet.js +2 -1
- package/package.json +2 -14
- package/dist/cli/logs.test.d.ts +0 -1
- package/dist/cli/logs.test.js +0 -272
- package/dist/products/logs.test.d.ts +0 -1
- package/dist/products/logs.test.js +0 -343
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { Transport } from "../transport.js";
|
|
2
|
+
import { type ResourceRecord } from "./resources.js";
|
|
3
|
+
/** Private until launch: load balancers and the inbound firewall
|
|
4
|
+
* (packages/cloud/src/api/products/balancers.ts). A change answers
|
|
5
|
+
* `applying`; it is `active` once every router serves it, and `waitingOn`
|
|
6
|
+
* names any router that does not yet. */
|
|
7
|
+
export type AddressState = "applying" | "active" | "failed" | "deleting" | "deleted";
|
|
8
|
+
type Common = ResourceRecord & {
|
|
9
|
+
name: string | null;
|
|
10
|
+
labels: Record<string, string>;
|
|
11
|
+
state: AddressState;
|
|
12
|
+
createdAt: number;
|
|
13
|
+
updatedAt: number;
|
|
14
|
+
deletedAt: number | null;
|
|
15
|
+
/** Routers that do not yet serve the newest change. */
|
|
16
|
+
waitingOn: string[];
|
|
17
|
+
};
|
|
18
|
+
/** A sandbox with the port it serves on, or an App or Function by id. */
|
|
19
|
+
export type BalancerTarget = {
|
|
20
|
+
id: string;
|
|
21
|
+
port?: number;
|
|
22
|
+
};
|
|
23
|
+
export type Balancer = Common & {
|
|
24
|
+
hostname: string;
|
|
25
|
+
targets: BalancerTarget[];
|
|
26
|
+
};
|
|
27
|
+
export type CreateBalancer = {
|
|
28
|
+
name?: string;
|
|
29
|
+
labels?: Record<string, string>;
|
|
30
|
+
/** Up to 1000, all the organization's own. */
|
|
31
|
+
targets?: BalancerTarget[];
|
|
32
|
+
};
|
|
33
|
+
export type UpdateBalancer = {
|
|
34
|
+
name?: string | null;
|
|
35
|
+
labels?: Record<string, string>;
|
|
36
|
+
/** Replaces the whole list. */
|
|
37
|
+
targets?: BalancerTarget[];
|
|
38
|
+
};
|
|
39
|
+
export type FirewallRule = Common & {
|
|
40
|
+
address: string;
|
|
41
|
+
effect: "allow" | "deny" | "limit";
|
|
42
|
+
source?: string;
|
|
43
|
+
ports?: string;
|
|
44
|
+
requests?: number;
|
|
45
|
+
seconds?: number;
|
|
46
|
+
burst?: number;
|
|
47
|
+
priority: number;
|
|
48
|
+
};
|
|
49
|
+
export type CreateFirewallRule = {
|
|
50
|
+
name?: string;
|
|
51
|
+
labels?: Record<string, string>;
|
|
52
|
+
/** The load balancer it guards. */
|
|
53
|
+
address: string;
|
|
54
|
+
priority?: number;
|
|
55
|
+
} & ({
|
|
56
|
+
effect: "allow" | "deny";
|
|
57
|
+
source?: string;
|
|
58
|
+
ports?: string;
|
|
59
|
+
} | {
|
|
60
|
+
effect: "limit";
|
|
61
|
+
requests: number;
|
|
62
|
+
seconds: number;
|
|
63
|
+
burst?: number;
|
|
64
|
+
});
|
|
65
|
+
export type UpdateFirewallRule = {
|
|
66
|
+
name?: string | null;
|
|
67
|
+
labels?: Record<string, string>;
|
|
68
|
+
};
|
|
69
|
+
export declare function balancers(t: Transport): {
|
|
70
|
+
base: string;
|
|
71
|
+
create: (input: CreateBalancer, options?: import("../transport.js").RequestOptions) => Promise<Balancer>;
|
|
72
|
+
get: (id: string, options?: import("./resources.js").ResourceGetOptions<"active" | "deleted" | "failed" | "deleting" | "applying">) => Promise<Balancer>;
|
|
73
|
+
list: (query?: import("./resources.js").ResourceListQuery<"active" | "deleted" | "failed" | "deleting" | "applying">, options?: import("../transport.js").RequestOptions) => Promise<import("../page.js").Page<Balancer>>;
|
|
74
|
+
update: (id: string, input: UpdateBalancer, options?: import("../transport.js").RequestOptions) => Promise<Balancer>;
|
|
75
|
+
wait: (id: string, target: "active" | "deleted" | "failed" | "deleting" | "applying", options?: import("./resources.js").ResourceWaitOptions) => Promise<Balancer>;
|
|
76
|
+
delete: (id: string, options?: import("./resources.js").ResourceDeleteOptions) => Promise<Balancer>;
|
|
77
|
+
};
|
|
78
|
+
export declare function firewall(t: Transport): {
|
|
79
|
+
base: string;
|
|
80
|
+
create: (input: CreateFirewallRule, options?: import("../transport.js").RequestOptions) => Promise<FirewallRule>;
|
|
81
|
+
get: (id: string, options?: import("./resources.js").ResourceGetOptions<"active" | "deleted" | "failed" | "deleting" | "applying">) => Promise<FirewallRule>;
|
|
82
|
+
list: (query?: import("./resources.js").ResourceListQuery<"active" | "deleted" | "failed" | "deleting" | "applying">, options?: import("../transport.js").RequestOptions) => Promise<import("../page.js").Page<FirewallRule>>;
|
|
83
|
+
update: (id: string, input: UpdateFirewallRule, options?: import("../transport.js").RequestOptions) => Promise<FirewallRule>;
|
|
84
|
+
wait: (id: string, target: "active" | "deleted" | "failed" | "deleting" | "applying", options?: import("./resources.js").ResourceWaitOptions) => Promise<FirewallRule>;
|
|
85
|
+
delete: (id: string, options?: import("./resources.js").ResourceDeleteOptions) => Promise<FirewallRule>;
|
|
86
|
+
};
|
|
87
|
+
export {};
|
|
@@ -16,16 +16,33 @@ type Resource = {
|
|
|
16
16
|
export type Build = Resource & {
|
|
17
17
|
repositoryId: string;
|
|
18
18
|
ref: string;
|
|
19
|
+
/** The branch whose move asked for it, when the caller said. */
|
|
20
|
+
branch: string | null;
|
|
19
21
|
commit: string | null;
|
|
20
22
|
state: BuildState;
|
|
21
23
|
reason: string | null;
|
|
24
|
+
/** Milliseconds per step once the runner is done (install, build, mirror,
|
|
25
|
+
* fork, snapshot, total, ...); null until then. */
|
|
26
|
+
timings: Record<string, number> | null;
|
|
27
|
+
/** The saved server an App deploys, once a build of a repository with a
|
|
28
|
+
* start command succeeded; null otherwise. */
|
|
29
|
+
outputSnapshotId: string | null;
|
|
22
30
|
};
|
|
23
31
|
export type CreateBuild = {
|
|
24
32
|
repositoryId: string;
|
|
25
33
|
ref: string;
|
|
34
|
+
/** The same branch and commit is one build; a newer build of the branch
|
|
35
|
+
* supersedes older ones not yet ended. */
|
|
36
|
+
branch?: string;
|
|
26
37
|
name?: string;
|
|
27
38
|
labels?: Record<string, string>;
|
|
28
39
|
};
|
|
40
|
+
/** A connected repository's settings changed in place; its id and builds
|
|
41
|
+
* stay. A field left out stays as it is; a null name clears it. */
|
|
42
|
+
export type ChangeRepository = Partial<Omit<ConnectRepository, "provider" | "name" | "labels">> & {
|
|
43
|
+
name?: string | null;
|
|
44
|
+
labels?: Record<string, string>;
|
|
45
|
+
};
|
|
29
46
|
export type ConnectRepository = {
|
|
30
47
|
provider: "github" | "git";
|
|
31
48
|
repository: string;
|
|
@@ -37,6 +54,14 @@ export type ConnectRepository = {
|
|
|
37
54
|
/** Leave out to choose by the lockfile (bun, pnpm, Yarn or npm). */
|
|
38
55
|
installCommand?: string | null;
|
|
39
56
|
buildCommand?: string | null;
|
|
57
|
+
/** Run after the build with PORT set; the build ends as this server,
|
|
58
|
+
* answering healthPath with 200 and saved with its memory. */
|
|
59
|
+
startCommand?: string | null;
|
|
60
|
+
port?: number;
|
|
61
|
+
healthPath?: string | null;
|
|
62
|
+
/** Settings install and build are given. Never a secret: anyone who reads
|
|
63
|
+
* the repository sees them. */
|
|
64
|
+
buildEnv?: Record<string, string>;
|
|
40
65
|
name?: string;
|
|
41
66
|
labels?: Record<string, string>;
|
|
42
67
|
};
|
|
@@ -49,6 +74,10 @@ export type Repository = Resource & {
|
|
|
49
74
|
rootDirectory: string;
|
|
50
75
|
installCommand: string | null;
|
|
51
76
|
buildCommand: string | null;
|
|
77
|
+
startCommand: string | null;
|
|
78
|
+
port: number;
|
|
79
|
+
healthPath: string | null;
|
|
80
|
+
buildEnv: Record<string, string>;
|
|
52
81
|
state: "connected" | "disconnected";
|
|
53
82
|
};
|
|
54
83
|
export type ListBuilds = {
|
|
@@ -120,6 +149,7 @@ export declare function builds(t: Transport): {
|
|
|
120
149
|
repositories: {
|
|
121
150
|
connect: (input: ConnectRepository, options?: RequestOptions) => Promise<Repository>;
|
|
122
151
|
list: (query?: ListRepositories, options?: RequestOptions) => Promise<Page<Repository>>;
|
|
152
|
+
update: (id: string, change: ChangeRepository, options?: RequestOptions) => Promise<Repository>;
|
|
123
153
|
disconnect: (id: string, options?: RequestOptions) => Promise<Repository>;
|
|
124
154
|
};
|
|
125
155
|
};
|
package/dist/products/builds.js
CHANGED
|
@@ -211,6 +211,12 @@ export function builds(t) {
|
|
|
211
211
|
repositories: {
|
|
212
212
|
connect: (input, options) => t.json({ method: "POST", path: "/v1/repositories", body: input, ...options }),
|
|
213
213
|
list: listRepositories,
|
|
214
|
+
update: (id, change, options) => t.json({
|
|
215
|
+
method: "POST",
|
|
216
|
+
path: `/v1/repositories/${encodeURIComponent(id)}:update`,
|
|
217
|
+
body: change,
|
|
218
|
+
...options,
|
|
219
|
+
}),
|
|
214
220
|
disconnect: (id, options) => t.json({
|
|
215
221
|
method: "DELETE",
|
|
216
222
|
path: `/v1/repositories/${encodeURIComponent(id)}`,
|
package/dist/products/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { audit } from "./audit.js";
|
|
|
13
13
|
import { sso } from "./sso.js";
|
|
14
14
|
import { switching } from "./switching.js";
|
|
15
15
|
import { limits } from "./limits.js";
|
|
16
|
+
import { keys } from "./api-keys.js";
|
|
16
17
|
import { volumes } from "./volumes.js";
|
|
17
18
|
import { jobs } from "./jobs.js";
|
|
18
19
|
import { sandboxMounts } from "./mounts.js";
|
|
@@ -27,6 +28,7 @@ export declare const clientExtensions: {
|
|
|
27
28
|
referrals: typeof referrals;
|
|
28
29
|
billing: typeof billing;
|
|
29
30
|
limits: typeof limits;
|
|
31
|
+
keys: typeof keys;
|
|
30
32
|
switching: typeof switching;
|
|
31
33
|
secrets: typeof secrets;
|
|
32
34
|
webhooks: typeof webhooks;
|
package/dist/products/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { audit } from "./audit.js";
|
|
|
11
11
|
import { sso } from "./sso.js";
|
|
12
12
|
import { switching } from "./switching.js";
|
|
13
13
|
import { limits } from "./limits.js";
|
|
14
|
+
import { keys } from "./api-keys.js";
|
|
14
15
|
import { volumes } from "./volumes.js";
|
|
15
16
|
import { jobs } from "./jobs.js";
|
|
16
17
|
import { sandboxMounts } from "./mounts.js";
|
|
@@ -40,6 +41,7 @@ export const clientExtensions = {
|
|
|
40
41
|
referrals,
|
|
41
42
|
billing,
|
|
42
43
|
limits,
|
|
44
|
+
keys,
|
|
43
45
|
switching,
|
|
44
46
|
secrets,
|
|
45
47
|
webhooks,
|
package/dist/products/jobs.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Page } from "../page.js";
|
|
2
2
|
import type { RequestOptions, Transport } from "../transport.js";
|
|
3
3
|
/** A run's sandbox. Every field has a default; a run is a sandbox of this
|
|
4
|
-
* size, on the
|
|
4
|
+
* size, on the free usage first and then on credit at the sandbox rates. */
|
|
5
5
|
export type JobCompute = {
|
|
6
6
|
region: string;
|
|
7
7
|
vcpu: number;
|
|
@@ -176,7 +176,7 @@ export declare function jobBody(input: CreateJob): Record<string, unknown>;
|
|
|
176
176
|
* request id, and says no retry will change it. */
|
|
177
177
|
export declare function switchedOff(product: string): (error: unknown) => never;
|
|
178
178
|
/** Scheduled jobs: a command run in a fresh sandbox, once or on a cron
|
|
179
|
-
* schedule. Each run is a sandbox, paid for as one: from the
|
|
179
|
+
* schedule. Each run is a sandbox, paid for as one: from the free usage first while the run fits the limits without credit, then from credit
|
|
180
180
|
* at the sandbox rates, through the same holds and spending limits.
|
|
181
181
|
*
|
|
182
182
|
* const job = await runtime.jobs.create({
|
package/dist/products/jobs.js
CHANGED
|
@@ -131,7 +131,7 @@ export function switchedOff(product) {
|
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
133
|
/** Scheduled jobs: a command run in a fresh sandbox, once or on a cron
|
|
134
|
-
* schedule. Each run is a sandbox, paid for as one: from the
|
|
134
|
+
* schedule. Each run is a sandbox, paid for as one: from the free usage first while the run fits the limits without credit, then from credit
|
|
135
135
|
* at the sandbox rates, through the same holds and spending limits.
|
|
136
136
|
*
|
|
137
137
|
* const job = await runtime.jobs.create({
|
|
@@ -35,7 +35,7 @@ export type SandboxMetrics = {
|
|
|
35
35
|
latest: MetricPoint | null;
|
|
36
36
|
points: MetricPoint[];
|
|
37
37
|
};
|
|
38
|
-
export type WebhookEventType = "sandbox.created" | "sandbox.running" | "sandbox.paused" | "sandbox.woken" | "sandbox.stopped" | "sandbox.start_failed" | "sandbox.wake_failed" | "sandbox.host_unreachable" | "sandbox.host_reachable" | "sandbox.recovered" | "sandbox.recovery_failed" | "sandbox.files.changed" | "sandbox.out_of_memory" | "sandbox.secret_not_applied" | "snapshot.ready" | "snapshot.failed" | "snapshot.deleted" | "volume.ready" | "volume.failed" | "volume.deleted" | "egress_secret.created" | "egress_secret.updated" | "egress_secret.deleted" | "upstream_proxy.set" | "upstream_proxy.removed";
|
|
38
|
+
export type WebhookEventType = "sandbox.created" | "sandbox.running" | "sandbox.paused" | "sandbox.woken" | "sandbox.stopped" | "sandbox.start_failed" | "sandbox.wake_failed" | "sandbox.host_unreachable" | "sandbox.host_reachable" | "sandbox.recovered" | "sandbox.recovery_failed" | "sandbox.files.changed" | "sandbox.out_of_memory" | "sandbox.secret_not_applied" | "snapshot.ready" | "snapshot.failed" | "snapshot.deleted" | "volume.ready" | "volume.failed" | "volume.deleted" | "volume.recovered" | "egress_secret.created" | "egress_secret.updated" | "egress_secret.deleted" | "upstream_proxy.set" | "upstream_proxy.removed";
|
|
39
39
|
/** An event as /v1/events lists it and a webhook delivers it. */
|
|
40
40
|
export type RuntimeEvent = {
|
|
41
41
|
id: string;
|
|
@@ -113,7 +113,7 @@ export type UsageExportRow = {
|
|
|
113
113
|
name: string | null;
|
|
114
114
|
kind: string;
|
|
115
115
|
meter: string;
|
|
116
|
-
/** credit, the trial's hours, or the month's
|
|
116
|
+
/** credit, the trial's hours, or the month's free usage. */
|
|
117
117
|
fundedBy: "credit" | "trial" | "allowance";
|
|
118
118
|
startsAt: string;
|
|
119
119
|
endsAt: string;
|
|
@@ -128,12 +128,17 @@ export type UsageExportRow = {
|
|
|
128
128
|
/** Exact JSON text, preserving integers beyond JavaScript's number range. */
|
|
129
129
|
measurementsJson: string | null;
|
|
130
130
|
evidence: "measured" | "partial" | "missing" | null;
|
|
131
|
+
/** The resource is deleted and every charge it will ever have settled
|
|
132
|
+
* before finalThrough. A volume's backups bill on until retention ends. */
|
|
133
|
+
final: boolean;
|
|
131
134
|
};
|
|
132
135
|
export type UsageExportPage = {
|
|
133
136
|
since: string;
|
|
134
137
|
until: string;
|
|
135
138
|
data: UsageExportRow[];
|
|
136
139
|
nextCursor: string | null;
|
|
140
|
+
/** No row with settledAt before this can still appear or change. */
|
|
141
|
+
finalThrough: string | null;
|
|
137
142
|
/** Concatenate pages in order; only the first page includes the header. */
|
|
138
143
|
csv: string;
|
|
139
144
|
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Page } from "../page.js";
|
|
2
|
+
import type { RequestOptions, Transport } from "../transport.js";
|
|
3
|
+
/** The five verbs every resource product shares (CLOUD.md section 4, "One
|
|
4
|
+
* shape for every interface"; packages/cloud/src/api/resource-product.ts),
|
|
5
|
+
* and following one to a state. A product builds its client on these and
|
|
6
|
+
* adds only its own verbs. */
|
|
7
|
+
export type ResourceRecord = {
|
|
8
|
+
id: string;
|
|
9
|
+
state: string;
|
|
10
|
+
error: {
|
|
11
|
+
code: string;
|
|
12
|
+
message: string;
|
|
13
|
+
} | null;
|
|
14
|
+
};
|
|
15
|
+
export type ResourceListQuery<S extends string = string> = {
|
|
16
|
+
name?: string;
|
|
17
|
+
/** Repeated key:value filters; all must match. */
|
|
18
|
+
label?: readonly string[];
|
|
19
|
+
/** Defaults to the product's live states. */
|
|
20
|
+
state?: readonly S[];
|
|
21
|
+
/** 1 to 100; defaults to 50. */
|
|
22
|
+
limit?: number;
|
|
23
|
+
cursor?: string;
|
|
24
|
+
};
|
|
25
|
+
export type ResourceGetOptions<S extends string = string> = RequestOptions & {
|
|
26
|
+
waitFor?: S;
|
|
27
|
+
/** Server wait, 1 to 60 seconds; defaults to 30. Check the returned state. */
|
|
28
|
+
timeoutSeconds?: number;
|
|
29
|
+
};
|
|
30
|
+
export type ResourceWaitOptions = RequestOptions & {
|
|
31
|
+
/** Whole wait, including retries; defaults to 300000 ms. 0 disables it. */
|
|
32
|
+
timeoutMs?: number;
|
|
33
|
+
/** Delay between reads; defaults to 250 ms. */
|
|
34
|
+
pollMs?: number;
|
|
35
|
+
};
|
|
36
|
+
export type ResourceDeleteOptions = ResourceWaitOptions & {
|
|
37
|
+
wait?: boolean;
|
|
38
|
+
};
|
|
39
|
+
export declare const enc: typeof encodeURIComponent;
|
|
40
|
+
/** One deadline covers the write, retries and every read. A timed-out delete
|
|
41
|
+
* can be retried after a process restart with this same key. */
|
|
42
|
+
export declare function following<T>(options: ResourceWaitOptions, work: (request: RequestOptions, pause: () => Promise<void>) => Promise<T>): Promise<T>;
|
|
43
|
+
/** A resource product's client at `/v1/<path>`. A wait ends at its target
|
|
44
|
+
* or at any of `settled`, which answers with the resource's error. */
|
|
45
|
+
export declare function resource<R extends ResourceRecord, C, U, S extends string = R["state"]>(t: Transport, path: string, settled: readonly S[]): {
|
|
46
|
+
base: string;
|
|
47
|
+
/** Defaults belong to the API, so the SDK does not override them. */
|
|
48
|
+
create: (input: C, options?: RequestOptions) => Promise<R>;
|
|
49
|
+
get: (id: string, options?: ResourceGetOptions<S>) => Promise<R>;
|
|
50
|
+
list: (query?: ResourceListQuery<S>, options?: RequestOptions) => Promise<Page<R>>;
|
|
51
|
+
update: (id: string, input: U, options?: RequestOptions) => Promise<R>;
|
|
52
|
+
wait: (id: string, target: S, options?: ResourceWaitOptions) => Promise<R>;
|
|
53
|
+
delete: (id: string, options?: ResourceDeleteOptions) => Promise<R>;
|
|
54
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { RuntimeError } from "../errors.js";
|
|
2
|
+
import { Page } from "../page.js";
|
|
3
|
+
import { pause } from "../wait.js";
|
|
4
|
+
export const enc = encodeURIComponent;
|
|
5
|
+
function invalid(field) {
|
|
6
|
+
return new RuntimeError({
|
|
7
|
+
code: "invalid_request",
|
|
8
|
+
status: 0,
|
|
9
|
+
message: `${field} must be a finite, nonnegative number.`,
|
|
10
|
+
hint: "Fix the option and call again; nothing was sent.",
|
|
11
|
+
details: { field },
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/** One deadline covers the write, retries and every read. A timed-out delete
|
|
15
|
+
* can be retried after a process restart with this same key. */
|
|
16
|
+
export async function following(options, work) {
|
|
17
|
+
const { pollMs = 250, timeoutMs = 300_000, signal, ...rest } = options;
|
|
18
|
+
if (!Number.isFinite(pollMs) || pollMs < 0)
|
|
19
|
+
throw invalid("pollMs");
|
|
20
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 0)
|
|
21
|
+
throw invalid("timeoutMs");
|
|
22
|
+
const deadline = new AbortController();
|
|
23
|
+
const combined = signal ? AbortSignal.any([signal, deadline.signal]) : deadline.signal;
|
|
24
|
+
const timer = timeoutMs > 0 ? setTimeout(() => deadline.abort(), timeoutMs) : undefined;
|
|
25
|
+
const request = { ...rest, timeoutMs: 0, signal: combined };
|
|
26
|
+
const poll = () => pause(pollMs, combined);
|
|
27
|
+
try {
|
|
28
|
+
combined.throwIfAborted();
|
|
29
|
+
const result = await work(request, poll);
|
|
30
|
+
combined.throwIfAborted();
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
catch (cause) {
|
|
34
|
+
if (!combined.aborted) {
|
|
35
|
+
// A read failure still belongs to the write that started this wait.
|
|
36
|
+
// Keep its key so a caller can recover after losing the reply.
|
|
37
|
+
if (cause instanceof RuntimeError && rest.idempotencyKey && !cause.idempotencyKey)
|
|
38
|
+
throw new RuntimeError({
|
|
39
|
+
message: cause.message,
|
|
40
|
+
code: cause.code,
|
|
41
|
+
status: cause.status,
|
|
42
|
+
requestId: cause.requestId,
|
|
43
|
+
hint: cause.hint,
|
|
44
|
+
details: cause.details,
|
|
45
|
+
retryAfterMs: cause.retryAfterMs,
|
|
46
|
+
idempotencyKey: rest.idempotencyKey,
|
|
47
|
+
cause,
|
|
48
|
+
});
|
|
49
|
+
throw cause;
|
|
50
|
+
}
|
|
51
|
+
throw new RuntimeError({
|
|
52
|
+
code: "timeout",
|
|
53
|
+
status: 0,
|
|
54
|
+
message: "Waiting was cancelled or reached its deadline; the resource may still change.",
|
|
55
|
+
hint: "Read the resource to resume waiting. Retry a write with the same idempotencyKey.",
|
|
56
|
+
...(rest.idempotencyKey === undefined ? {} : { idempotencyKey: rest.idempotencyKey }),
|
|
57
|
+
cause,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** A resource product's client at `/v1/<path>`. A wait ends at its target
|
|
65
|
+
* or at any of `settled`, which answers with the resource's error. */
|
|
66
|
+
export function resource(t, path, settled) {
|
|
67
|
+
const base = `/v1/${path}`;
|
|
68
|
+
const get = (id, options = {}) => {
|
|
69
|
+
const { waitFor, timeoutSeconds, ...request } = options;
|
|
70
|
+
return t.json({
|
|
71
|
+
method: "GET",
|
|
72
|
+
path: `${base}/${enc(id)}`,
|
|
73
|
+
query: { waitFor, timeoutSeconds },
|
|
74
|
+
...request,
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
const list = async (query = {}, options) => {
|
|
78
|
+
const page = await t.json({
|
|
79
|
+
method: "GET",
|
|
80
|
+
path: base,
|
|
81
|
+
query,
|
|
82
|
+
...options,
|
|
83
|
+
});
|
|
84
|
+
return new Page(page.data, page.nextCursor, (cursor) => list({ ...query, cursor }, options));
|
|
85
|
+
};
|
|
86
|
+
const follow = async (id, target, request, pause, initial) => {
|
|
87
|
+
let record = initial ?? (await get(id, request));
|
|
88
|
+
for (;;) {
|
|
89
|
+
if (record.state === target)
|
|
90
|
+
return record;
|
|
91
|
+
if (settled.includes(record.state))
|
|
92
|
+
throw new RuntimeError({
|
|
93
|
+
code: record.error?.code ?? "unexpected_state",
|
|
94
|
+
status: 0,
|
|
95
|
+
message: record.error?.message ?? `Expected ${target}, but ${id} reached ${record.state}.`,
|
|
96
|
+
hint: "Read the resource and inspect its error before starting another change.",
|
|
97
|
+
details: { id, state: record.state, target, resource: record },
|
|
98
|
+
...(request.idempotencyKey === undefined
|
|
99
|
+
? {}
|
|
100
|
+
: { idempotencyKey: request.idempotencyKey }),
|
|
101
|
+
});
|
|
102
|
+
await pause();
|
|
103
|
+
record = await get(id, request);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
base,
|
|
108
|
+
/** Defaults belong to the API, so the SDK does not override them. */
|
|
109
|
+
create: (input, options) => t.json({ method: "POST", path: base, body: input, ...options }),
|
|
110
|
+
get,
|
|
111
|
+
list,
|
|
112
|
+
update: (id, input, options) => t.json({ method: "POST", path: `${base}/${enc(id)}:update`, body: input, ...options }),
|
|
113
|
+
wait: (id, target, options = {}) => following(options, (request, pause) => follow(id, target, request, pause)),
|
|
114
|
+
delete: (id, options = {}) => {
|
|
115
|
+
const { wait = false, pollMs, ...request } = options;
|
|
116
|
+
const remove = (rest) => t.json({ method: "DELETE", path: `${base}/${enc(id)}`, ...rest });
|
|
117
|
+
if (!wait)
|
|
118
|
+
return remove(request);
|
|
119
|
+
return following({ ...request, pollMs, idempotencyKey: request.idempotencyKey ?? crypto.randomUUID() }, async (rest, pause) => follow(id, "deleted", rest, pause, await remove(rest)));
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -9,6 +9,17 @@ export type SecretRule = {
|
|
|
9
9
|
methods?: string[];
|
|
10
10
|
paths: string[];
|
|
11
11
|
};
|
|
12
|
+
/** Which sandboxes get a secret: those whose labels hold every pair of any one
|
|
13
|
+
* selector in `labels` (including sandboxes created or labelled later), and
|
|
14
|
+
* those named in `sandboxes`. Any other sandbox of the organization sees no
|
|
15
|
+
* placeholder, and its requests never carry the value. Absent on a secret:
|
|
16
|
+
* every sandbox of the organization. */
|
|
17
|
+
export type SecretScope = {
|
|
18
|
+
/** 1 to 8 selectors of 1 to 8 key/value pairs: `[{ role: "hub" }]`. */
|
|
19
|
+
labels?: Array<Record<string, string>>;
|
|
20
|
+
/** 1 to 100 sandbox ids of the organization. */
|
|
21
|
+
sandboxes?: string[];
|
|
22
|
+
};
|
|
12
23
|
/** A secret your sandboxes use without seeing it. Every sandbox of your
|
|
13
24
|
* organization has an environment variable of the secret's name holding
|
|
14
25
|
* `placeholder`; the egress proxy sets `header` on every HTTPS request to
|
|
@@ -22,6 +33,8 @@ export type Secret = {
|
|
|
22
33
|
header?: string;
|
|
23
34
|
format?: string;
|
|
24
35
|
rules?: SecretRule[];
|
|
36
|
+
/** Absent: every sandbox of the organization. */
|
|
37
|
+
only?: SecretScope;
|
|
25
38
|
placeholder: string;
|
|
26
39
|
valueBytes: number;
|
|
27
40
|
createdAt: string;
|
|
@@ -45,6 +58,10 @@ export type SetSecret = {
|
|
|
45
58
|
* Absent: every request to the hosts; replacing a secret without rules
|
|
46
59
|
* clears them. */
|
|
47
60
|
rules?: SecretRule[];
|
|
61
|
+
/** Only these sandboxes get it. Absent when replacing a secret: the scope it
|
|
62
|
+
* has stays, so a new value never widens who gets it. `{}`: every sandbox of
|
|
63
|
+
* the organization again. */
|
|
64
|
+
only?: SecretScope;
|
|
48
65
|
/** Also keep a copy for scheduled jobs, which a job puts into its run's
|
|
49
66
|
* environment (`jobs.create({ secrets: [...] })`). Unlike the sandboxes'
|
|
50
67
|
* copy, the jobs copy is yours rather than the account's, versioned, and can
|
|
@@ -98,6 +115,11 @@ export declare function secrets(t: Transport): {
|
|
|
98
115
|
enforced: boolean;
|
|
99
116
|
}>>;
|
|
100
117
|
};
|
|
118
|
+
/** Changes which sandboxes get a secret, without its value: `{}` is every
|
|
119
|
+
* sandbox of the organization again. */
|
|
120
|
+
scope: (name: string, only: SecretScope, options?: RequestOptions) => Promise<Secret & {
|
|
121
|
+
enforced: boolean;
|
|
122
|
+
}>;
|
|
101
123
|
/** The sandboxes' secrets: names, hosts and placeholders. Never values.
|
|
102
124
|
* `all()` includes the jobs copies. */
|
|
103
125
|
list: (options?: RequestOptions) => Promise<Secret[]>;
|
package/dist/products/secrets.js
CHANGED
|
@@ -153,6 +153,14 @@ export function secrets(t) {
|
|
|
153
153
|
* at once store both; if only the first lands, `SecretPartlyStoredError`
|
|
154
154
|
* says so and the same call again finishes it. */
|
|
155
155
|
set,
|
|
156
|
+
/** Changes which sandboxes get a secret, without its value: `{}` is every
|
|
157
|
+
* sandbox of the organization again. */
|
|
158
|
+
scope: (name, only, options) => t.json({
|
|
159
|
+
method: "PUT",
|
|
160
|
+
path: `${path(name)}/sandboxes`,
|
|
161
|
+
body: only,
|
|
162
|
+
...options,
|
|
163
|
+
}),
|
|
156
164
|
/** The sandboxes' secrets: names, hosts and placeholders. Never values.
|
|
157
165
|
* `all()` includes the jobs copies. */
|
|
158
166
|
list,
|
|
@@ -90,6 +90,9 @@ export type VolumeBackup = {
|
|
|
90
90
|
* the host never began; back it up again. */
|
|
91
91
|
errorCode?: string | null;
|
|
92
92
|
createdAt: string;
|
|
93
|
+
/** When its copy off the volume's server began; null while it waits its
|
|
94
|
+
* turn behind other copies there. */
|
|
95
|
+
copyStartedAt?: string | null;
|
|
93
96
|
readyAt: string | null;
|
|
94
97
|
expiresAt: string;
|
|
95
98
|
};
|
package/dist/sandbox.d.ts
CHANGED
|
@@ -8,6 +8,10 @@ import type { StreamUploadSource } from "./file-source.js";
|
|
|
8
8
|
import { Machine, type MachineProduct } from "./machine.js";
|
|
9
9
|
/** Sandboxes, as a product of the machine engine (machine.ts). */
|
|
10
10
|
export declare const SANDBOXES: MachineProduct;
|
|
11
|
+
/** How long past its timeoutMs a streamed exec waits for an exit that never came. */
|
|
12
|
+
export declare const EXEC_EXIT_GRACE: {
|
|
13
|
+
ms: number;
|
|
14
|
+
};
|
|
11
15
|
/** A running (or stopped) sandbox. Every method is one API call or a short
|
|
12
16
|
* sequence of them; `await using sbx = await Sandbox.create()` deletes it at
|
|
13
17
|
* the end of the block, or stops it, keeping its disk, if created with
|
package/dist/sandbox.js
CHANGED
|
@@ -87,6 +87,8 @@ function commandBody(command, options) {
|
|
|
87
87
|
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
|
+
/** How long past its timeoutMs a streamed exec waits for an exit that never came. */
|
|
91
|
+
export const EXEC_EXIT_GRACE = { ms: 120_000 };
|
|
90
92
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
|
91
93
|
export class Sandbox extends Machine {
|
|
92
94
|
#t;
|
|
@@ -147,8 +149,22 @@ export class Sandbox extends Machine {
|
|
|
147
149
|
let exit;
|
|
148
150
|
let processId;
|
|
149
151
|
let dropped = false;
|
|
152
|
+
// A command with a time limit ends by it, the server killing it if need
|
|
153
|
+
// be; an exit that never comes after (a follow that resumes forever)
|
|
154
|
+
// ends the wait past the limit and EXEC_EXIT_GRACE.ms, as a timeout,
|
|
155
|
+
// its process named and sent SIGTERM. The hub's release train waited
|
|
156
|
+
// 29 min on a 10 min command (15 October 2026).
|
|
157
|
+
// A timer cleared when the call ends, so none outlives the exec.
|
|
158
|
+
const deadline = options.timeoutMs === undefined ? undefined : new AbortController();
|
|
159
|
+
const timer = deadline
|
|
160
|
+
? setTimeout(() => deadline.abort(), options.timeoutMs + EXEC_EXIT_GRACE.ms)
|
|
161
|
+
: undefined;
|
|
162
|
+
timer?.unref?.();
|
|
163
|
+
const signal = deadline && options.signal
|
|
164
|
+
? AbortSignal.any([options.signal, deadline.signal])
|
|
165
|
+
: (deadline?.signal ?? options.signal);
|
|
150
166
|
try {
|
|
151
|
-
for await (const event of this.execStream(command, options)) {
|
|
167
|
+
for await (const event of this.execStream(command, { ...options, signal })) {
|
|
152
168
|
if (event.type === "start")
|
|
153
169
|
processId = event.processId;
|
|
154
170
|
else if (event.type === "stdout" || event.type === "stderr") {
|
|
@@ -162,7 +178,16 @@ export class Sandbox extends Machine {
|
|
|
162
178
|
}
|
|
163
179
|
}
|
|
164
180
|
catch (error) {
|
|
165
|
-
|
|
181
|
+
if (!deadline?.signal.aborted || options.signal?.aborted)
|
|
182
|
+
throw await this.#unfinished(error, processId, signal);
|
|
183
|
+
if (processId)
|
|
184
|
+
await new Process(this.#t, this.id, { id: processId })
|
|
185
|
+
.kill("SIGTERM")
|
|
186
|
+
.catch(() => { });
|
|
187
|
+
exit = { type: "exit", exitCode: null, timedOut: true };
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
clearTimeout(timer);
|
|
166
191
|
}
|
|
167
192
|
result = {
|
|
168
193
|
exitCode: exit?.exitCode ?? null,
|
package/dist/transport.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RuntimeError } from "./errors.js";
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.14.0";
|
|
3
3
|
export declare const DEFAULT_BASE_URL = "https://api.withruntime.com";
|
|
4
4
|
/** Runtime's API as code inside a Runtime sandbox reaches it: the sandbox's
|
|
5
5
|
* own host sends each request on to DEFAULT_BASE_URL over HTTPS. The API runs
|
|
@@ -7,6 +7,9 @@ export declare const DEFAULT_BASE_URL = "https://api.withruntime.com";
|
|
|
7
7
|
* because the hop never leaves the machine: it goes from the program to the
|
|
8
8
|
* guest's own proxy and over the sandbox's private channel to its host. */
|
|
9
9
|
export declare const SANDBOX_BASE_URL = "http://runtime.internal";
|
|
10
|
+
/** How a Runtime secret's placeholder starts, the value a sandbox sees in its
|
|
11
|
+
* place (packages/cloud-guide/docs/security.md). */
|
|
12
|
+
export declare const PLACEHOLDER = "rtsec_";
|
|
10
13
|
/** Every Runtime sandbox has this file; guest-net.py keeps it current. Tests
|
|
11
14
|
* move it, so the machine running them does not decide what they see. */
|
|
12
15
|
export declare const sandboxMarker: {
|