@blaxel/core 0.2.87 → 0.2.88
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 +62 -2
- package/dist/cjs/types/sandbox/sandbox.d.ts +1 -0
- package/dist/cjs-browser/.tsbuildinfo +1 -1
- package/dist/cjs-browser/common/settings.js +2 -2
- package/dist/cjs-browser/sandbox/sandbox.js +62 -2
- package/dist/cjs-browser/types/sandbox/sandbox.d.ts +1 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/common/settings.js +2 -2
- package/dist/esm/sandbox/sandbox.js +62 -2
- package/dist/esm-browser/.tsbuildinfo +1 -1
- package/dist/esm-browser/common/settings.js +2 -2
- package/dist/esm-browser/sandbox/sandbox.js +62 -2
- package/package.json +1 -1
|
@@ -22,8 +22,8 @@ function missingCredentialsMessage() {
|
|
|
22
22
|
return "No Blaxel credentials found. Set the BL_API_KEY and BL_WORKSPACE environment variables, or run `bl login`.";
|
|
23
23
|
}
|
|
24
24
|
// Build info - these placeholders are replaced at build time by build:replace-imports
|
|
25
|
-
const BUILD_VERSION = "0.2.
|
|
26
|
-
const BUILD_COMMIT = "
|
|
25
|
+
const BUILD_VERSION = "0.2.88";
|
|
26
|
+
const BUILD_COMMIT = "3d4dcbfdf0ac479adef35da9cd8523954af3e7f7";
|
|
27
27
|
const BUILD_SENTRY_DSN = "https://fd5e60e1c9820e1eef5ccebb84a07127@o4508714045276160.ingest.us.sentry.io/4510465864564736";
|
|
28
28
|
const BLAXEL_API_VERSION = "2026-04-16";
|
|
29
29
|
// Cache for config.yaml tracking value
|
|
@@ -18,6 +18,23 @@ const NON_REUSABLE_SANDBOX_STATUSES = new Set([
|
|
|
18
18
|
"DELETING",
|
|
19
19
|
"DEACTIVATING",
|
|
20
20
|
]);
|
|
21
|
+
// Statuses that resolve on their own (a delete or deactivation in flight). The control
|
|
22
|
+
// plane keeps answering 409 to creates while the record is in one of these, so retrying
|
|
23
|
+
// instantly burns the whole attempt budget inside the window. Terminal statuses
|
|
24
|
+
// (FAILED, TERMINATED) accept a create immediately and are not listed here.
|
|
25
|
+
const TRANSIENT_SANDBOX_STATUSES = new Set([
|
|
26
|
+
"TERMINATING",
|
|
27
|
+
"DELETING",
|
|
28
|
+
"DEACTIVATING",
|
|
29
|
+
]);
|
|
30
|
+
const TRANSIENT_STATUS_MAX_WAIT_MS = 30_000;
|
|
31
|
+
const TRANSIENT_STATUS_POLL_MS = 500;
|
|
32
|
+
const isSandboxNotFound = (e) => {
|
|
33
|
+
if (typeof e !== "object" || e === null)
|
|
34
|
+
return false;
|
|
35
|
+
const candidate = e;
|
|
36
|
+
return candidate.code === 404 || candidate.code === "404" || candidate.status === 404;
|
|
37
|
+
};
|
|
21
38
|
export class SandboxInstance {
|
|
22
39
|
sandbox;
|
|
23
40
|
fs;
|
|
@@ -286,7 +303,9 @@ export class SandboxInstance {
|
|
|
286
303
|
}
|
|
287
304
|
static async createIfNotExists(sandbox) {
|
|
288
305
|
const ATTEMPTS = 3;
|
|
306
|
+
let lastStatus = "unknown";
|
|
289
307
|
for (let i = 0; i < ATTEMPTS; ++i) {
|
|
308
|
+
const finalAttempt = i === ATTEMPTS - 1;
|
|
290
309
|
try {
|
|
291
310
|
return await this.create(sandbox, { createIfNotExist: true });
|
|
292
311
|
}
|
|
@@ -297,18 +316,59 @@ export class SandboxInstance {
|
|
|
297
316
|
throw new Error("Sandbox name is required");
|
|
298
317
|
}
|
|
299
318
|
// Get the existing sandbox to check its status
|
|
300
|
-
|
|
319
|
+
let sandboxInstance;
|
|
320
|
+
try {
|
|
321
|
+
sandboxInstance = await this.get(name);
|
|
322
|
+
}
|
|
323
|
+
catch (getError) {
|
|
324
|
+
if (isSandboxNotFound(getError)) {
|
|
325
|
+
// The record vanished between the create conflict and this status check
|
|
326
|
+
// (its deletion just finished); give the control plane a beat and retry.
|
|
327
|
+
lastStatus = "vanished";
|
|
328
|
+
if (!finalAttempt) {
|
|
329
|
+
await new Promise((resolve) => setTimeout(resolve, TRANSIENT_STATUS_POLL_MS));
|
|
330
|
+
}
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
throw getError;
|
|
334
|
+
}
|
|
301
335
|
// Recreate instead of returning sandbox records that cannot be reused.
|
|
302
336
|
if (!NON_REUSABLE_SANDBOX_STATUSES.has(sandboxInstance.status ?? "")) {
|
|
303
337
|
return sandboxInstance;
|
|
304
338
|
}
|
|
339
|
+
// A delete or deactivation in flight rejects creates until it finishes;
|
|
340
|
+
// wait it out instead of burning the remaining attempts inside the window.
|
|
341
|
+
// No point waiting after the last attempt: nothing will use the result.
|
|
342
|
+
lastStatus = sandboxInstance.status ?? "unknown";
|
|
343
|
+
if (TRANSIENT_SANDBOX_STATUSES.has(lastStatus) && !finalAttempt) {
|
|
344
|
+
await this.waitWhileSandboxDying(name);
|
|
345
|
+
}
|
|
305
346
|
// Retry creation. We want the same error handling on the retry as creates can race.
|
|
306
347
|
continue;
|
|
307
348
|
}
|
|
308
349
|
throw e;
|
|
309
350
|
}
|
|
310
351
|
}
|
|
311
|
-
throw new Error(`Unable to create sandbox after ${ATTEMPTS} attempts.`);
|
|
352
|
+
throw new Error(`Unable to create sandbox after ${ATTEMPTS} attempts. Last conflicting status: ${lastStatus}.`);
|
|
353
|
+
}
|
|
354
|
+
// Poll the record until an in-flight delete/deactivation settles (or the record
|
|
355
|
+
// disappears), bounded by TRANSIENT_STATUS_MAX_WAIT_MS. Errors from get (e.g. 404
|
|
356
|
+
// once the record is gone) end the wait: the caller's create retry decides next.
|
|
357
|
+
static async waitWhileSandboxDying(name) {
|
|
358
|
+
const deadline = Date.now() + TRANSIENT_STATUS_MAX_WAIT_MS;
|
|
359
|
+
while (Date.now() < deadline) {
|
|
360
|
+
await new Promise((resolve) => setTimeout(resolve, TRANSIENT_STATUS_POLL_MS));
|
|
361
|
+
try {
|
|
362
|
+
const current = await this.get(name);
|
|
363
|
+
if (!TRANSIENT_SANDBOX_STATUSES.has(current.status ?? "")) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
logger.debug(`Sandbox ${name} still ${current.status}; waiting for the record to settle before recreating`);
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
312
372
|
}
|
|
313
373
|
/* eslint-disable */
|
|
314
374
|
static async fromSession(session) {
|