@miosa/sdk 3.2.3 → 3.2.4

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/index.d.ts CHANGED
@@ -7468,30 +7468,39 @@ declare class Sandbox {
7468
7468
  /** Check readiness of the sandbox (GET /sandboxes/:id/readiness). */
7469
7469
  readiness(): Promise<Record<string, unknown>>;
7470
7470
  /**
7471
- * Block until the sandbox reports ready, or *timeout* seconds elapse.
7471
+ * Block until the sandbox reports ready, or the caller's *timeout* seconds
7472
+ * (the full deadline budget) elapse.
7472
7473
  *
7473
- * When `stream` is `true` (the default) this opens an SSE connection
7474
- * to `GET /sandboxes/:id/readiness/stream` and waits for an
7475
- * `event: ready` frame. The server emits `ready` immediately if the
7476
- * sandbox is already ready, otherwise as soon as the readiness PubSub
7477
- * message fires.
7474
+ * When `stream` is `true` (the default) this opens an SSE connection to
7475
+ * `GET /sandboxes/:id/readiness/stream?timeout=<window>` and waits for an
7476
+ * `event: ready` frame. `<window>` is the caller's remaining budget clamped
7477
+ * to {@link READINESS_STREAM_MAX_TIMEOUT_SEC}, the server's own maximum
7478
+ * lease for a single stream connection — a long-restoring persistent
7479
+ * sandbox (e.g. Docker restore, 60-90 s) legitimately outlives one lease.
7480
+ * The server emits `ready` immediately if the sandbox is already ready,
7481
+ * `event: timeout` when *that connection's* window elapses without a
7482
+ * verdict, and `event: error` on a terminal boot failure.
7478
7483
  *
7479
- * Returns `true` once the sandbox is ready, `false` on `event: timeout`
7480
- * or when the local timeout elapses before ready.
7484
+ * An `event: timeout` (or the stream closing without a verdict) is NOT
7485
+ * treated as the final answer while the caller's overall deadline still has
7486
+ * budget left — that would silently truncate a 180 s wait down to the
7487
+ * server's lease window and return `false` while the sandbox was still
7488
+ * mid-restore. Instead this reconnects the stream (or falls back to
7489
+ * polling) for the remaining budget. `false` is only returned once the
7490
+ * caller's full deadline has elapsed.
7481
7491
  *
7482
7492
  * Throws {@link MiosaError} with code `SANDBOX_BOOT_FAILED` as soon as the
7483
7493
  * sandbox is observed in a terminal failure state (`error` or `destroyed`)
7484
7494
  * while waiting, instead of silently exhausting the timeout and returning
7485
7495
  * `false` — a permanent boot failure and "still booting, keep waiting" must
7486
- * never look the same to the caller. The readiness SSE stream itself has no
7487
- * dedicated failure event today (the server only ever emits
7488
- * `ready`/`status`/`timeout`), so on `event: timeout` this makes one
7489
- * authoritative {@link readiness} check before giving up, to catch a
7490
- * failure that landed right at the boundary.
7496
+ * never look the same to the caller. On each `event: timeout` boundary this
7497
+ * also makes one authoritative {@link readiness} check before deciding to
7498
+ * reconnect or stop, to catch a failure that landed right at the boundary.
7491
7499
  *
7492
7500
  * If the SSE endpoint returns 404 (server pre-dates the streaming
7493
7501
  * endpoint) this transparently falls back to polling {@link readiness}
7494
- * every 10 ms until ready, timeout, or terminal failure.
7502
+ * every 10 ms until ready, the caller's deadline elapses, or terminal
7503
+ * failure.
7495
7504
  */
7496
7505
  waitUntilReady(options?: {
7497
7506
  timeout?: number;
@@ -7522,6 +7531,15 @@ declare class Sandbox {
7522
7531
  * stream endpoint is unavailable (404 or transport error) so callers
7523
7532
  * can fall back to polling.
7524
7533
  */
7534
+ /**
7535
+ * Opens one readiness SSE connection for at most *windowSec* seconds (this
7536
+ * is a single lease, not the caller's overall {@link waitUntilReady}
7537
+ * budget — see there for how leases are chained). *windowSec* is sent to
7538
+ * the server as `?timeout=<seconds>` so the server's own stream deadline
7539
+ * matches what was actually requested instead of always falling back to
7540
+ * its 30 s default, which is what let a 180 s `waitUntilReady` call get
7541
+ * cut short at 30 s.
7542
+ */
7525
7543
  private tryReadinessStream;
7526
7544
  destroy(): Promise<void>;
7527
7545
  delete(): Promise<void>;
package/dist/index.js CHANGED
@@ -174,7 +174,7 @@ var TokenRefreshFailedError = class extends MiosaError {
174
174
  };
175
175
 
176
176
  // src/version.ts
177
- var SDK_VERSION = "3.2.3";
177
+ var SDK_VERSION = "3.2.4";
178
178
  var SDK_USER_AGENT = `@miosa/sdk/${SDK_VERSION}`;
179
179
 
180
180
  // src/http.ts
@@ -8517,6 +8517,7 @@ function isCommandReady(payload) {
8517
8517
  return status === "ready";
8518
8518
  });
8519
8519
  }
8520
+ var READINESS_STREAM_MAX_TIMEOUT_SEC = 120;
8520
8521
  var SANDBOX_TEMPLATE = "miosa-sandbox";
8521
8522
  var SANDBOX_TIER_BY_CPU = {
8522
8523
  1: { size: "xs", memoryMb: 2048 },
@@ -9565,47 +9566,66 @@ var Sandbox = class _Sandbox {
9565
9566
  );
9566
9567
  }
9567
9568
  /**
9568
- * Block until the sandbox reports ready, or *timeout* seconds elapse.
9569
+ * Block until the sandbox reports ready, or the caller's *timeout* seconds
9570
+ * (the full deadline budget) elapse.
9569
9571
  *
9570
- * When `stream` is `true` (the default) this opens an SSE connection
9571
- * to `GET /sandboxes/:id/readiness/stream` and waits for an
9572
- * `event: ready` frame. The server emits `ready` immediately if the
9573
- * sandbox is already ready, otherwise as soon as the readiness PubSub
9574
- * message fires.
9572
+ * When `stream` is `true` (the default) this opens an SSE connection to
9573
+ * `GET /sandboxes/:id/readiness/stream?timeout=<window>` and waits for an
9574
+ * `event: ready` frame. `<window>` is the caller's remaining budget clamped
9575
+ * to {@link READINESS_STREAM_MAX_TIMEOUT_SEC}, the server's own maximum
9576
+ * lease for a single stream connection — a long-restoring persistent
9577
+ * sandbox (e.g. Docker restore, 60-90 s) legitimately outlives one lease.
9578
+ * The server emits `ready` immediately if the sandbox is already ready,
9579
+ * `event: timeout` when *that connection's* window elapses without a
9580
+ * verdict, and `event: error` on a terminal boot failure.
9575
9581
  *
9576
- * Returns `true` once the sandbox is ready, `false` on `event: timeout`
9577
- * or when the local timeout elapses before ready.
9582
+ * An `event: timeout` (or the stream closing without a verdict) is NOT
9583
+ * treated as the final answer while the caller's overall deadline still has
9584
+ * budget left — that would silently truncate a 180 s wait down to the
9585
+ * server's lease window and return `false` while the sandbox was still
9586
+ * mid-restore. Instead this reconnects the stream (or falls back to
9587
+ * polling) for the remaining budget. `false` is only returned once the
9588
+ * caller's full deadline has elapsed.
9578
9589
  *
9579
9590
  * Throws {@link MiosaError} with code `SANDBOX_BOOT_FAILED` as soon as the
9580
9591
  * sandbox is observed in a terminal failure state (`error` or `destroyed`)
9581
9592
  * while waiting, instead of silently exhausting the timeout and returning
9582
9593
  * `false` — a permanent boot failure and "still booting, keep waiting" must
9583
- * never look the same to the caller. The readiness SSE stream itself has no
9584
- * dedicated failure event today (the server only ever emits
9585
- * `ready`/`status`/`timeout`), so on `event: timeout` this makes one
9586
- * authoritative {@link readiness} check before giving up, to catch a
9587
- * failure that landed right at the boundary.
9594
+ * never look the same to the caller. On each `event: timeout` boundary this
9595
+ * also makes one authoritative {@link readiness} check before deciding to
9596
+ * reconnect or stop, to catch a failure that landed right at the boundary.
9588
9597
  *
9589
9598
  * If the SSE endpoint returns 404 (server pre-dates the streaming
9590
9599
  * endpoint) this transparently falls back to polling {@link readiness}
9591
- * every 10 ms until ready, timeout, or terminal failure.
9600
+ * every 10 ms until ready, the caller's deadline elapses, or terminal
9601
+ * failure.
9592
9602
  */
9593
9603
  async waitUntilReady(options = {}) {
9594
9604
  const timeout = options.timeout ?? 30;
9595
9605
  const stream = options.stream ?? true;
9596
9606
  const requireCommandReady = options.requireCommandReady ?? true;
9607
+ const deadlineMs = Date.now() + timeout * 1e3;
9597
9608
  if (stream) {
9598
- const sseResult = await this.tryReadinessStream(timeout);
9599
- if (sseResult === true) {
9600
- await this.adoptReadyState();
9601
- return true;
9602
- }
9603
- if (sseResult === false) {
9604
- await this.throwIfTerminallyFailed();
9605
- return false;
9609
+ while (true) {
9610
+ const remainingMs = deadlineMs - Date.now();
9611
+ if (remainingMs <= 0) return false;
9612
+ const remainingSec = remainingMs / 1e3;
9613
+ const clamped = remainingSec > READINESS_STREAM_MAX_TIMEOUT_SEC;
9614
+ const windowSec = clamped ? READINESS_STREAM_MAX_TIMEOUT_SEC : remainingSec;
9615
+ const sseResult = await this.tryReadinessStream(windowSec);
9616
+ if (sseResult === true) {
9617
+ await this.adoptReadyState();
9618
+ return true;
9619
+ }
9620
+ if (sseResult === false) {
9621
+ await this.throwIfTerminallyFailed();
9622
+ if (!clamped || Date.now() >= deadlineMs) return false;
9623
+ continue;
9624
+ }
9625
+ break;
9606
9626
  }
9627
+ if (Date.now() >= deadlineMs) return false;
9607
9628
  }
9608
- const deadlineMs = Date.now() + timeout * 1e3;
9609
9629
  while (Date.now() < deadlineMs) {
9610
9630
  try {
9611
9631
  const data = await this.readiness();
@@ -9669,9 +9689,25 @@ var Sandbox = class _Sandbox {
9669
9689
  * stream endpoint is unavailable (404 or transport error) so callers
9670
9690
  * can fall back to polling.
9671
9691
  */
9672
- async tryReadinessStream(timeoutSec) {
9692
+ /**
9693
+ * Opens one readiness SSE connection for at most *windowSec* seconds (this
9694
+ * is a single lease, not the caller's overall {@link waitUntilReady}
9695
+ * budget — see there for how leases are chained). *windowSec* is sent to
9696
+ * the server as `?timeout=<seconds>` so the server's own stream deadline
9697
+ * matches what was actually requested instead of always falling back to
9698
+ * its 30 s default, which is what let a 180 s `waitUntilReady` call get
9699
+ * cut short at 30 s.
9700
+ */
9701
+ async tryReadinessStream(windowSec) {
9702
+ const clampedWindowSec = Math.max(
9703
+ 1,
9704
+ Math.min(windowSec, READINESS_STREAM_MAX_TIMEOUT_SEC)
9705
+ );
9673
9706
  const abort = new AbortController();
9674
- const timer = setTimeout(() => abort.abort(), (timeoutSec + 5) * 1e3);
9707
+ const timer = setTimeout(
9708
+ () => abort.abort(),
9709
+ (clampedWindowSec + 5) * 1e3
9710
+ );
9675
9711
  try {
9676
9712
  const headers = {
9677
9713
  Authorization: `Bearer ${this.http.apiKey}`,
@@ -9683,7 +9719,7 @@ var Sandbox = class _Sandbox {
9683
9719
  "User-Agent": SDK_USER_AGENT
9684
9720
  };
9685
9721
  const response = await fetch(
9686
- `${this.http.baseUrl}/sandboxes/${this.id}/readiness/stream`,
9722
+ `${this.http.baseUrl}/sandboxes/${this.id}/readiness/stream?timeout=${clampedWindowSec}`,
9687
9723
  { method: "GET", headers, signal: abort.signal }
9688
9724
  );
9689
9725
  if (response.status === 404) return null;
@@ -9704,6 +9740,7 @@ var Sandbox = class _Sandbox {
9704
9740
  const evt = line.slice(6).trim();
9705
9741
  if (evt === "ready") return true;
9706
9742
  if (evt === "timeout") return false;
9743
+ if (evt === "error") throw this.bootFailedError("error");
9707
9744
  }
9708
9745
  }
9709
9746
  }
@@ -9714,7 +9751,10 @@ var Sandbox = class _Sandbox {
9714
9751
  }
9715
9752
  }
9716
9753
  return null;
9717
- } catch {
9754
+ } catch (err) {
9755
+ if (err instanceof MiosaError && err.code === "SANDBOX_BOOT_FAILED") {
9756
+ throw err;
9757
+ }
9718
9758
  return null;
9719
9759
  } finally {
9720
9760
  clearTimeout(timer);