@pablofdezr/microvm 0.1.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/LICENSE +202 -0
- package/README.md +133 -0
- package/dist/client.d.ts +335 -0
- package/dist/client.js +651 -0
- package/dist/types.d.ts +178 -0
- package/dist/types.gen.d.ts +1557 -0
- package/dist/types.gen.js +5 -0
- package/dist/types.js +8 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# microvm
|
|
2
|
+
|
|
3
|
+
TypeScript client for the microvm daemon: run untrusted code in Firecracker
|
|
4
|
+
microVMs. ESM, Node ≥ 18, zero runtime dependencies (it uses the built-in
|
|
5
|
+
`fetch`). Types are generated from the same OpenAPI spec as the server.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
npm install @pablofdezr/microvm
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { Client } from "@pablofdezr/microvm";
|
|
15
|
+
|
|
16
|
+
const client = new Client("http://127.0.0.1:8080", { token });
|
|
17
|
+
|
|
18
|
+
const sb = await client.sandboxes.create({ image: "python" });
|
|
19
|
+
try {
|
|
20
|
+
const exe = await client.run(sb.id, "python3", ["-c", "print('hi')"]);
|
|
21
|
+
console.log(exe.stdout);
|
|
22
|
+
} finally {
|
|
23
|
+
await client.sandboxes.delete(sb.id);
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Sandboxes vs tasks
|
|
28
|
+
|
|
29
|
+
- **Sandbox** — you hold a VM and run commands in it. `create` throws a capacity
|
|
30
|
+
error when the node is full (`err.isCapacity`), so backpressure is yours.
|
|
31
|
+
- **Task** — you hand work to the fleet; it never fails for capacity and waits
|
|
32
|
+
for a slot on any node, sized to the CPU and memory you request.
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
const task = await client.tasks.create({
|
|
36
|
+
image: "python",
|
|
37
|
+
cmd: "python3",
|
|
38
|
+
args: ["-c", "print(2 + 2)"],
|
|
39
|
+
vcpus: 2,
|
|
40
|
+
mem_mib: 1024,
|
|
41
|
+
priority: 7, // 0-10, higher first
|
|
42
|
+
});
|
|
43
|
+
const done = await client.tasks.wait(task.id);
|
|
44
|
+
console.log(done.stdout);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Optional fields are plain optional properties — no pointer wrappers.
|
|
48
|
+
|
|
49
|
+
A task has no live sandbox to upload to first, so its files travel inside
|
|
50
|
+
`create`, keyed by path and written before `cmd` runs. Pass the content as text
|
|
51
|
+
or bytes — it is base64-encoded for you, exactly as `files.write` does:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
await client.tasks.create({
|
|
55
|
+
image: "python",
|
|
56
|
+
cmd: "python3",
|
|
57
|
+
args: ["/app/main.py"],
|
|
58
|
+
files: { "/app/main.py": 'print("hi")' },
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Streaming output
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
for await (const frame of client.executions.stream(sb.id, exe.id)) {
|
|
66
|
+
process.stdout.write(frame.data);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Pagination
|
|
71
|
+
|
|
72
|
+
`all` is an async iterator that follows `has_more` to the end:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
for await (const sb of client.sandboxes.all({})) {
|
|
76
|
+
console.log(sb.id, sb.state);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`client.executions.all(sandboxId, {})` works the same way.
|
|
81
|
+
|
|
82
|
+
## Retries
|
|
83
|
+
|
|
84
|
+
Transient failures — a network error, or a 429/500/502/503/504 — are retried
|
|
85
|
+
with exponential backoff, full jitter, and any `Retry-After` honoured. Only
|
|
86
|
+
idempotent requests are retried: GET/PUT/DELETE always, and POST only when it
|
|
87
|
+
carries an idempotency key.
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
const client = new Client(baseURL, { token, maxRetries: 4 }); // default 2, 0 disables
|
|
91
|
+
|
|
92
|
+
await client.tasks.create(params, { idempotencyKey: crypto.randomUUID() });
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Errors
|
|
96
|
+
|
|
97
|
+
Failures are `APIError` with the API's `type`, `code`, `message`, `param` and
|
|
98
|
+
`requestId`. Branch on the guards:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
try {
|
|
102
|
+
await client.sandboxes.retrieve(id);
|
|
103
|
+
} catch (e) {
|
|
104
|
+
if (e instanceof APIError) {
|
|
105
|
+
if (e.isNotFound) { /* 404 */ }
|
|
106
|
+
if (e.isCapacity) { /* node full — consider a task */ }
|
|
107
|
+
if (e.isConflict) { /* e.g. executing in a stopped sandbox */ }
|
|
108
|
+
if (e.isForbidden) { /* key lacks permission (admin-only route) */ }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Observability
|
|
114
|
+
|
|
115
|
+
`onResponse` is called once per HTTP attempt — retries included:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
new Client(baseURL, {
|
|
119
|
+
token,
|
|
120
|
+
onResponse: (info) =>
|
|
121
|
+
console.log(`${info.method} ${info.path} attempt=${info.attempt} status=${info.status} ${info.durationMs}ms`),
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Tenants (admin)
|
|
126
|
+
|
|
127
|
+
Setting a tenant's storage policy needs an admin token; an ordinary key gets a
|
|
128
|
+
403 (`err.isForbidden`).
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
await admin.tenants.setLimit(tenantId, 500 * 1024 * 1024, "evict"); // or "preserve"
|
|
132
|
+
const t = await admin.tenants.retrieve(tenantId); // policy + live usage
|
|
133
|
+
```
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TypeScript client for the microvm API.
|
|
3
|
+
*
|
|
4
|
+
* The resource types in `types.gen.ts` are generated from `api/openapi.yaml`,
|
|
5
|
+
* the same file the server and the Go SDK are generated from. What is written
|
|
6
|
+
* here by hand is only what a generator does badly: the transport, typed
|
|
7
|
+
* errors, streaming, auto-pagination, and the few helpers that turn three calls
|
|
8
|
+
* into one.
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* const client = new Client("http://127.0.0.1:8080", { token });
|
|
12
|
+
*
|
|
13
|
+
* const sb = await client.sandboxes.create({ image: "python" });
|
|
14
|
+
* try {
|
|
15
|
+
* await client.files.write(sb.id, "main.py", 'print("hello")');
|
|
16
|
+
* const exe = await client.run(sb.id, "python3", ["main.py"]);
|
|
17
|
+
* console.log(exe.stdout);
|
|
18
|
+
* } finally {
|
|
19
|
+
* await client.sandboxes.delete(sb.id);
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import type { components } from "./types.gen.js";
|
|
24
|
+
type Schemas = components["schemas"];
|
|
25
|
+
export type Sandbox = Schemas["Sandbox"];
|
|
26
|
+
export type SandboxList = Schemas["SandboxList"];
|
|
27
|
+
export type SandboxState = Schemas["SandboxState"];
|
|
28
|
+
export type SandboxCreateParams = Schemas["SandboxCreateParams"];
|
|
29
|
+
export type Execution = Schemas["Execution"];
|
|
30
|
+
export type ExecutionList = Schemas["ExecutionList"];
|
|
31
|
+
export type ExecutionStatus = Schemas["ExecutionStatus"];
|
|
32
|
+
export type ExecutionCreateParams = Schemas["ExecutionCreateParams"];
|
|
33
|
+
export type ExecutionCancelParams = Schemas["ExecutionCancelParams"];
|
|
34
|
+
export type File = Schemas["File"];
|
|
35
|
+
export type Task = Schemas["Task"];
|
|
36
|
+
export type TaskStatus = Schemas["TaskStatus"];
|
|
37
|
+
export type TaskCreateParams = Omit<Schemas["TaskCreateParams"], "files"> & {
|
|
38
|
+
/**
|
|
39
|
+
* Files written into the sandbox before `cmd` runs, keyed by path. Pass the
|
|
40
|
+
* content as text or raw bytes; it is base64-encoded for you, exactly as
|
|
41
|
+
* `files.write` does — the wire form is base64, but that is not the caller's
|
|
42
|
+
* job to produce here any more than it is there.
|
|
43
|
+
*/
|
|
44
|
+
files?: Record<string, string | Uint8Array>;
|
|
45
|
+
};
|
|
46
|
+
export type Queue = Schemas["Queue"];
|
|
47
|
+
export type Image = Schemas["Image"];
|
|
48
|
+
export type ImageList = Schemas["ImageList"];
|
|
49
|
+
export type Tenant = Schemas["Tenant"];
|
|
50
|
+
export type TenantList = Schemas["TenantList"];
|
|
51
|
+
export type TenantUpdateParams = Schemas["TenantUpdateParams"];
|
|
52
|
+
export type TenantFullPolicy = Schemas["TenantFullPolicy"];
|
|
53
|
+
export type Health = Schemas["Health"];
|
|
54
|
+
export type Frame = Schemas["Frame"];
|
|
55
|
+
export type ErrorType = Schemas["ErrorType"];
|
|
56
|
+
/** How long an ordinary request may take. Streams and waits opt out. */
|
|
57
|
+
export declare const DEFAULT_TIMEOUT_MS = 30000;
|
|
58
|
+
/** Where a daemon listens unless told otherwise. */
|
|
59
|
+
export declare const DEFAULT_BASE_URL = "http://127.0.0.1:8080";
|
|
60
|
+
/** This SDK's version, sent in the User-Agent. */
|
|
61
|
+
export declare const SDK_VERSION = "0.1.0";
|
|
62
|
+
/** How many times a transient failure is retried unless told otherwise. */
|
|
63
|
+
export declare const DEFAULT_MAX_RETRIES = 2;
|
|
64
|
+
/** What an onResponse observer is told about one HTTP attempt. */
|
|
65
|
+
export interface RequestInfo {
|
|
66
|
+
method: string;
|
|
67
|
+
path: string;
|
|
68
|
+
/** 1 for the first try, 2 for the first retry, ... */
|
|
69
|
+
attempt: number;
|
|
70
|
+
/** 0 when the request never got a response. */
|
|
71
|
+
status: number;
|
|
72
|
+
error?: unknown;
|
|
73
|
+
durationMs: number;
|
|
74
|
+
}
|
|
75
|
+
export interface ClientOptions {
|
|
76
|
+
token?: string;
|
|
77
|
+
/** Overrides the global fetch, for tests or a custom agent. */
|
|
78
|
+
fetch?: typeof globalThis.fetch;
|
|
79
|
+
timeoutMs?: number;
|
|
80
|
+
/**
|
|
81
|
+
* How many times a transient failure -- a network error, or a
|
|
82
|
+
* 429/500/502/503/504 -- is retried before it is thrown, with exponential
|
|
83
|
+
* backoff and jitter and any Retry-After honoured. Only idempotent requests
|
|
84
|
+
* are retried: GET/PUT/DELETE always, POST only with an idempotency key.
|
|
85
|
+
* Defaults to DEFAULT_MAX_RETRIES; 0 disables.
|
|
86
|
+
*/
|
|
87
|
+
maxRetries?: number;
|
|
88
|
+
/** Called once per HTTP attempt -- retries included -- for logging or metrics. */
|
|
89
|
+
onResponse?: (info: RequestInfo) => void;
|
|
90
|
+
}
|
|
91
|
+
/** Per-call options. */
|
|
92
|
+
export interface RequestOptions {
|
|
93
|
+
signal?: AbortSignal;
|
|
94
|
+
/**
|
|
95
|
+
* Makes a create safe to retry.
|
|
96
|
+
*
|
|
97
|
+
* A request whose reply never arrived cannot be known to have failed, so a
|
|
98
|
+
* bare retry may run the work twice. With a key, the retry returns the
|
|
99
|
+
* original answer instead.
|
|
100
|
+
*/
|
|
101
|
+
idempotencyKey?: string;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* An error the API reported.
|
|
105
|
+
*
|
|
106
|
+
* `type` is what to branch on: it says what class of thing went wrong and so
|
|
107
|
+
* what to do about it. `code` says exactly which thing, for when that matters.
|
|
108
|
+
*/
|
|
109
|
+
export declare class APIError extends Error {
|
|
110
|
+
readonly status: number;
|
|
111
|
+
readonly type: ErrorType;
|
|
112
|
+
readonly code: string;
|
|
113
|
+
readonly param?: string | undefined;
|
|
114
|
+
readonly requestId?: string | undefined;
|
|
115
|
+
constructor(status: number, type: ErrorType, code: string, message: string, param?: string | undefined, requestId?: string | undefined);
|
|
116
|
+
/** The object does not exist. */
|
|
117
|
+
get isNotFound(): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* The node has no room.
|
|
120
|
+
*
|
|
121
|
+
* The one error worth retrying unchanged — and the signal to consider a task
|
|
122
|
+
* instead, since tasks wait for a slot anywhere in the fleet rather than
|
|
123
|
+
* failing.
|
|
124
|
+
*/
|
|
125
|
+
get isCapacity(): boolean;
|
|
126
|
+
/** The object is in a state that forbids the call. */
|
|
127
|
+
get isConflict(): boolean;
|
|
128
|
+
/**
|
|
129
|
+
* The key lacks permission — an ordinary token calling an admin-only endpoint,
|
|
130
|
+
* such as setting a tenant's policy. Distinct from a missing token: the
|
|
131
|
+
* request was authenticated, and refused.
|
|
132
|
+
*/
|
|
133
|
+
get isForbidden(): boolean;
|
|
134
|
+
}
|
|
135
|
+
export declare class Client {
|
|
136
|
+
#private;
|
|
137
|
+
readonly sandboxes: SandboxResource;
|
|
138
|
+
readonly executions: ExecutionResource;
|
|
139
|
+
readonly files: FileResource;
|
|
140
|
+
readonly tasks: TaskResource;
|
|
141
|
+
readonly queue: QueueResource;
|
|
142
|
+
readonly images: ImageResource;
|
|
143
|
+
readonly tenants: TenantResource;
|
|
144
|
+
constructor(baseURL?: string, opts?: ClientOptions);
|
|
145
|
+
/** @internal */
|
|
146
|
+
request(method: string, path: string, init?: {
|
|
147
|
+
body?: unknown;
|
|
148
|
+
query?: Record<string, string | number | undefined>;
|
|
149
|
+
opts?: RequestOptions;
|
|
150
|
+
/** Streams manage their own lifetime, so no timeout is imposed. */
|
|
151
|
+
noTimeout?: boolean;
|
|
152
|
+
}): Promise<Response>;
|
|
153
|
+
/** @internal */
|
|
154
|
+
json<T>(method: string, path: string, init?: Parameters<Client["request"]>[2]): Promise<T>;
|
|
155
|
+
/** Whether the daemon is up. Needs no token. */
|
|
156
|
+
health(opts?: RequestOptions): Promise<Health>;
|
|
157
|
+
/**
|
|
158
|
+
* Start a command and wait for it to finish.
|
|
159
|
+
*
|
|
160
|
+
* Check `err(exe)` afterwards: a non-zero exit is the code's own verdict,
|
|
161
|
+
* whereas a timeout or a vanished sandbox is not.
|
|
162
|
+
*/
|
|
163
|
+
run(sandboxID: string, cmd: string, args?: string[], opts?: RequestOptions): Promise<Execution>;
|
|
164
|
+
}
|
|
165
|
+
export type ListParams = {
|
|
166
|
+
limit?: number;
|
|
167
|
+
starting_after?: string;
|
|
168
|
+
ending_before?: string;
|
|
169
|
+
};
|
|
170
|
+
declare class SandboxResource {
|
|
171
|
+
private readonly c;
|
|
172
|
+
constructor(c: Client);
|
|
173
|
+
/**
|
|
174
|
+
* Boot a sandbox and wait for it to be ready.
|
|
175
|
+
*
|
|
176
|
+
* Throws a capacity error when the node is full — see `APIError.isCapacity`.
|
|
177
|
+
* That is by design: a sandbox is a reservation, so you are told at once
|
|
178
|
+
* rather than left waiting. Submit a task if you would rather wait.
|
|
179
|
+
*/
|
|
180
|
+
create(params: SandboxCreateParams, opts?: RequestOptions): Promise<Sandbox>;
|
|
181
|
+
retrieve(id: string, opts?: RequestOptions): Promise<Sandbox>;
|
|
182
|
+
/**
|
|
183
|
+
* Kill the sandbox and get back its final cost.
|
|
184
|
+
*
|
|
185
|
+
* Those numbers are sampled just before the kill and cannot be had after: the
|
|
186
|
+
* accounting dies with the VM. This reply is the only record of what the
|
|
187
|
+
* sandbox consumed.
|
|
188
|
+
*/
|
|
189
|
+
delete(id: string, opts?: RequestOptions): Promise<Sandbox>;
|
|
190
|
+
list(params?: ListParams & {
|
|
191
|
+
state?: SandboxState;
|
|
192
|
+
}, opts?: RequestOptions): Promise<SandboxList>;
|
|
193
|
+
/**
|
|
194
|
+
* Every sandbox, fetching pages as needed.
|
|
195
|
+
*
|
|
196
|
+
* Paging is mechanical and easy to get subtly wrong — forgetting `has_more`,
|
|
197
|
+
* or taking the cursor from the wrong end — and it is the same loop every
|
|
198
|
+
* time, so it lives here rather than in every caller.
|
|
199
|
+
*/
|
|
200
|
+
all(params?: ListParams & {
|
|
201
|
+
state?: SandboxState;
|
|
202
|
+
}, opts?: RequestOptions): AsyncGenerator<Sandbox>;
|
|
203
|
+
}
|
|
204
|
+
declare class ExecutionResource {
|
|
205
|
+
private readonly c;
|
|
206
|
+
constructor(c: Client);
|
|
207
|
+
/**
|
|
208
|
+
* Start a command and return at once, without waiting for it.
|
|
209
|
+
*
|
|
210
|
+
* The command belongs to the sandbox, not to this call: dropping the
|
|
211
|
+
* connection does not kill it. Follow it with `stream`, or collect it later
|
|
212
|
+
* with `retrieve`.
|
|
213
|
+
*/
|
|
214
|
+
create(sandboxID: string, params: ExecutionCreateParams, opts?: RequestOptions): Promise<Execution>;
|
|
215
|
+
/**
|
|
216
|
+
* An execution and everything it printed.
|
|
217
|
+
*
|
|
218
|
+
* Works after the sandbox is gone, which is the point: the output you most
|
|
219
|
+
* want is from the run that was killed.
|
|
220
|
+
*/
|
|
221
|
+
retrieve(sandboxID: string, executionID: string, opts?: RequestOptions): Promise<Execution>;
|
|
222
|
+
list(sandboxID: string, params?: ListParams, opts?: RequestOptions): Promise<ExecutionList>;
|
|
223
|
+
/**
|
|
224
|
+
* Signal a running execution.
|
|
225
|
+
*
|
|
226
|
+
* The signal reaches the whole process group, so a program that spawned
|
|
227
|
+
* children does not leave them behind. Defaults to SIGKILL. Cancelling
|
|
228
|
+
* something that already finished is not an error.
|
|
229
|
+
*/
|
|
230
|
+
cancel(sandboxID: string, executionID: string, params?: ExecutionCancelParams, opts?: RequestOptions): Promise<Execution>;
|
|
231
|
+
/**
|
|
232
|
+
* Follow an execution's output as it is produced.
|
|
233
|
+
*
|
|
234
|
+
* The stream replays from the beginning before it follows, so connecting late
|
|
235
|
+
* — or reconnecting after a dropped connection — loses nothing. Aborting the
|
|
236
|
+
* signal stops watching; the execution keeps running, because it belongs to
|
|
237
|
+
* its sandbox. To stop the execution itself, use `cancel`.
|
|
238
|
+
*
|
|
239
|
+
* ```ts
|
|
240
|
+
* for await (const frame of client.executions.stream(sbID, exeID)) {
|
|
241
|
+
* if (frame.type === "stdout") process.stdout.write(frameText(frame));
|
|
242
|
+
* }
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
stream(sandboxID: string, executionID: string, opts?: RequestOptions): AsyncGenerator<Frame>;
|
|
246
|
+
/**
|
|
247
|
+
* Wait for an execution to finish.
|
|
248
|
+
*
|
|
249
|
+
* Polls rather than streams: streaming is for showing output as it appears,
|
|
250
|
+
* waiting is for knowing the result, and polling survives a dropped
|
|
251
|
+
* connection without any work from the caller.
|
|
252
|
+
*/
|
|
253
|
+
wait(sandboxID: string, executionID: string, opts?: RequestOptions): Promise<Execution>;
|
|
254
|
+
}
|
|
255
|
+
declare class FileResource {
|
|
256
|
+
private readonly c;
|
|
257
|
+
constructor(c: Client);
|
|
258
|
+
/** Write a file into the sandbox, making parent directories. */
|
|
259
|
+
write(sandboxID: string, path: string, content: string | Uint8Array, opts?: RequestOptions): Promise<File>;
|
|
260
|
+
/** Download a file's bytes. */
|
|
261
|
+
retrieve(sandboxID: string, path: string, opts?: RequestOptions): Promise<Uint8Array>;
|
|
262
|
+
/** Download a file as text. */
|
|
263
|
+
readText(sandboxID: string, path: string, opts?: RequestOptions): Promise<string>;
|
|
264
|
+
}
|
|
265
|
+
declare class TaskResource {
|
|
266
|
+
private readonly c;
|
|
267
|
+
constructor(c: Client);
|
|
268
|
+
/**
|
|
269
|
+
* Queue work for the fleet.
|
|
270
|
+
*
|
|
271
|
+
* Unlike creating a sandbox this never fails for capacity: the task waits for
|
|
272
|
+
* a slot on any node. Use it for throughput, and a sandbox for several
|
|
273
|
+
* commands that share state.
|
|
274
|
+
*/
|
|
275
|
+
create(params: TaskCreateParams, opts?: RequestOptions): Promise<Task>;
|
|
276
|
+
retrieve(taskID: string, opts?: RequestOptions): Promise<Task>;
|
|
277
|
+
/** Wait for a task to have a result. */
|
|
278
|
+
wait(taskID: string, opts?: RequestOptions): Promise<Task>;
|
|
279
|
+
}
|
|
280
|
+
declare class QueueResource {
|
|
281
|
+
private readonly c;
|
|
282
|
+
constructor(c: Client);
|
|
283
|
+
/**
|
|
284
|
+
* The queue's depth and this node's slots.
|
|
285
|
+
*
|
|
286
|
+
* The depth is the fleet's; the slots are this node's alone. No node knows
|
|
287
|
+
* the fleet's capacity, which is what lets one be added without telling
|
|
288
|
+
* anything else.
|
|
289
|
+
*/
|
|
290
|
+
retrieve(opts?: RequestOptions): Promise<Queue>;
|
|
291
|
+
}
|
|
292
|
+
declare class ImageResource {
|
|
293
|
+
private readonly c;
|
|
294
|
+
constructor(c: Client);
|
|
295
|
+
list(opts?: RequestOptions): Promise<ImageList>;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* The `/v1/tenants` resource, and it is administrative: a tenant's storage cap
|
|
299
|
+
* is set by an operator, never by the code that runs under it. Updating needs an
|
|
300
|
+
* admin token; an ordinary key is refused with a 403 (see `APIError.isForbidden`).
|
|
301
|
+
*/
|
|
302
|
+
declare class TenantResource {
|
|
303
|
+
private readonly c;
|
|
304
|
+
constructor(c: Client);
|
|
305
|
+
/** Set a tenant's byte cap and full policy, replacing any previous one. */
|
|
306
|
+
update(tenantID: string, params: TenantUpdateParams, opts?: RequestOptions): Promise<Tenant>;
|
|
307
|
+
/**
|
|
308
|
+
* `update` for the common case: a byte cap and a policy. Pass `"preserve"` to
|
|
309
|
+
* reject writes when full, or `"evict"` to delete the oldest objects to make
|
|
310
|
+
* room. A `maxBytes` of 0 means unlimited.
|
|
311
|
+
*/
|
|
312
|
+
setLimit(tenantID: string, maxBytes: number, policy: TenantFullPolicy, opts?: RequestOptions): Promise<Tenant>;
|
|
313
|
+
/**
|
|
314
|
+
* A tenant's policy and its current usage, the usage read live from the bucket
|
|
315
|
+
* at call time (so it costs a listing — see `Tenant.usage_bytes`).
|
|
316
|
+
*/
|
|
317
|
+
retrieve(tenantID: string, opts?: RequestOptions): Promise<Tenant>;
|
|
318
|
+
/** Every configured tenant. A tenant with no policy is absent: it is unlimited. */
|
|
319
|
+
list(opts?: RequestOptions): Promise<TenantList>;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Why an execution did not simply run to completion, or null.
|
|
323
|
+
*
|
|
324
|
+
* A non-zero exit returns null: the process ran, and that is its own verdict
|
|
325
|
+
* rather than a failure of ours. The endings that are *not* the code's doing —
|
|
326
|
+
* a timeout, a cancel, a VM taken away, a command that never started — return
|
|
327
|
+
* an error, because those are the ones that must not be mistaken for a program
|
|
328
|
+
* choosing to fail.
|
|
329
|
+
*/
|
|
330
|
+
export declare function err(exe: Execution): Error | null;
|
|
331
|
+
/** A frame's bytes. */
|
|
332
|
+
export declare function frameBytes(frame: Frame): Uint8Array;
|
|
333
|
+
/** A frame's bytes as text. */
|
|
334
|
+
export declare function frameText(frame: Frame): string;
|
|
335
|
+
export {};
|