@molecule/api-code-sandbox-e2b 1.2.8 → 1.2.9
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 +9 -6
- package/dist/browser-guard.d.ts +2 -0
- package/dist/browser-guard.d.ts.map +1 -0
- package/dist/browser-guard.js +18 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/provider.d.ts +8 -5
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +156 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
|
|
|
3
3
|
Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
|
|
4
4
|
Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
|
|
5
5
|
To change this document, edit the module-level JSDoc in src/index.ts.
|
|
6
|
-
Generated: 2026-10-
|
|
6
|
+
Generated: 2026-10-09T06:56:17.299Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/api-code-sandbox-e2b
|
|
@@ -396,11 +396,14 @@ interface EgressProbeCodes {
|
|
|
396
396
|
|
|
397
397
|
E2B implementation of {@link SandboxProvider}.
|
|
398
398
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
399
|
+
The required surface (`create`/`get`/`list`/`destroy`) plus the optionals the
|
|
400
|
+
control plane uses are all wired here: `describe`, volumes
|
|
401
|
+
(`createVolume`/`removeVolume`/`volumeExists`/`listVolumes`), snapshots
|
|
402
|
+
(`commitTemplate`/`getTemplate`/`listTemplates`/`removeTemplate`) and
|
|
403
|
+
`verifyEgress`. `verifyEgress` proves deny-by-default by OBSERVING a
|
|
404
|
+
throwaway probe sandbox, and answers `inconclusive` whenever it cannot look —
|
|
405
|
+
a failed create, a dead probe — so "could not observe" never wears the shape
|
|
406
|
+
of the safe verdict.
|
|
404
407
|
|
|
405
408
|
### Functions
|
|
406
409
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-guard.d.ts","sourceRoot":"","sources":["../src/browser-guard.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAE,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser guard — `@molecule/api-code-sandbox-e2b` is SERVER-ONLY.
|
|
3
|
+
*
|
|
4
|
+
* Generated by scripts/gen-browser-guards.mjs (workspace root) — edit THAT, not this.
|
|
5
|
+
* Evaluating a server package in a browser bundle is always an import-graph mistake
|
|
6
|
+
* (node APIs, secrets); without this guard it surfaces as a cryptic downstream crash
|
|
7
|
+
* ("Buffer is not defined") far from the culprit. Throwing here names the package and
|
|
8
|
+
* the fix at the exact moment the client bundle evaluates it. jsdom tests and SSR are
|
|
9
|
+
* unaffected: the throw requires browser globals AND the absence of a node runtime.
|
|
10
|
+
*/
|
|
11
|
+
const g = globalThis;
|
|
12
|
+
if (g.window !== undefined && g.document !== undefined && !g.process?.versions?.node) {
|
|
13
|
+
throw new Error('@molecule/api-code-sandbox-e2b is SERVER-ONLY: it was bundled into browser/client code. Import it only ' +
|
|
14
|
+
'from server code (a server route/function or your API), or dynamic-import it inside ' +
|
|
15
|
+
'the server handler — never from components or shared client modules, and never ' +
|
|
16
|
+
'polyfill Buffer/process to silence this.');
|
|
17
|
+
}
|
|
18
|
+
export {};
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiKG;AAEH,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiKG;AAEH,cAAc,oBAAoB,CAAA;AAClC,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA"}
|
package/dist/index.js
CHANGED
package/dist/provider.d.ts
CHANGED
|
@@ -13,11 +13,14 @@ import type { E2BConfig, E2BSandboxClientLike } from './types.js';
|
|
|
13
13
|
/**
|
|
14
14
|
* E2B implementation of {@link SandboxProvider}.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
16
|
+
* The required surface (`create`/`get`/`list`/`destroy`) plus the optionals the
|
|
17
|
+
* control plane uses are all wired here: `describe`, volumes
|
|
18
|
+
* (`createVolume`/`removeVolume`/`volumeExists`/`listVolumes`), snapshots
|
|
19
|
+
* (`commitTemplate`/`getTemplate`/`listTemplates`/`removeTemplate`) and
|
|
20
|
+
* `verifyEgress`. `verifyEgress` proves deny-by-default by OBSERVING a
|
|
21
|
+
* throwaway probe sandbox, and answers `inconclusive` whenever it cannot look —
|
|
22
|
+
* a failed create, a dead probe — so "could not observe" never wears the shape
|
|
23
|
+
* of the safe verdict.
|
|
21
24
|
*/
|
|
22
25
|
export declare class E2BSandboxProvider implements SandboxProvider {
|
|
23
26
|
readonly name = "e2b";
|
package/dist/provider.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACV,qBAAqB,EAErB,aAAa,EAKb,oBAAoB,EACpB,kBAAkB,EAClB,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,eAAe,EAGf,UAAU,EACX,MAAM,4BAA4B,CAAA;AAEnC,OAAO,KAAK,EAGV,SAAS,EACT,oBAAoB,EAMrB,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
|
package/dist/provider.js
CHANGED
|
@@ -43,6 +43,41 @@ const EXIT_REPORT_GRACE_MS = 2_000;
|
|
|
43
43
|
* build (partial `dist/` and all) as a successful one.
|
|
44
44
|
*/
|
|
45
45
|
const EXIT_CODE_UNKNOWN = 137;
|
|
46
|
+
/**
|
|
47
|
+
* How many sandbox connects {@link E2BSandboxProvider.list} keeps in flight at
|
|
48
|
+
* once. Each handle costs a full connect round trip — on the real SDK, a live
|
|
49
|
+
* websocket to the sandbox's envd — and `list()` is what a control-plane sweep
|
|
50
|
+
* polls, so putting ALL N running sandboxes in flight at once spends N sockets
|
|
51
|
+
* and file descriptors per poll and walks straight into the API's concurrency
|
|
52
|
+
* limits. A bounded window keeps the sweep concurrent (O(⌈N/16⌉) round trips
|
|
53
|
+
* deep, not N) without the per-poll fan-out growing with the fleet.
|
|
54
|
+
*/
|
|
55
|
+
const LIST_CONNECT_CONCURRENCY = 16;
|
|
56
|
+
/**
|
|
57
|
+
* Maps `items` through `fn` with at most `limit` calls in flight, preserving
|
|
58
|
+
* order in the result. The first rejection propagates (the remaining
|
|
59
|
+
* in-flight calls still run to completion, their results discarded) — the
|
|
60
|
+
* same fail-fast `Promise.all` gives, without its unbounded concurrency.
|
|
61
|
+
*
|
|
62
|
+
* @param items - The items to map.
|
|
63
|
+
* @param limit - Maximum concurrent `fn` calls.
|
|
64
|
+
* @param fn - The async mapping.
|
|
65
|
+
* @returns The results, in `items` order.
|
|
66
|
+
*/
|
|
67
|
+
async function mapWithConcurrency(items, limit, fn) {
|
|
68
|
+
const out = new Array(items.length);
|
|
69
|
+
let next = 0;
|
|
70
|
+
const worker = async () => {
|
|
71
|
+
for (;;) {
|
|
72
|
+
const index = next++;
|
|
73
|
+
if (index >= items.length)
|
|
74
|
+
return;
|
|
75
|
+
out[index] = await fn(items[index]);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
46
81
|
/** Default lifetime for a PTY session; same reasoning as {@link DEFAULT_SPAWN_TIMEOUT_MS}. */
|
|
47
82
|
const DEFAULT_PTY_TIMEOUT_MS = 60 * 60 * 1000;
|
|
48
83
|
/**
|
|
@@ -56,6 +91,32 @@ const ALL_TRAFFIC = '0.0.0.0/0';
|
|
|
56
91
|
/** Hosts the egress probe treats as the canonical allow/deny witnesses. */
|
|
57
92
|
const EGRESS_PROBE_ALLOW = 'registry.npmjs.org';
|
|
58
93
|
const EGRESS_PROBE_DENY = 'example.com';
|
|
94
|
+
/**
|
|
95
|
+
* Race a promise against a one-shot timer, CLEARING the timer the moment the
|
|
96
|
+
* race settles — win, lose, or reject. The bare `Promise.race` leaves the
|
|
97
|
+
* losing timer armed: resolving into an already-settled race is a no-op, but
|
|
98
|
+
* the timer itself stays on the event loop for its whole window, so every
|
|
99
|
+
* call whose command finishes first kept an otherwise-done short-lived
|
|
100
|
+
* process (test worker, CLI script) alive up to the window per call.
|
|
101
|
+
*
|
|
102
|
+
* @param promise - The promise to wait on.
|
|
103
|
+
* @param ms - The timeout window in milliseconds.
|
|
104
|
+
* @returns The promise's value, or null when the window elapsed first.
|
|
105
|
+
*/
|
|
106
|
+
async function raceWithClearedTimer(promise, ms) {
|
|
107
|
+
let timer;
|
|
108
|
+
try {
|
|
109
|
+
return await Promise.race([
|
|
110
|
+
promise,
|
|
111
|
+
new Promise((resolve) => {
|
|
112
|
+
timer = setTimeout(() => resolve(null), ms);
|
|
113
|
+
}),
|
|
114
|
+
]);
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
59
120
|
/**
|
|
60
121
|
* Adapt the real `e2b` SDK `Sandbox` class to {@link E2BSandboxClientLike}.
|
|
61
122
|
* Imported lazily so the SDK is only required when the bond is actually used.
|
|
@@ -108,6 +169,14 @@ async function defaultClient(apiKey) {
|
|
|
108
169
|
volumeMounts: it.volumeMounts,
|
|
109
170
|
});
|
|
110
171
|
};
|
|
172
|
+
// A rejected listing (the promise-of-array SDK shape) must THROW, never
|
|
173
|
+
// degrade to `[]`: every consumer of this listing reads emptiness as
|
|
174
|
+
// absence (`list()`: no live sandboxes; `listVolumes()`: every volume
|
|
175
|
+
// unattached; `listTemplates()`: no template in use). The trailing
|
|
176
|
+
// `.catch(() => result)` is inert to that — a catch handler that
|
|
177
|
+
// returns the SAME rejected promise re-rejects the await below, so the
|
|
178
|
+
// failure always reaches the caller (pinned in
|
|
179
|
+
// __tests__/client-list-shapes.test.ts).
|
|
111
180
|
const r = await Promise.resolve(result).catch(() => result);
|
|
112
181
|
if (Array.isArray(r)) {
|
|
113
182
|
push(r);
|
|
@@ -126,6 +195,26 @@ async function defaultClient(apiKey) {
|
|
|
126
195
|
else if (r && Array.isArray(r.sandboxes)) {
|
|
127
196
|
push(r.sandboxes);
|
|
128
197
|
}
|
|
198
|
+
else {
|
|
199
|
+
// An UNRECOGNIZED shape (a minor SDK change — a new pager wrapper, an
|
|
200
|
+
// `{ items: [...] }` envelope, a bare null) must THROW, never fall
|
|
201
|
+
// through to `[]`: every consumer of this listing reads EMPTINESS as
|
|
202
|
+
// ABSENCE (`list()`: no live sandboxes; `listVolumes()`: every volume
|
|
203
|
+
// unattached; `getTemplate`/`listTemplates()`: no template in use —
|
|
204
|
+
// and an eviction/reclamation sweep deletes on that answer, destroying
|
|
205
|
+
// resources live sandboxes are still using). An unreadable listing is
|
|
206
|
+
// a failure to LOOK, and this bond's contracts never deliver that as
|
|
207
|
+
// "looked, and nothing exists".
|
|
208
|
+
let seen;
|
|
209
|
+
try {
|
|
210
|
+
seen = JSON.stringify(r) ?? String(r);
|
|
211
|
+
}
|
|
212
|
+
catch (_error) {
|
|
213
|
+
// Unserializable (a cyclic wrapper) — the type is the diagnosable part.
|
|
214
|
+
seen = typeof r;
|
|
215
|
+
}
|
|
216
|
+
throw new Error(`e2b: Sandbox.list() returned a shape this adapter cannot read (${seen.slice(0, 200)}) — refusing to answer an empty listing, since every consumer of it reads emptiness as absence`);
|
|
217
|
+
}
|
|
129
218
|
return out;
|
|
130
219
|
},
|
|
131
220
|
};
|
|
@@ -342,10 +431,7 @@ class E2BSandbox {
|
|
|
342
431
|
const finished = handle.wait().finally(() => {
|
|
343
432
|
settled = true;
|
|
344
433
|
});
|
|
345
|
-
const raced = await
|
|
346
|
-
finished,
|
|
347
|
-
new Promise((resolve) => setTimeout(() => resolve(null), SETTLE_PROBE_MS)),
|
|
348
|
-
]);
|
|
434
|
+
const raced = await raceWithClearedTimer(finished, SETTLE_PROBE_MS);
|
|
349
435
|
if (raced)
|
|
350
436
|
return raced;
|
|
351
437
|
if (settled)
|
|
@@ -357,10 +443,7 @@ class E2BSandbox {
|
|
|
357
443
|
// exit code rides the stream's `end` event, so give the handle a bounded
|
|
358
444
|
// window to deliver it — when the process truly finished, envd closes the
|
|
359
445
|
// stream right behind it and the result lands here.
|
|
360
|
-
const reported = await
|
|
361
|
-
finished,
|
|
362
|
-
new Promise((resolve) => setTimeout(() => resolve(null), EXIT_REPORT_GRACE_MS)),
|
|
363
|
-
]);
|
|
446
|
+
const reported = await raceWithClearedTimer(finished, EXIT_REPORT_GRACE_MS);
|
|
364
447
|
if (reported)
|
|
365
448
|
return reported;
|
|
366
449
|
if (typeof handle.exitCode === 'number') {
|
|
@@ -685,6 +768,36 @@ class E2BSandbox {
|
|
|
685
768
|
// Returning a no-op unsubscribe keeps callers that register-and-forget safe.
|
|
686
769
|
return () => { };
|
|
687
770
|
}
|
|
771
|
+
/**
|
|
772
|
+
* Run a short, self-contained command to completion and return its result as
|
|
773
|
+
* data. The SDK's inline `commands.run()` (no `background`) THROWS
|
|
774
|
+
* `CommandExitError` on a non-zero exit — it never returns one — so an
|
|
775
|
+
* `r.exitCode !== 0` check after a bare `await run()` is dead code in
|
|
776
|
+
* production: the caller saw a bare `CommandExitError` ("exit status N") with
|
|
777
|
+
* no stage name and no stderr, and only the test fakes (which return the
|
|
778
|
+
* result) made those checks look live. Mapping the thrown `.result` back to
|
|
779
|
+
* data here — the same mapping `exec()`'s catch applies — makes the callers'
|
|
780
|
+
* exit-code checks real on both shapes.
|
|
781
|
+
*
|
|
782
|
+
* @param cmd - The shell command to run.
|
|
783
|
+
* @param timeoutMs - Deadline for the command in milliseconds.
|
|
784
|
+
* @returns stdout, stderr and the exit code (even when non-zero).
|
|
785
|
+
*/
|
|
786
|
+
async runCommand(cmd, timeoutMs) {
|
|
787
|
+
try {
|
|
788
|
+
return await this.sbx.commands.run(cmd, { timeoutMs });
|
|
789
|
+
}
|
|
790
|
+
catch (error) {
|
|
791
|
+
// A non-zero exit arrives as CommandExitError carrying `.result` — map it
|
|
792
|
+
// to data so the caller's exit-code check owns the failure. Anything else
|
|
793
|
+
// (timeout, connection loss) is a genuine infrastructure failure and must
|
|
794
|
+
// propagate untouched.
|
|
795
|
+
const res = error?.result;
|
|
796
|
+
if (res && typeof res.exitCode === 'number')
|
|
797
|
+
return res;
|
|
798
|
+
throw error;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
688
801
|
/**
|
|
689
802
|
* Extract a POSIX tar stream into the sandbox at `path`.
|
|
690
803
|
*
|
|
@@ -715,7 +828,7 @@ class E2BSandbox {
|
|
|
715
828
|
pending = [];
|
|
716
829
|
pendingBytes = 0;
|
|
717
830
|
await this.sbx.files.write(piecePath, new Blob([piece]));
|
|
718
|
-
const r = await this.
|
|
831
|
+
const r = await this.runCommand(`cat ${piecePath} ${pieces === 0 ? '>' : '>>'} ${tarPath} && rm -f ${piecePath}`, 120_000);
|
|
719
832
|
if (r.exitCode !== 0) {
|
|
720
833
|
throw new Error(`importFiles: spooling the archive failed (${r.exitCode}): ${r.stderr.slice(0, 300)}`);
|
|
721
834
|
}
|
|
@@ -737,9 +850,18 @@ class E2BSandbox {
|
|
|
737
850
|
}
|
|
738
851
|
}
|
|
739
852
|
await flush();
|
|
740
|
-
if (pieces === 0)
|
|
741
|
-
|
|
742
|
-
|
|
853
|
+
if (pieces === 0) {
|
|
854
|
+
// An archive that yielded nothing still gets its (empty) tar created
|
|
855
|
+
// so the extract below fails as BAD ARCHIVE DATA, not as a missing
|
|
856
|
+
// file — and through runCommand, so ITS failure (a full disk is the
|
|
857
|
+
// usual cause) reports its stage and stderr like the spool/extract
|
|
858
|
+
// failures do, never the SDK's bare exit-status error.
|
|
859
|
+
const created = await this.runCommand(`: > ${tarPath}`, 30_000);
|
|
860
|
+
if (created.exitCode !== 0) {
|
|
861
|
+
throw new Error(`importFiles: creating the empty archive failed (${created.exitCode}): ${created.stderr.slice(0, 300)}`);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
const r = await this.runCommand(`mkdir -p ${shellQuote(path)} && tar xf ${tarPath} -C ${shellQuote(path)} --no-same-owner --no-same-permissions`, 300_000);
|
|
743
865
|
if (r.exitCode !== 0) {
|
|
744
866
|
throw new Error(`importFiles: tar extract failed (${r.exitCode}): ${r.stderr.slice(0, 300)}`);
|
|
745
867
|
}
|
|
@@ -779,7 +901,7 @@ class E2BSandbox {
|
|
|
779
901
|
const tarPath = `/tmp/mol-export-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.tar`;
|
|
780
902
|
let bytes;
|
|
781
903
|
try {
|
|
782
|
-
const r = await this.
|
|
904
|
+
const r = await this.runCommand(`tar cf ${tarPath} -C ${shellQuote(parent)} ${shellQuote(name)}`, 300_000);
|
|
783
905
|
if (r.exitCode !== 0) {
|
|
784
906
|
throw new Error(`exportFiles: tar create failed (${r.exitCode}): ${r.stderr.slice(0, 300)}`);
|
|
785
907
|
}
|
|
@@ -817,11 +939,14 @@ class E2BSandbox {
|
|
|
817
939
|
/**
|
|
818
940
|
* E2B implementation of {@link SandboxProvider}.
|
|
819
941
|
*
|
|
820
|
-
*
|
|
821
|
-
*
|
|
822
|
-
*
|
|
823
|
-
*
|
|
824
|
-
*
|
|
942
|
+
* The required surface (`create`/`get`/`list`/`destroy`) plus the optionals the
|
|
943
|
+
* control plane uses are all wired here: `describe`, volumes
|
|
944
|
+
* (`createVolume`/`removeVolume`/`volumeExists`/`listVolumes`), snapshots
|
|
945
|
+
* (`commitTemplate`/`getTemplate`/`listTemplates`/`removeTemplate`) and
|
|
946
|
+
* `verifyEgress`. `verifyEgress` proves deny-by-default by OBSERVING a
|
|
947
|
+
* throwaway probe sandbox, and answers `inconclusive` whenever it cannot look —
|
|
948
|
+
* a failed create, a dead probe — so "could not observe" never wears the shape
|
|
949
|
+
* of the safe verdict.
|
|
825
950
|
*/
|
|
826
951
|
export class E2BSandboxProvider {
|
|
827
952
|
name = 'e2b';
|
|
@@ -1032,18 +1157,23 @@ export class E2BSandboxProvider {
|
|
|
1032
1157
|
const client = await this.client();
|
|
1033
1158
|
const running = await client.list({});
|
|
1034
1159
|
const items = Array.isArray(running) ? running : (running.sandboxes ?? []);
|
|
1035
|
-
|
|
1036
|
-
|
|
1160
|
+
// All the per-sandbox connects are issued through a bounded-concurrency
|
|
1161
|
+
// window, not one awaited after the next: each `get()` is a full connect
|
|
1162
|
+
// round trip, and a fleet sweep over N running sandboxes must not
|
|
1163
|
+
// serialize N of them (O(N) latency on a method the control plane polls) —
|
|
1164
|
+
// while still not putting all N sockets in flight at once (see
|
|
1165
|
+
// {@link LIST_CONNECT_CONCURRENCY}). Same semantics as the serial loop —
|
|
1166
|
+
// a paused sandbox is skipped, a gone sandbox contributes no handle, any
|
|
1167
|
+
// other failure still throws — in ⌈N/16⌉ round-trip depth instead of N.
|
|
1168
|
+
const handles = await mapWithConcurrency(items, LIST_CONNECT_CONCURRENCY, async (it) => {
|
|
1037
1169
|
// A PAUSED sandbox is deliberately skipped: building its handle means
|
|
1038
1170
|
// connecting, and connecting resumes it. Enumerating an account would
|
|
1039
1171
|
// otherwise wake — and start billing — every hibernated project on it.
|
|
1040
1172
|
if (it.state === 'paused')
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
}
|
|
1046
|
-
return handles;
|
|
1173
|
+
return null;
|
|
1174
|
+
return this.get(it.sandboxId);
|
|
1175
|
+
});
|
|
1176
|
+
return handles.filter((h) => h !== null);
|
|
1047
1177
|
}
|
|
1048
1178
|
/**
|
|
1049
1179
|
* Destroy a sandbox, freeing its resources.
|
package/package.json
CHANGED