@blaxel/core 0.3.24 → 0.3.25-preview.305
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/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/common/settings.js +2 -2
- package/dist/cjs/sandbox/sandbox.js +10 -101
- package/dist/cjs/types/sandbox/sandbox.d.ts +9 -1
- package/dist/cjs-browser/.tsbuildinfo +1 -1
- package/dist/cjs-browser/common/settings.js +2 -2
- package/dist/cjs-browser/sandbox/sandbox.js +10 -101
- package/dist/cjs-browser/types/sandbox/sandbox.d.ts +9 -1
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/common/settings.js +2 -2
- package/dist/esm/sandbox/sandbox.js +10 -101
- package/dist/esm-browser/.tsbuildinfo +1 -1
- package/dist/esm-browser/common/settings.js +2 -2
- package/dist/esm-browser/sandbox/sandbox.js +10 -101
- package/package.json +1 -1
|
@@ -24,8 +24,8 @@ function missingCredentialsMessage() {
|
|
|
24
24
|
return "No Blaxel credentials found. Set the BL_API_KEY and BL_WORKSPACE environment variables, or run `bl login`.";
|
|
25
25
|
}
|
|
26
26
|
// Build info - these placeholders are replaced at build time by build:replace-imports
|
|
27
|
-
const BUILD_VERSION = "0.3.
|
|
28
|
-
const BUILD_COMMIT = "
|
|
27
|
+
const BUILD_VERSION = "0.3.25-preview.305";
|
|
28
|
+
const BUILD_COMMIT = "ace03ab19f4092ad71b760bc05a61e53cfe6b465";
|
|
29
29
|
const BUILD_SENTRY_DSN = "https://fd5e60e1c9820e1eef5ccebb84a07127@o4508714045276160.ingest.us.sentry.io/4510465864564736";
|
|
30
30
|
const BLAXEL_API_VERSION = "2026-09-22";
|
|
31
31
|
// Bun < 1.3.11 never sends connection-level WINDOW_UPDATE: the pooled h2
|
|
@@ -14,24 +14,6 @@ import { SandboxSnapshotsResource } from "./snapshot.js";
|
|
|
14
14
|
import { SandboxSessions } from "./session.js";
|
|
15
15
|
import { SandboxSystem } from "./system.js";
|
|
16
16
|
import { normalizeEnvs, normalizePorts, normalizeVolumes } from "./types.js";
|
|
17
|
-
const NON_REUSABLE_SANDBOX_STATUSES = new Set([
|
|
18
|
-
"FAILED",
|
|
19
|
-
"TERMINATED",
|
|
20
|
-
"TERMINATING",
|
|
21
|
-
"DELETING",
|
|
22
|
-
"DEACTIVATING",
|
|
23
|
-
]);
|
|
24
|
-
// Statuses that resolve on their own (a delete or deactivation in flight). The control
|
|
25
|
-
// plane keeps answering 409 to creates while the record is in one of these, so retrying
|
|
26
|
-
// instantly burns the whole attempt budget inside the window. Terminal statuses
|
|
27
|
-
// (FAILED, TERMINATED) accept a create immediately and are not listed here.
|
|
28
|
-
const TRANSIENT_SANDBOX_STATUSES = new Set([
|
|
29
|
-
"TERMINATING",
|
|
30
|
-
"DELETING",
|
|
31
|
-
"DEACTIVATING",
|
|
32
|
-
]);
|
|
33
|
-
const TRANSIENT_STATUS_MAX_WAIT_MS = 30_000;
|
|
34
|
-
const TRANSIENT_STATUS_POLL_MS = 500;
|
|
35
17
|
// Archiving a filesystem, and restoring it, take as long as that filesystem is
|
|
36
18
|
// big — minutes for a few gigabytes.
|
|
37
19
|
const ARCHIVE_MAX_WAIT_MS = 1_800_000;
|
|
@@ -641,71 +623,17 @@ export class SandboxInstance {
|
|
|
641
623
|
const instance = new SandboxInstance(data);
|
|
642
624
|
return SandboxInstance.attachH2Session(instance);
|
|
643
625
|
}
|
|
626
|
+
/**
|
|
627
|
+
* Create the sandbox, or return the one already holding this name.
|
|
628
|
+
*
|
|
629
|
+
* The control plane owns the reconciliation: an alive sandbox is returned as
|
|
630
|
+
* is, a FAILED/TERMINATED one is replaced, and a deletion or concurrent
|
|
631
|
+
* creation still in flight is waited for server-side. A 409 therefore only
|
|
632
|
+
* surfaces when the name really cannot be used, and is thrown as is.
|
|
633
|
+
* `options` (e.g. `timeout`) are forwarded to `create`.
|
|
634
|
+
*/
|
|
644
635
|
static async createIfNotExists(sandbox, options = {}) {
|
|
645
|
-
|
|
646
|
-
let lastStatus = "unknown";
|
|
647
|
-
// The 'vanished' window (create 409s while get 404s) is driven by the same
|
|
648
|
-
// transient causes as 'dying': an in-flight delete finishing, or a
|
|
649
|
-
// concurrent create whose row is not readable yet. Give it the same time
|
|
650
|
-
// budget as waitWhileSandboxDying instead of burning the attempt budget
|
|
651
|
-
// 500ms apart (~1s total), which threw on deletes taking >1s (ENG-3667).
|
|
652
|
-
const vanishedDeadline = Date.now() + TRANSIENT_STATUS_MAX_WAIT_MS;
|
|
653
|
-
for (let i = 0; i < ATTEMPTS; ++i) {
|
|
654
|
-
const finalAttempt = i === ATTEMPTS - 1;
|
|
655
|
-
try {
|
|
656
|
-
return await this.create(sandbox, { ...options, createIfNotExist: true });
|
|
657
|
-
}
|
|
658
|
-
catch (e) {
|
|
659
|
-
if (typeof e === "object" && e !== null && "code" in e && (e.code === 409 || e.code === 'SANDBOX_ALREADY_EXISTS')) {
|
|
660
|
-
const name = 'name' in sandbox ? sandbox.name : sandbox.metadata.name;
|
|
661
|
-
if (!name) {
|
|
662
|
-
throw new Error("Sandbox name is required");
|
|
663
|
-
}
|
|
664
|
-
// The controlplane tags the creation-lock 409 with
|
|
665
|
-
// reason=CREATION_IN_PROGRESS when the conflict comes from a
|
|
666
|
-
// concurrent create still in flight (ENG-3776). The field is absent
|
|
667
|
-
// on older controlplanes and on real row conflicts, so it only
|
|
668
|
-
// sharpens the give-up error; the polling behavior is the same.
|
|
669
|
-
const creationInProgress = e.reason === "CREATION_IN_PROGRESS";
|
|
670
|
-
// Get the existing sandbox to check its status
|
|
671
|
-
let sandboxInstance;
|
|
672
|
-
try {
|
|
673
|
-
sandboxInstance = await this.get(name);
|
|
674
|
-
}
|
|
675
|
-
catch (getError) {
|
|
676
|
-
if (isSandboxNotFound(getError)) {
|
|
677
|
-
// The record vanished between the create conflict and this status check.
|
|
678
|
-
lastStatus = creationInProgress ? "creation in progress" : "vanished";
|
|
679
|
-
if (Date.now() < vanishedDeadline) {
|
|
680
|
-
// Inside the transient window: poll without consuming attempts.
|
|
681
|
-
await new Promise((resolve) => setTimeout(resolve, TRANSIENT_STATUS_POLL_MS));
|
|
682
|
-
--i;
|
|
683
|
-
}
|
|
684
|
-
else if (!finalAttempt) {
|
|
685
|
-
await new Promise((resolve) => setTimeout(resolve, TRANSIENT_STATUS_POLL_MS));
|
|
686
|
-
}
|
|
687
|
-
continue;
|
|
688
|
-
}
|
|
689
|
-
throw getError;
|
|
690
|
-
}
|
|
691
|
-
// Recreate instead of returning sandbox records that cannot be reused.
|
|
692
|
-
if (!NON_REUSABLE_SANDBOX_STATUSES.has(sandboxInstance.status ?? "")) {
|
|
693
|
-
return sandboxInstance;
|
|
694
|
-
}
|
|
695
|
-
// A delete or deactivation in flight rejects creates until it finishes;
|
|
696
|
-
// wait it out instead of burning the remaining attempts inside the window.
|
|
697
|
-
// No point waiting after the last attempt: nothing will use the result.
|
|
698
|
-
lastStatus = sandboxInstance.status ?? "unknown";
|
|
699
|
-
if (TRANSIENT_SANDBOX_STATUSES.has(lastStatus) && !finalAttempt) {
|
|
700
|
-
await this.waitWhileSandboxDying(name);
|
|
701
|
-
}
|
|
702
|
-
// Retry creation. We want the same error handling on the retry as creates can race.
|
|
703
|
-
continue;
|
|
704
|
-
}
|
|
705
|
-
throw e;
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
throw new Error(`Unable to create sandbox after ${ATTEMPTS} attempts. Last conflicting status: ${lastStatus}.`);
|
|
636
|
+
return this.create(sandbox, { ...options, createIfNotExist: true });
|
|
709
637
|
}
|
|
710
638
|
// Poll the record after a create was cut by the edge with a 504 while the
|
|
711
639
|
// control plane is still deploying it. Resolves with the sandbox once it
|
|
@@ -736,25 +664,6 @@ export class SandboxInstance {
|
|
|
736
664
|
}
|
|
737
665
|
throw createError;
|
|
738
666
|
}
|
|
739
|
-
// Poll the record until an in-flight delete/deactivation settles (or the record
|
|
740
|
-
// disappears), bounded by TRANSIENT_STATUS_MAX_WAIT_MS. Errors from get (e.g. 404
|
|
741
|
-
// once the record is gone) end the wait: the caller's create retry decides next.
|
|
742
|
-
static async waitWhileSandboxDying(name) {
|
|
743
|
-
const deadline = Date.now() + TRANSIENT_STATUS_MAX_WAIT_MS;
|
|
744
|
-
while (Date.now() < deadline) {
|
|
745
|
-
await new Promise((resolve) => setTimeout(resolve, TRANSIENT_STATUS_POLL_MS));
|
|
746
|
-
try {
|
|
747
|
-
const current = await this.get(name);
|
|
748
|
-
if (!TRANSIENT_SANDBOX_STATUSES.has(current.status ?? "")) {
|
|
749
|
-
return;
|
|
750
|
-
}
|
|
751
|
-
logger.debug(`Sandbox ${name} still ${current.status}; waiting for the record to settle before recreating`);
|
|
752
|
-
}
|
|
753
|
-
catch {
|
|
754
|
-
return;
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
667
|
/* eslint-disable */
|
|
759
668
|
static async fromSession(session) {
|
|
760
669
|
// Create a minimal sandbox configuration for session-based access
|