@sandbox-kit/sandbox 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -46
- package/dist/client/client.d.ts +7 -126
- package/dist/client/client.js +37 -564
- package/dist/client/errors.d.ts +35 -0
- package/dist/client/errors.js +88 -0
- package/dist/client/operation.d.ts +12 -0
- package/dist/client/operation.js +73 -0
- package/dist/client/sandbox.d.ts +33 -0
- package/dist/client/sandbox.js +160 -0
- package/dist/client/types.d.ts +63 -0
- package/dist/client/types.js +3 -0
- package/dist/client/validation.d.ts +9 -0
- package/dist/client/validation.js +221 -0
- package/dist/contracts/sandbox_data_contracts.d.ts +10 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/internal/net_validate.d.ts +4 -0
- package/dist/internal/net_validate.js +53 -0
- package/package.json +7 -2
package/dist/client/client.js
CHANGED
|
@@ -1,390 +1,15 @@
|
|
|
1
|
-
// Client
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
import { pathToString } from "@bufbuild/protobuf/reflect";
|
|
5
|
-
import { durationFromMs } from "@bufbuild/protobuf/wkt";
|
|
6
|
-
import { createValidator } from "@bufbuild/protovalidate";
|
|
7
|
-
import { ConfigSchema, } from "@sandbox-kit/contracts/client";
|
|
8
|
-
import { CreateOptionsSchema, ErrorKind as ProtoErrorKind, } from "@sandbox-kit/contracts/sandbox";
|
|
9
|
-
import { SandboxState, } from "#contracts/sandbox_data_contracts.js";
|
|
10
|
-
import { materializeDurationFields, } from "#internal/duration_ms.js";
|
|
11
|
-
import { metadataObjectFromMap } from "#metadata/metadata_object.js";
|
|
12
|
-
import { protoEnum } from "#internal/proto_enum.js";
|
|
1
|
+
// Client runtime behavior is handwritten. Portable request and response data
|
|
2
|
+
// remain generated from protobuf. This module owns construction and create /
|
|
3
|
+
// attach / get / close; validation, errors, and the Sandbox handle live beside it.
|
|
13
4
|
import { InfoFieldId } from "#contracts/field_paths.js";
|
|
14
|
-
import { ConfigFieldProvider
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
memory_limit: "limit",
|
|
23
|
-
cpu_requires_positive: "requires_positive",
|
|
24
|
-
memory_requires_positive: "requires_positive",
|
|
25
|
-
restricted_requires_allowlist: "requires_any",
|
|
26
|
-
block_all_forbids_network: "forbids",
|
|
27
|
-
allow_all_forbids_allowlists: "forbids",
|
|
28
|
-
after_requires_duration: "requires_any",
|
|
29
|
-
after_forbids_duration: "forbids",
|
|
30
|
-
};
|
|
31
|
-
// Ergonomic PascalCase names over protobuf enum values; not a second contract.
|
|
32
|
-
export const ErrorKind = protoEnum(ProtoErrorKind);
|
|
33
|
-
export class SandboxError extends Error {
|
|
34
|
-
kind;
|
|
35
|
-
provider;
|
|
36
|
-
operation;
|
|
37
|
-
field;
|
|
38
|
-
rule;
|
|
39
|
-
statusCode;
|
|
40
|
-
providerCode;
|
|
41
|
-
providerSource;
|
|
42
|
-
constructor(info, options) {
|
|
43
|
-
super(info.message, options);
|
|
44
|
-
this.name = "SandboxError";
|
|
45
|
-
this.kind = info.kind;
|
|
46
|
-
this.provider = info.provider;
|
|
47
|
-
this.operation = info.operation;
|
|
48
|
-
this.field = info.field;
|
|
49
|
-
this.rule = info.rule;
|
|
50
|
-
this.statusCode = info.statusCode;
|
|
51
|
-
this.providerCode = info.providerCode;
|
|
52
|
-
this.providerSource = info.providerSource;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
export function sandboxError(info, cause) {
|
|
56
|
-
return new SandboxError(info, cause === undefined ? undefined : { cause });
|
|
57
|
-
}
|
|
58
|
-
function validationError(provider, operation, field, rule, message) {
|
|
59
|
-
throw sandboxError({
|
|
60
|
-
kind: ErrorKind.InvalidArgument,
|
|
61
|
-
provider,
|
|
62
|
-
operation,
|
|
63
|
-
field,
|
|
64
|
-
rule,
|
|
65
|
-
message: "sandbox-kit: " + message,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
function validSecureEndpoint(value) {
|
|
69
|
-
try {
|
|
70
|
-
const url = new URL(value);
|
|
71
|
-
if (!url.hostname || url.username || url.password || url.hash)
|
|
72
|
-
return false;
|
|
73
|
-
if (url.protocol === "https:")
|
|
74
|
-
return true;
|
|
75
|
-
if (url.protocol !== "http:")
|
|
76
|
-
return false;
|
|
77
|
-
const host = url.hostname.toLowerCase();
|
|
78
|
-
return (host === "localhost" ||
|
|
79
|
-
host === "[::1]" ||
|
|
80
|
-
host === "::1" ||
|
|
81
|
-
/^127(?:\.[0-9]{1,3}){3}$/.test(host));
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Rejects public {@link CreateOptions} the shared contract cannot represent.
|
|
89
|
-
* Materializes wire form privately for Protovalidate. Provider-specific checks
|
|
90
|
-
* remain in each provider adapter.
|
|
91
|
-
*/
|
|
92
|
-
export function validateCreateOptions(value, provider = "", operation = "create") {
|
|
93
|
-
const message = materializeCreateOptions(value);
|
|
94
|
-
const result = contractValidator.validate(CreateOptionsSchema, message);
|
|
95
|
-
if (result.kind === "valid") {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
if (result.kind === "error") {
|
|
99
|
-
throw sandboxError({
|
|
100
|
-
kind: ErrorKind.InvalidArgument,
|
|
101
|
-
provider,
|
|
102
|
-
operation,
|
|
103
|
-
field: "",
|
|
104
|
-
message: "sandbox-kit: invalid configuration",
|
|
105
|
-
}, result.error);
|
|
106
|
-
}
|
|
107
|
-
const violation = result.violations[0];
|
|
108
|
-
const ruleID = violation.ruleId;
|
|
109
|
-
let field = pathToString(violation.field);
|
|
110
|
-
let rule = stableRuleIDs[ruleID] ?? ruleID;
|
|
111
|
-
if (!field) {
|
|
112
|
-
field = messageFieldHint(ruleID);
|
|
113
|
-
}
|
|
114
|
-
validationError(provider, operation, field, rule, violation.message || "invalid configuration");
|
|
115
|
-
}
|
|
116
|
-
function messageFieldHint(ruleID) {
|
|
117
|
-
switch (ruleID) {
|
|
118
|
-
case "cpu_limit":
|
|
119
|
-
case "cpu_requires_positive":
|
|
120
|
-
return "resources.cpu_limit_cores";
|
|
121
|
-
case "memory_limit":
|
|
122
|
-
case "memory_requires_positive":
|
|
123
|
-
return "resources.memory_limit_mib";
|
|
124
|
-
case "restricted_requires_allowlist":
|
|
125
|
-
return "network.outbound_cidrs,outbound_domains";
|
|
126
|
-
case "block_all_forbids_network":
|
|
127
|
-
return "network.outbound_cidrs,outbound_domains,private_network,linked_sandbox,inbound_cidrs,ports";
|
|
128
|
-
case "allow_all_forbids_allowlists":
|
|
129
|
-
return "network.outbound_cidrs,outbound_domains";
|
|
130
|
-
case "after_requires_duration":
|
|
131
|
-
case "after_forbids_duration":
|
|
132
|
-
return "after";
|
|
133
|
-
case "oneof.required":
|
|
134
|
-
return "tcp_port,command";
|
|
135
|
-
case "auth_credentials_required":
|
|
136
|
-
return "api_key,token_pair,bearer_token,oauth_refresh";
|
|
137
|
-
case "exactly_one":
|
|
138
|
-
return "client_secret,jwt_key";
|
|
139
|
-
default:
|
|
140
|
-
return "";
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
export function validateConfig(value, provider = "", operation = "create") {
|
|
144
|
-
const message = materializeClientConfig(value);
|
|
145
|
-
const result = contractValidator.validate(ConfigSchema, message);
|
|
146
|
-
if (result.kind === "error") {
|
|
147
|
-
throw sandboxError({
|
|
148
|
-
kind: ErrorKind.InvalidArgument,
|
|
149
|
-
provider,
|
|
150
|
-
operation,
|
|
151
|
-
field: "",
|
|
152
|
-
message: "sandbox-kit: invalid configuration",
|
|
153
|
-
}, result.error);
|
|
154
|
-
}
|
|
155
|
-
if (result.kind === "invalid") {
|
|
156
|
-
const violation = result.violations[0];
|
|
157
|
-
const ruleID = violation.ruleId;
|
|
158
|
-
let field = pathToString(violation.field);
|
|
159
|
-
const rule = stableRuleIDs[ruleID] ?? ruleID;
|
|
160
|
-
if (!field) {
|
|
161
|
-
field = messageFieldHint(ruleID);
|
|
162
|
-
}
|
|
163
|
-
validationError(provider, operation, field, rule, violation.message || "invalid configuration");
|
|
164
|
-
}
|
|
165
|
-
if (message.endpoint !== undefined &&
|
|
166
|
-
!validSecureEndpoint(message.endpoint)) {
|
|
167
|
-
validationError(provider, operation, "endpoint", "secure_endpoint", "must use HTTPS except for loopback HTTP development endpoints");
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
function clone(value) {
|
|
171
|
-
return structuredClone(value);
|
|
172
|
-
}
|
|
173
|
-
function operationSignal(options, inheritedTimeoutMs) {
|
|
174
|
-
const timeout = options.timeout ?? inheritedTimeoutMs;
|
|
175
|
-
if (timeout !== undefined && (!Number.isFinite(timeout) || timeout < 0)) {
|
|
176
|
-
throw sandboxError({
|
|
177
|
-
kind: ErrorKind.InvalidArgument,
|
|
178
|
-
provider: "",
|
|
179
|
-
operation: "",
|
|
180
|
-
field: ConfigFieldTimeout,
|
|
181
|
-
message: "sandbox-kit: timeout must be a finite non-negative number",
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
if ((timeout === undefined || timeout === 0) && options.signal === undefined)
|
|
185
|
-
return { dispose() { } };
|
|
186
|
-
const controller = new AbortController();
|
|
187
|
-
const abort = () => controller.abort(options.signal?.reason);
|
|
188
|
-
if (options.signal?.aborted)
|
|
189
|
-
abort();
|
|
190
|
-
else
|
|
191
|
-
options.signal?.addEventListener("abort", abort, { once: true });
|
|
192
|
-
const timer = timeout !== undefined && timeout > 0
|
|
193
|
-
? setTimeout(() => controller.abort(new DOMException("The operation timed out", "TimeoutError")), timeout)
|
|
194
|
-
: undefined;
|
|
195
|
-
return {
|
|
196
|
-
signal: controller.signal,
|
|
197
|
-
dispose() {
|
|
198
|
-
if (timer !== undefined)
|
|
199
|
-
clearTimeout(timer);
|
|
200
|
-
options.signal?.removeEventListener("abort", abort);
|
|
201
|
-
},
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
function operationOptions(options, signal) {
|
|
205
|
-
return {
|
|
206
|
-
...(options.timeout === undefined ? {} : { timeout: options.timeout }),
|
|
207
|
-
...(signal === undefined ? {} : { signal }),
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
function abortReason(signal) {
|
|
211
|
-
const reason = signal.reason;
|
|
212
|
-
return reason instanceof DOMException &&
|
|
213
|
-
(reason.name === "AbortError" || reason.name === "TimeoutError")
|
|
214
|
-
? reason
|
|
215
|
-
: new DOMException("The operation was aborted", "AbortError");
|
|
216
|
-
}
|
|
217
|
-
async function awaitWithSignal(operation, signal) {
|
|
218
|
-
if (signal?.aborted)
|
|
219
|
-
throw abortReason(signal);
|
|
220
|
-
const pending = operation();
|
|
221
|
-
if (signal === undefined)
|
|
222
|
-
return pending;
|
|
223
|
-
return new Promise((resolve, reject) => {
|
|
224
|
-
const aborted = () => reject(abortReason(signal));
|
|
225
|
-
const cleanup = () => signal.removeEventListener("abort", aborted);
|
|
226
|
-
signal.addEventListener("abort", aborted, { once: true });
|
|
227
|
-
pending.then((value) => {
|
|
228
|
-
cleanup();
|
|
229
|
-
resolve(value);
|
|
230
|
-
}, (error) => {
|
|
231
|
-
cleanup();
|
|
232
|
-
reject(error);
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
const sandboxConstructorToken = Symbol("sandbox-kit.Sandbox");
|
|
237
|
-
export class Sandbox {
|
|
238
|
-
#backend;
|
|
239
|
-
#owner;
|
|
240
|
-
#timeout;
|
|
241
|
-
#info;
|
|
242
|
-
#closed = false;
|
|
243
|
-
#tail = Promise.resolve();
|
|
244
|
-
constructor(token, backend, owner, info, timeout) {
|
|
245
|
-
if (token !== sandboxConstructorToken)
|
|
246
|
-
throw new TypeError("Sandbox handles are created by Client");
|
|
247
|
-
this.#backend = backend;
|
|
248
|
-
this.#owner = owner;
|
|
249
|
-
this.#info = clone(info);
|
|
250
|
-
this.#timeout = timeout;
|
|
251
|
-
}
|
|
252
|
-
get id() {
|
|
253
|
-
return this.#info.id;
|
|
254
|
-
}
|
|
255
|
-
get providerName() {
|
|
256
|
-
return this.#info.provider;
|
|
257
|
-
}
|
|
258
|
-
info() {
|
|
259
|
-
return clone(this.#info);
|
|
260
|
-
}
|
|
261
|
-
capabilities() {
|
|
262
|
-
if (this.#closed || this.#owner.isClosed() || this.#info.terminal)
|
|
263
|
-
return [];
|
|
264
|
-
return this.#backend.capabilities?.(clone(this.#info)) ?? [];
|
|
265
|
-
}
|
|
266
|
-
supports(capability) {
|
|
267
|
-
return this.capabilities().includes(capability);
|
|
268
|
-
}
|
|
269
|
-
async refresh(options = {}) {
|
|
270
|
-
return this.#serialized("refresh", options, this.#timeout, async (signal) => {
|
|
271
|
-
const method = this.#backend.refresh;
|
|
272
|
-
if (!method)
|
|
273
|
-
unsupported(this.providerName, "refresh");
|
|
274
|
-
const info = await awaitWithSignal(() => method.call(this.#backend, this.id, operationOptions(options, signal)), signal);
|
|
275
|
-
this.#info = clone({ ...info, provider: this.providerName });
|
|
276
|
-
return this.info();
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
async start(options = {}) {
|
|
280
|
-
return this.#update("start", this.#backend.start, wireTimeoutOptions(options), options);
|
|
281
|
-
}
|
|
282
|
-
async stop(options = {}) {
|
|
283
|
-
const { force, ...call } = options;
|
|
284
|
-
return this.#update("stop", this.#backend.stop, {
|
|
285
|
-
...wireTimeoutOptions(call),
|
|
286
|
-
...(force === undefined ? {} : { force }),
|
|
287
|
-
}, call);
|
|
288
|
-
}
|
|
289
|
-
async pause(options = {}) {
|
|
290
|
-
return this.#update("pause", this.#backend.pause, wireTimeoutOptions(options), options);
|
|
291
|
-
}
|
|
292
|
-
async terminate(options = {}) {
|
|
293
|
-
const { wait, ...call } = options;
|
|
294
|
-
const wire = {
|
|
295
|
-
...wireTimeoutOptions(call),
|
|
296
|
-
...(wait === undefined ? {} : { wait }),
|
|
297
|
-
};
|
|
298
|
-
return this.#serialized("terminate", call, call.timeout ?? this.#timeout, async (signal) => {
|
|
299
|
-
const method = this.#backend.terminate;
|
|
300
|
-
if (!method)
|
|
301
|
-
unsupported(this.providerName, "terminate");
|
|
302
|
-
const result = await awaitWithSignal(() => method.call(this.#backend, this.id, clone(wire), operationOptions(call, signal)), signal);
|
|
303
|
-
this.#info = {
|
|
304
|
-
...this.#info,
|
|
305
|
-
state: SandboxState.Terminated,
|
|
306
|
-
terminal: true,
|
|
307
|
-
};
|
|
308
|
-
return result;
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
async archive(options = {}) {
|
|
312
|
-
return this.#update("archive", this.#backend.archive, wireTimeoutOptions(options), options);
|
|
313
|
-
}
|
|
314
|
-
async delete(options = {}) {
|
|
315
|
-
const { wait, ...call } = options;
|
|
316
|
-
return this.#update("delete", this.#backend.delete, { ...wireTimeoutOptions(call), ...(wait === undefined ? {} : { wait }) }, call);
|
|
317
|
-
}
|
|
318
|
-
async close(options = {}) {
|
|
319
|
-
if (this.#closed)
|
|
320
|
-
return;
|
|
321
|
-
await this.#serialized("close", options, this.#timeout, async (signal) => {
|
|
322
|
-
const close = this.#backend.closeSandbox;
|
|
323
|
-
if (close)
|
|
324
|
-
await awaitWithSignal(() => close.call(this.#backend, this.id, operationOptions(options, signal)), signal);
|
|
325
|
-
this.#closed = true;
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
async [Symbol.asyncDispose]() {
|
|
329
|
-
await this.close();
|
|
330
|
-
}
|
|
331
|
-
async #update(operation, method, wire, options) {
|
|
332
|
-
return this.#serialized(operation, options, options.timeout ?? this.#timeout, async (signal) => {
|
|
333
|
-
if (!method)
|
|
334
|
-
unsupported(this.providerName, operation);
|
|
335
|
-
const info = await awaitWithSignal(() => method.call(this.#backend, this.id, clone(wire), operationOptions(options, signal)), signal);
|
|
336
|
-
this.#info = clone({ ...info, provider: this.providerName });
|
|
337
|
-
return this.info();
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
async #serialized(operation, options, timeout, body) {
|
|
341
|
-
this.#ensureOpen(operation);
|
|
342
|
-
const releaseClient = this.#owner.begin(operation);
|
|
343
|
-
const previous = this.#tail;
|
|
344
|
-
let unlock;
|
|
345
|
-
this.#tail = new Promise((resolve) => {
|
|
346
|
-
unlock = resolve;
|
|
347
|
-
});
|
|
348
|
-
let acquired = false;
|
|
349
|
-
let scoped;
|
|
350
|
-
try {
|
|
351
|
-
scoped = operationSignal(options, timeout);
|
|
352
|
-
await awaitWithSignal(() => previous, scoped.signal);
|
|
353
|
-
acquired = true;
|
|
354
|
-
this.#ensureOpen(operation);
|
|
355
|
-
return await body(scoped.signal);
|
|
356
|
-
}
|
|
357
|
-
catch (error) {
|
|
358
|
-
throw withContext(error, this.providerName, operation);
|
|
359
|
-
}
|
|
360
|
-
finally {
|
|
361
|
-
scoped?.dispose();
|
|
362
|
-
if (acquired)
|
|
363
|
-
unlock();
|
|
364
|
-
else
|
|
365
|
-
void previous.then(unlock, unlock);
|
|
366
|
-
releaseClient();
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
#ensureOpen(operation) {
|
|
370
|
-
if (this.#closed || this.#owner.isClosed())
|
|
371
|
-
throw sandboxError({
|
|
372
|
-
kind: ErrorKind.FailedPrecondition,
|
|
373
|
-
provider: this.providerName,
|
|
374
|
-
operation,
|
|
375
|
-
field: "sandbox",
|
|
376
|
-
message: "sandbox-kit: sandbox handle is closed",
|
|
377
|
-
});
|
|
378
|
-
if (operation !== "close" && this.#info.terminal)
|
|
379
|
-
throw sandboxError({
|
|
380
|
-
kind: ErrorKind.FailedPrecondition,
|
|
381
|
-
provider: this.providerName,
|
|
382
|
-
operation,
|
|
383
|
-
field: "sandbox",
|
|
384
|
-
message: "sandbox-kit: sandbox lifecycle is terminal",
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
}
|
|
5
|
+
import { ConfigFieldProvider } from "#contracts/config_paths.js";
|
|
6
|
+
import { ErrorKind, sandboxError, unsupported, withContext, } from "#client/errors.js";
|
|
7
|
+
import { awaitWithSignal, operationOptions, operationSignal, } from "#client/operation.js";
|
|
8
|
+
import { Sandbox, sandboxConstructorToken, } from "#client/sandbox.js";
|
|
9
|
+
import { validateConfig, validateCreateOptions } from "#client/validation.js";
|
|
10
|
+
export { ErrorKind, SandboxError, sandboxError } from "#client/errors.js";
|
|
11
|
+
export { Sandbox } from "#client/sandbox.js";
|
|
12
|
+
export { validateConfig, validateCreateOptions } from "#client/validation.js";
|
|
388
13
|
export class Client {
|
|
389
14
|
#backend;
|
|
390
15
|
#providerName;
|
|
@@ -442,33 +67,21 @@ export class Client {
|
|
|
442
67
|
return this.#providerName;
|
|
443
68
|
}
|
|
444
69
|
async attach(id, options = {}) {
|
|
445
|
-
|
|
446
|
-
let scoped;
|
|
447
|
-
try {
|
|
448
|
-
scoped = operationSignal(options, this.#timeout);
|
|
449
|
-
if (!id.trim())
|
|
450
|
-
throw sandboxError({
|
|
451
|
-
kind: ErrorKind.InvalidArgument,
|
|
452
|
-
provider: this.providerName,
|
|
453
|
-
operation: "attach",
|
|
454
|
-
field: InfoFieldId,
|
|
455
|
-
message: "sandbox-kit: sandbox ID must not be blank",
|
|
456
|
-
});
|
|
70
|
+
return this.#openById("attach", id, options, (fetchOptions) => {
|
|
457
71
|
if (!this.#backend.attach)
|
|
458
72
|
unsupported(this.providerName, "attach");
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
}
|
|
462
|
-
catch (error) {
|
|
463
|
-
throw withContext(error, this.providerName, "attach");
|
|
464
|
-
}
|
|
465
|
-
finally {
|
|
466
|
-
scoped?.dispose();
|
|
467
|
-
release();
|
|
468
|
-
}
|
|
73
|
+
return this.#backend.attach(id, fetchOptions);
|
|
74
|
+
});
|
|
469
75
|
}
|
|
470
76
|
async get(id, options = {}) {
|
|
471
|
-
|
|
77
|
+
return this.#openById("get", id, options, (fetchOptions) => {
|
|
78
|
+
if (!this.#backend.refresh)
|
|
79
|
+
unsupported(this.providerName, "get");
|
|
80
|
+
return this.#backend.refresh(id, fetchOptions);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async #openById(operation, id, options, fetch) {
|
|
84
|
+
const release = this.#begin(operation);
|
|
472
85
|
let scoped;
|
|
473
86
|
try {
|
|
474
87
|
scoped = operationSignal(options, this.#timeout);
|
|
@@ -476,17 +89,20 @@ export class Client {
|
|
|
476
89
|
throw sandboxError({
|
|
477
90
|
kind: ErrorKind.InvalidArgument,
|
|
478
91
|
provider: this.providerName,
|
|
479
|
-
operation
|
|
92
|
+
operation,
|
|
480
93
|
field: InfoFieldId,
|
|
481
94
|
message: "sandbox-kit: sandbox ID must not be blank",
|
|
482
95
|
});
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
96
|
+
const info = await awaitWithSignal(() => fetch(operationOptions(options, scoped?.signal)), scoped.signal);
|
|
97
|
+
return new Sandbox(sandboxConstructorToken, {
|
|
98
|
+
backend: this.#backend,
|
|
99
|
+
owner: this.#lifecycle,
|
|
100
|
+
info: { ...info, id, provider: this.providerName },
|
|
101
|
+
...(this.#timeout === undefined ? {} : { timeout: this.#timeout }),
|
|
102
|
+
});
|
|
487
103
|
}
|
|
488
104
|
catch (error) {
|
|
489
|
-
throw withContext(error, this.providerName,
|
|
105
|
+
throw withContext(error, this.providerName, operation);
|
|
490
106
|
}
|
|
491
107
|
finally {
|
|
492
108
|
scoped?.dispose();
|
|
@@ -533,7 +149,12 @@ export class Client {
|
|
|
533
149
|
field: ConfigFieldProvider,
|
|
534
150
|
message: "sandbox-kit: response provider differs from selected provider",
|
|
535
151
|
});
|
|
536
|
-
return new Sandbox(sandboxConstructorToken,
|
|
152
|
+
return new Sandbox(sandboxConstructorToken, {
|
|
153
|
+
backend: this.#backend,
|
|
154
|
+
owner: this.#lifecycle,
|
|
155
|
+
info: { ...result.sandbox, provider: this.providerName },
|
|
156
|
+
...(this.#timeout === undefined ? {} : { timeout: this.#timeout }),
|
|
157
|
+
});
|
|
537
158
|
}
|
|
538
159
|
catch (error) {
|
|
539
160
|
throw withContext(error, this.providerName, "create");
|
|
@@ -599,151 +220,3 @@ export class Client {
|
|
|
599
220
|
};
|
|
600
221
|
}
|
|
601
222
|
}
|
|
602
|
-
/** Materializes a plain client config into the generated protobuf message. */
|
|
603
|
-
function materializeClientConfig(input) {
|
|
604
|
-
const { auth, timeout, ...rest } = input;
|
|
605
|
-
return create(ConfigSchema, {
|
|
606
|
-
...rest,
|
|
607
|
-
...(timeout === undefined ? {} : { timeout: durationFromMs(timeout) }),
|
|
608
|
-
...(auth === undefined
|
|
609
|
-
? {}
|
|
610
|
-
: { auth: { credentials: authCredentials(auth) } }),
|
|
611
|
-
});
|
|
612
|
-
}
|
|
613
|
-
function wireTimeoutOptions(options) {
|
|
614
|
-
return options.timeout === undefined
|
|
615
|
-
? {}
|
|
616
|
-
: { timeout: durationFromMs(options.timeout) };
|
|
617
|
-
}
|
|
618
|
-
/** Materializes plain create options into the generated protobuf message. */
|
|
619
|
-
function materializeCreateOptions(input) {
|
|
620
|
-
const { source, readiness, providerOptions, network, ...rest } = input;
|
|
621
|
-
const withDurations = materializeDurationFields(rest);
|
|
622
|
-
return create(CreateOptionsSchema, {
|
|
623
|
-
...withDurations,
|
|
624
|
-
...(network === undefined
|
|
625
|
-
? {}
|
|
626
|
-
: { network: materializeNetworkConfig(network) }),
|
|
627
|
-
...(source === undefined ? {} : { source: { kind: sourceKind(source) } }),
|
|
628
|
-
...(readiness === undefined
|
|
629
|
-
? {}
|
|
630
|
-
: { readiness: { check: readinessCheck(readiness) } }),
|
|
631
|
-
...(providerOptions === undefined
|
|
632
|
-
? {}
|
|
633
|
-
: { providerOptions: metadataObjectFromMap(providerOptions) }),
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
/** Wrap public string-array allowlists into wire StringAllowlist messages. */
|
|
637
|
-
function materializeNetworkConfig(network) {
|
|
638
|
-
const { outboundCidrs, outboundDomains, inboundCidrs, ...rest } = network;
|
|
639
|
-
return {
|
|
640
|
-
...rest,
|
|
641
|
-
...(outboundCidrs === undefined
|
|
642
|
-
? {}
|
|
643
|
-
: { outboundCidrs: { entries: outboundCidrs } }),
|
|
644
|
-
...(outboundDomains === undefined
|
|
645
|
-
? {}
|
|
646
|
-
: { outboundDomains: { entries: outboundDomains } }),
|
|
647
|
-
...(inboundCidrs === undefined
|
|
648
|
-
? {}
|
|
649
|
-
: { inboundCidrs: { entries: inboundCidrs } }),
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
function authCredentials(auth) {
|
|
653
|
-
if (auth.apiKey !== undefined)
|
|
654
|
-
return { case: "apiKey", value: auth.apiKey };
|
|
655
|
-
if (auth.tokenPair !== undefined)
|
|
656
|
-
return {
|
|
657
|
-
case: "tokenPair",
|
|
658
|
-
value: auth.tokenPair,
|
|
659
|
-
};
|
|
660
|
-
if (auth.bearerToken !== undefined)
|
|
661
|
-
return {
|
|
662
|
-
case: "bearerToken",
|
|
663
|
-
value: auth.bearerToken,
|
|
664
|
-
};
|
|
665
|
-
if (auth.oauthRefresh !== undefined)
|
|
666
|
-
return {
|
|
667
|
-
case: "oauthRefresh",
|
|
668
|
-
value: auth.oauthRefresh,
|
|
669
|
-
};
|
|
670
|
-
return { case: undefined };
|
|
671
|
-
}
|
|
672
|
-
function sourceKind(source) {
|
|
673
|
-
if (source.providerDefault !== undefined)
|
|
674
|
-
return {
|
|
675
|
-
case: "providerDefault",
|
|
676
|
-
value: {},
|
|
677
|
-
};
|
|
678
|
-
if (source.image !== undefined)
|
|
679
|
-
return { case: "image", value: source.image };
|
|
680
|
-
if (source.snapshot !== undefined)
|
|
681
|
-
return { case: "snapshot", value: source.snapshot };
|
|
682
|
-
if (source.warmPool !== undefined)
|
|
683
|
-
return { case: "warmPool", value: source.warmPool };
|
|
684
|
-
return { case: undefined };
|
|
685
|
-
}
|
|
686
|
-
function readinessCheck(readiness) {
|
|
687
|
-
if (readiness.tcpPort !== undefined)
|
|
688
|
-
return { case: "tcpPort", value: readiness.tcpPort };
|
|
689
|
-
if (readiness.command !== undefined)
|
|
690
|
-
return { case: "command", value: readiness.command };
|
|
691
|
-
return { case: undefined };
|
|
692
|
-
}
|
|
693
|
-
function unsupported(provider, operation) {
|
|
694
|
-
throw sandboxError({
|
|
695
|
-
kind: ErrorKind.Unsupported,
|
|
696
|
-
provider,
|
|
697
|
-
operation,
|
|
698
|
-
field: "",
|
|
699
|
-
message: "sandbox-kit: provider does not support " + operation,
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
|
-
function withContext(error, provider, operation) {
|
|
703
|
-
if (error instanceof SandboxError) {
|
|
704
|
-
if (error.provider === provider && error.operation === operation)
|
|
705
|
-
return error;
|
|
706
|
-
return sandboxError({
|
|
707
|
-
kind: error.kind,
|
|
708
|
-
provider: error.provider || provider,
|
|
709
|
-
operation: error.operation || operation,
|
|
710
|
-
field: error.field,
|
|
711
|
-
message: error.message,
|
|
712
|
-
...(error.rule === undefined ? {} : { rule: error.rule }),
|
|
713
|
-
...(error.statusCode === undefined
|
|
714
|
-
? {}
|
|
715
|
-
: { statusCode: error.statusCode }),
|
|
716
|
-
...(error.providerCode === undefined
|
|
717
|
-
? {}
|
|
718
|
-
: { providerCode: error.providerCode }),
|
|
719
|
-
...(error.providerSource === undefined
|
|
720
|
-
? {}
|
|
721
|
-
: { providerSource: error.providerSource }),
|
|
722
|
-
}, error.cause);
|
|
723
|
-
}
|
|
724
|
-
if (error instanceof DOMException && error.name === "AbortError")
|
|
725
|
-
return sandboxError({
|
|
726
|
-
kind: ErrorKind.Canceled,
|
|
727
|
-
provider,
|
|
728
|
-
operation,
|
|
729
|
-
field: "",
|
|
730
|
-
message: error.message,
|
|
731
|
-
}, error);
|
|
732
|
-
if (error instanceof DOMException && error.name === "TimeoutError")
|
|
733
|
-
return sandboxError({
|
|
734
|
-
kind: ErrorKind.Timeout,
|
|
735
|
-
provider,
|
|
736
|
-
operation,
|
|
737
|
-
field: "",
|
|
738
|
-
message: error.message,
|
|
739
|
-
}, error);
|
|
740
|
-
return sandboxError({
|
|
741
|
-
kind: ErrorKind.Unknown,
|
|
742
|
-
provider,
|
|
743
|
-
operation,
|
|
744
|
-
field: "",
|
|
745
|
-
message: error instanceof Error
|
|
746
|
-
? error.message
|
|
747
|
-
: "sandbox-kit: provider operation failed",
|
|
748
|
-
}, error);
|
|
749
|
-
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ErrorKind as ProtoErrorKind } from "@sandbox-kit/contracts/sandbox";
|
|
2
|
+
import type { ErrorInfo } from "#contracts/sandbox_data_contracts.js";
|
|
3
|
+
export declare const ErrorKind: {
|
|
4
|
+
readonly Unknown: ProtoErrorKind.UNKNOWN;
|
|
5
|
+
readonly InvalidArgument: ProtoErrorKind.INVALID_ARGUMENT;
|
|
6
|
+
readonly Unsupported: ProtoErrorKind.UNSUPPORTED;
|
|
7
|
+
readonly Authentication: ProtoErrorKind.AUTHENTICATION;
|
|
8
|
+
readonly PermissionDenied: ProtoErrorKind.PERMISSION_DENIED;
|
|
9
|
+
readonly NotFound: ProtoErrorKind.NOT_FOUND;
|
|
10
|
+
readonly AlreadyExists: ProtoErrorKind.ALREADY_EXISTS;
|
|
11
|
+
readonly Conflict: ProtoErrorKind.CONFLICT;
|
|
12
|
+
readonly RateLimited: ProtoErrorKind.RATE_LIMITED;
|
|
13
|
+
readonly ResourceExhausted: ProtoErrorKind.RESOURCE_EXHAUSTED;
|
|
14
|
+
readonly Timeout: ProtoErrorKind.TIMEOUT;
|
|
15
|
+
readonly Canceled: ProtoErrorKind.CANCELED;
|
|
16
|
+
readonly Unavailable: ProtoErrorKind.UNAVAILABLE;
|
|
17
|
+
readonly InvalidResponse: ProtoErrorKind.INVALID_RESPONSE;
|
|
18
|
+
readonly Internal: ProtoErrorKind.INTERNAL;
|
|
19
|
+
readonly FailedPrecondition: ProtoErrorKind.FAILED_PRECONDITION;
|
|
20
|
+
};
|
|
21
|
+
export type ErrorKind = (typeof ErrorKind)[keyof typeof ErrorKind];
|
|
22
|
+
export declare class SandboxError extends Error {
|
|
23
|
+
readonly kind: ErrorInfo["kind"];
|
|
24
|
+
readonly provider: string;
|
|
25
|
+
readonly operation: string;
|
|
26
|
+
readonly field: string;
|
|
27
|
+
readonly rule: string | undefined;
|
|
28
|
+
readonly statusCode: number | undefined;
|
|
29
|
+
readonly providerCode: string | undefined;
|
|
30
|
+
readonly providerSource: string | undefined;
|
|
31
|
+
constructor(info: ErrorInfo, options?: ErrorOptions);
|
|
32
|
+
}
|
|
33
|
+
export declare function sandboxError(info: ErrorInfo, cause?: unknown): SandboxError;
|
|
34
|
+
export declare function unsupported(provider: string, operation: string): never;
|
|
35
|
+
export declare function withContext(error: unknown, provider: string, operation: string): SandboxError;
|