@blaxel/core 0.3.23 → 0.3.24-preview.303

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.
@@ -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.23";
28
- const BUILD_COMMIT = "3f7319f88bf305c481c80932c29c7513c85b543d";
27
+ const BUILD_VERSION = "0.3.24-preview.303";
28
+ const BUILD_COMMIT = "d3dcd1599047a3cadaacac6805f29e210e57aaad";
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
@@ -58,6 +58,62 @@ const ARCHIVE_ENTRY_MAX_WAIT_MS = 30_000;
58
58
  const CREATE_GATEWAY_TIMEOUT_MAX_WAIT_MS = 120_000;
59
59
  const CREATE_GATEWAY_TIMEOUT_BASE_POLL_MS = 1_000;
60
60
  const CREATE_GATEWAY_TIMEOUT_MAX_POLL_MS = 5_000;
61
+ // A creation deadline the caller sets per request through an undocumented
62
+ // header; it can only shorten the control plane's default. Capped below the
63
+ // edge's 60s origin-read timeout so the control plane's 408 always reaches the
64
+ // client instead of being masked by an edge 504.
65
+ const CREATION_TIMEOUT_HEADER = "X-Blaxel-Creation-Timeout";
66
+ export const MAX_CREATION_TIMEOUT_SECONDS = 50;
67
+ /**
68
+ * Thrown by SandboxInstance.create / createIfNotExists when the sandbox was
69
+ * not ready within the creation deadline. The control plane has released the
70
+ * sandbox, so the name is free again and the creation can simply be started
71
+ * over. Catch it with `err instanceof SandboxCreationTimeoutError` or
72
+ * `isCreationTimeoutError(err)`.
73
+ */
74
+ export class SandboxCreationTimeoutError extends Error {
75
+ sandboxName;
76
+ timeout;
77
+ code = "CREATION_TIMEOUT";
78
+ status = 408;
79
+ /** Raw error body returned by the control plane. */
80
+ data;
81
+ constructor(
82
+ /** Name of the sandbox that was being created, if one was requested. */
83
+ sandboxName,
84
+ /** The `timeout` option of the call, in seconds; undefined when the control plane's default applied. */
85
+ timeout, data) {
86
+ const detail = typeof data === "object" && data !== null && typeof data.message === "string"
87
+ ? data.message
88
+ : undefined;
89
+ const target = sandboxName ? `Sandbox ${sandboxName}` : "Sandbox";
90
+ const deadline = timeout !== undefined ? ` within ${timeout}s` : " within the creation deadline";
91
+ super(`${target} was not ready${deadline}; the creation was cancelled.${detail ? ` ${detail}` : ""}`);
92
+ this.sandboxName = sandboxName;
93
+ this.timeout = timeout;
94
+ this.name = "SandboxCreationTimeoutError";
95
+ this.data = data;
96
+ }
97
+ }
98
+ /** True when `err` is a sandbox creation timeout (408 CREATION_TIMEOUT). */
99
+ export function isCreationTimeoutError(err) {
100
+ return err instanceof SandboxCreationTimeoutError;
101
+ }
102
+ const isCreationTimeoutResponse = (status, e) => {
103
+ if (status === 408)
104
+ return true;
105
+ if (typeof e !== "object" || e === null)
106
+ return false;
107
+ return e.code === "CREATION_TIMEOUT";
108
+ };
109
+ function validateCreateOptions({ timeout }) {
110
+ if (timeout === undefined)
111
+ return {};
112
+ if (!Number.isInteger(timeout) || timeout < 1 || timeout > MAX_CREATION_TIMEOUT_SECONDS) {
113
+ throw new Error(`SandboxInstance.create: 'timeout' must be a whole number of seconds between 1 and ${MAX_CREATION_TIMEOUT_SECONDS}, got ${timeout}.`);
114
+ }
115
+ return { timeout };
116
+ }
61
117
  const isSandboxNotFound = (e) => {
62
118
  if (typeof e !== "object" || e === null)
63
119
  return false;
@@ -252,7 +308,9 @@ export class SandboxInstance {
252
308
  logger.warn("⚠️ Warning: sandbox.wait() is deprecated. You don't need to wait for the sandbox to be deployed anymore.");
253
309
  return this;
254
310
  }
255
- static async create(sandbox, { safe = false, createIfNotExist = false } = {}) {
311
+ static async create(sandbox, options = {}) {
312
+ const { safe = false, createIfNotExist = false } = options;
313
+ const { timeout } = validateCreateOptions(options);
256
314
  // No client-side default name: when the caller omits a name we send the
257
315
  // creation without metadata.name so the server can assign one and unnamed
258
316
  // creations become eligible for warm sandbox pools (ENG-3931).
@@ -334,16 +392,21 @@ export class SandboxInstance {
334
392
  if (edgeDomain && !settings.disableH2) {
335
393
  import("../common/h2pool.js").then(({ h2Pool }) => h2Pool.warm(edgeDomain)).catch(() => { });
336
394
  }
395
+ const headers = timeout !== undefined ? { [CREATION_TIMEOUT_HEADER]: String(timeout) } : undefined;
337
396
  const [createResult, h2Session] = await Promise.all([
338
397
  createSandbox({
339
398
  body: sandbox,
340
399
  query: createIfNotExist ? { createIfNotExist } : undefined,
400
+ headers,
341
401
  }),
342
402
  edgeDomain && !settings.disableH2 ? import("../common/h2pool.js").then(({ h2Pool }) => h2Pool.get(edgeDomain)).catch(() => null) : Promise.resolve(null),
343
403
  ]);
344
404
  let data = createResult.data;
345
405
  if (createResult.error !== undefined) {
346
406
  const name = sandbox.metadata.name;
407
+ if (isCreationTimeoutResponse(createResult.response.status, createResult.error)) {
408
+ throw new SandboxCreationTimeoutError(name, timeout, createResult.error);
409
+ }
347
410
  if (createResult.response.status === 504 && name) {
348
411
  // The edge gave up on the connection but the creation is still running
349
412
  // server-side; wait for the record instead of failing (ENG-3662).
@@ -578,7 +641,7 @@ export class SandboxInstance {
578
641
  const instance = new SandboxInstance(data);
579
642
  return SandboxInstance.attachH2Session(instance);
580
643
  }
581
- static async createIfNotExists(sandbox) {
644
+ static async createIfNotExists(sandbox, options = {}) {
582
645
  const ATTEMPTS = 3;
583
646
  let lastStatus = "unknown";
584
647
  // The 'vanished' window (create 409s while get 404s) is driven by the same
@@ -590,7 +653,7 @@ export class SandboxInstance {
590
653
  for (let i = 0; i < ATTEMPTS; ++i) {
591
654
  const finalAttempt = i === ATTEMPTS - 1;
592
655
  try {
593
- return await this.create(sandbox, { createIfNotExist: true });
656
+ return await this.create(sandbox, { ...options, createIfNotExist: true });
594
657
  }
595
658
  catch (e) {
596
659
  if (typeof e === "object" && e !== null && "code" in e && (e.code === 409 || e.code === 'SANDBOX_ALREADY_EXISTS')) {