@gigadrive/sdk 0.9.4 → 0.10.0

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.js CHANGED
@@ -44,12 +44,34 @@ __export(src_exports, {
44
44
  ApplicationStorageResource: () => ApplicationStorageResource,
45
45
  ApplicationsResource: () => ApplicationsResource,
46
46
  AuthenticationError: () => AuthenticationError,
47
+ Command: () => Command,
48
+ CommandFailedError: () => CommandFailedError,
47
49
  ConfigurationError: () => ConfigurationError,
48
50
  DeploymentsResource: () => DeploymentsResource,
49
51
  DomainNotActiveError: () => DomainNotActiveError,
50
52
  GigadriveClient: () => GigadriveClient,
51
53
  GigadriveError: () => GigadriveError,
52
54
  ImageOptimizationResource: () => ImageOptimizationResource,
55
+ MICROVM_MAX_TIMEOUT_MS: () => MICROVM_MAX_TIMEOUT_MS,
56
+ MicroVM: () => MicroVM,
57
+ MicroVmBusyError: () => MicroVmBusyError,
58
+ MicroVmCleanupError: () => MicroVmCleanupError,
59
+ MicroVmCommands: () => MicroVmCommands,
60
+ MicroVmCommandsResource: () => MicroVmCommandsResource,
61
+ MicroVmFileNotFoundError: () => MicroVmFileNotFoundError,
62
+ MicroVmFiles: () => MicroVmFiles,
63
+ MicroVmFilesResource: () => MicroVmFilesResource,
64
+ MicroVmImageBuildError: () => MicroVmImageBuildError,
65
+ MicroVmImageNotReadyError: () => MicroVmImageNotReadyError,
66
+ MicroVmImagesResource: () => MicroVmImagesResource,
67
+ MicroVmNotFoundError: () => MicroVmNotFoundError,
68
+ MicroVmNotRunningError: () => MicroVmNotRunningError,
69
+ MicroVmPortNotExposedError: () => MicroVmPortNotExposedError,
70
+ MicroVmQuotaExceededError: () => MicroVmQuotaExceededError,
71
+ MicroVmStartError: () => MicroVmStartError,
72
+ MicroVmTimeoutError: () => MicroVmTimeoutError,
73
+ MicroVmUnreachableError: () => MicroVmUnreachableError,
74
+ MicroVmsResource: () => MicroVmsResource,
53
75
  NonRetryableError: () => NonRetryableError,
54
76
  OrganizationAiGatewayResource: () => OrganizationAiGatewayResource,
55
77
  OrganizationDomainsResource: () => OrganizationDomainsResource,
@@ -77,8 +99,10 @@ __export(src_exports, {
77
99
  createManagedImageUrl: () => createManagedImageUrl,
78
100
  createWorkflowQueue: () => createWorkflowQueue,
79
101
  defaultQueueCodec: () => defaultQueueCodec,
102
+ durationToMs: () => durationToMs,
80
103
  durationToSeconds: () => durationToSeconds,
81
104
  paginate: () => paginate,
105
+ parseSSEEvents: () => parseSSEEvents,
82
106
  parseSSEStream: () => parseSSEStream,
83
107
  queue: () => queue,
84
108
  signQueueDelivery: () => signQueueDelivery,
@@ -554,6 +578,16 @@ var HttpClient = class {
554
578
  }
555
579
  return response;
556
580
  }
581
+ /**
582
+ * Fetch an arbitrary URL without authentication and without throwing on a
583
+ * non-2xx status, using the client's configured `fetch`. Used to probe
584
+ * public URLs such as microVM preview URLs.
585
+ *
586
+ * @internal
587
+ */
588
+ async fetchPublic(url, init) {
589
+ return this.fetchFn(url, init);
590
+ }
557
591
  async request(method, path, options) {
558
592
  const response = await this.fetchWithAuth(method, path, options);
559
593
  return this.handleResponse(response);
@@ -578,8 +612,9 @@ var HttpClient = class {
578
612
  }
579
613
  }
580
614
  const signal = options?.signal;
581
- const response = await this.fetchFn(url, { method, headers, body: requestBody, signal });
582
615
  const bodyIsOneShot = typeof ReadableStream !== "undefined" && requestBody instanceof ReadableStream;
616
+ const init = bodyIsOneShot ? { method, headers, body: requestBody, signal, duplex: "half" } : { method, headers, body: requestBody, signal };
617
+ const response = await this.fetchFn(url, init);
583
618
  if (response.status === 401 && !bodyIsOneShot) {
584
619
  this.tokenManager.invalidate();
585
620
  const retryToken = await this.tokenManager.getToken();
@@ -640,12 +675,265 @@ var HttpClient = class {
640
675
  }
641
676
  };
642
677
 
678
+ // src/resources/base-resource.ts
679
+ var BaseResource = class {
680
+ constructor(httpClient) {
681
+ this.httpClient = httpClient;
682
+ }
683
+ httpClient;
684
+ };
685
+
686
+ // src/microvms/duration.ts
687
+ var PATTERN = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h)$/;
688
+ var UNIT_MS = { ms: 1, s: 1e3, m: 6e4, h: 36e5 };
689
+ var durationToMs = (value) => {
690
+ let ms;
691
+ if (typeof value === "number") {
692
+ ms = value;
693
+ } else {
694
+ const match = PATTERN.exec(value.trim());
695
+ if (!match)
696
+ throw new RangeError(`Invalid duration "${value}". Use milliseconds or a string such as 30s, 10m or 2h.`);
697
+ ms = Number(match[1]) * UNIT_MS[match[2]];
698
+ }
699
+ if (!Number.isFinite(ms) || ms < 0) throw new RangeError(`Invalid duration "${String(value)}"`);
700
+ return Math.ceil(ms);
701
+ };
702
+ var MICROVM_MAX_TIMEOUT_MS = 24 * 36e5;
703
+ var microvmTimeoutMs = (value) => {
704
+ const ms = durationToMs(value);
705
+ if (ms < 1 || ms > MICROVM_MAX_TIMEOUT_MS) {
706
+ throw new RangeError(`A microVM timeout must be between 1 ms and 24 hours (got ${String(value)}).`);
707
+ }
708
+ return ms;
709
+ };
710
+ var sleep = (ms, signal) => new Promise((resolve, reject) => {
711
+ if (signal?.aborted) {
712
+ reject(abortReason(signal));
713
+ return;
714
+ }
715
+ const onAbort = () => {
716
+ clearTimeout(timer);
717
+ reject(abortReason(signal));
718
+ };
719
+ const timer = setTimeout(() => {
720
+ signal?.removeEventListener("abort", onAbort);
721
+ resolve();
722
+ }, ms);
723
+ signal?.addEventListener("abort", onAbort, { once: true });
724
+ });
725
+ var abortReason = (signal) => {
726
+ const reason = signal?.reason;
727
+ if (reason instanceof Error) return reason;
728
+ const error = new Error(typeof reason === "string" ? reason : "The operation was aborted");
729
+ error.name = "AbortError";
730
+ return error;
731
+ };
732
+
733
+ // src/microvms/errors.ts
734
+ var MicroVmNotRunningError = class extends ApiError {
735
+ constructor(message = "The microVM is not running", code = "microvm_not_running", reason) {
736
+ super(message, 409, code, reason);
737
+ this.name = "MicroVmNotRunningError";
738
+ }
739
+ };
740
+ var MicroVmNotFoundError = class extends ApiError {
741
+ constructor(message = "Not found", code, reason) {
742
+ super(message, 404, code, reason);
743
+ this.name = "MicroVmNotFoundError";
744
+ }
745
+ };
746
+ var MicroVmFileNotFoundError = class extends MicroVmNotFoundError {
747
+ /** The path that was not found. */
748
+ path;
749
+ constructor(path, message = `No such file or directory: ${path}`, code) {
750
+ super(message, code);
751
+ this.name = "MicroVmFileNotFoundError";
752
+ this.path = path;
753
+ }
754
+ };
755
+ var MicroVmBusyError = class extends ApiError {
756
+ constructor(message, status, code = "microvm_busy", reason) {
757
+ super(message, status, code, reason);
758
+ this.name = "MicroVmBusyError";
759
+ }
760
+ };
761
+ var MicroVmUnreachableError = class extends ApiError {
762
+ constructor(message, code = "microvm_unreachable", reason) {
763
+ super(message, 502, code, reason);
764
+ this.name = "MicroVmUnreachableError";
765
+ }
766
+ };
767
+ var MicroVmImageNotReadyError = class extends ApiError {
768
+ constructor(message, code = "image_not_ready", reason) {
769
+ super(message, 409, code, reason);
770
+ this.name = "MicroVmImageNotReadyError";
771
+ }
772
+ };
773
+ var MicroVmQuotaExceededError = class extends ApiError {
774
+ constructor(message, status, code, reason) {
775
+ super(message, status, code, reason);
776
+ this.name = "MicroVmQuotaExceededError";
777
+ }
778
+ };
779
+ var CommandFailedError = class _CommandFailedError extends GigadriveError {
780
+ commandId;
781
+ /** Exit code, or `null` when a signal killed the process. */
782
+ exitCode;
783
+ signal;
784
+ stdout;
785
+ stderr;
786
+ constructor(output, label = "Command") {
787
+ const how = output.exitCode !== null ? `exited with code ${output.exitCode}` : `was killed by ${output.signal ?? "a signal"}`;
788
+ const tail = output.stderr.trim().split("\n").slice(-5).join("\n");
789
+ super(tail ? `${label} ${how}:
790
+ ${tail}` : `${label} ${how}`);
791
+ this.name = "CommandFailedError";
792
+ this.commandId = output.commandId;
793
+ this.exitCode = output.exitCode;
794
+ this.signal = output.signal;
795
+ this.stdout = output.stdout;
796
+ this.stderr = output.stderr;
797
+ }
798
+ /** Builds the error from a finished command whose output was not collected. */
799
+ static fromInfo(info) {
800
+ return new _CommandFailedError({
801
+ commandId: info.id,
802
+ exitCode: info.exitCode,
803
+ signal: info.signal,
804
+ stdout: "",
805
+ stderr: "",
806
+ durationMs: 0
807
+ });
808
+ }
809
+ };
810
+ var MicroVmTimeoutError = class extends GigadriveError {
811
+ constructor(message) {
812
+ super(message);
813
+ this.name = "MicroVmTimeoutError";
814
+ }
815
+ };
816
+ var MicroVmPortNotExposedError = class extends GigadriveError {
817
+ port;
818
+ constructor(port, exposed) {
819
+ super(
820
+ exposed.length === 0 ? `Port ${port} is not exposed: this microVM exposes no ports. Pass \`ports: [${port}]\` when creating it.` : `Port ${port} is not exposed. Exposed ports: ${exposed.join(", ")}.`
821
+ );
822
+ this.name = "MicroVmPortNotExposedError";
823
+ this.port = port;
824
+ }
825
+ };
826
+ var MicroVmStartError = class extends GigadriveError {
827
+ microvmId;
828
+ status;
829
+ stopReason;
830
+ constructor(microvmId, status, stopReason) {
831
+ super(`The microVM ${microvmId} is ${status}${stopReason ? ` (${stopReason})` : ""}`);
832
+ this.name = "MicroVmStartError";
833
+ this.microvmId = microvmId;
834
+ this.status = status;
835
+ this.stopReason = stopReason;
836
+ }
837
+ };
838
+ var MicroVmCleanupError = class extends GigadriveError {
839
+ microvmId;
840
+ /** Why deleting the microVM failed. {@link Error.cause} is why creating it failed. */
841
+ cleanupError;
842
+ constructor(microvmId, cause, cleanupError) {
843
+ super(
844
+ `Creating the microVM ${microvmId} failed and deleting it failed too. Delete it with MicroVM.get('${microvmId}') and delete().`
845
+ );
846
+ this.name = "MicroVmCleanupError";
847
+ this.microvmId = microvmId;
848
+ this.cleanupError = cleanupError;
849
+ this.cause = cause;
850
+ }
851
+ };
852
+ var MicroVmImageBuildError = class extends GigadriveError {
853
+ imageId;
854
+ constructor(imageId, message) {
855
+ super(`The image build failed${message ? `: ${message}` : ""}`);
856
+ this.name = "MicroVmImageBuildError";
857
+ this.imageId = imageId;
858
+ }
859
+ };
860
+ var NOT_RUNNING = /microvm[_ ]not[_ ]running|not running/i;
861
+ var toMicroVmError = (error, filePath) => {
862
+ if (!(error instanceof ApiError) || Object.getPrototypeOf(error) !== ApiError.prototype) return error;
863
+ const { message, status, code, reason } = error;
864
+ if (code === "microvm_busy") return new MicroVmBusyError(message, status, code, reason);
865
+ if (code === "microvm_unreachable") return new MicroVmUnreachableError(message, code, reason);
866
+ if (code === "image_not_ready") return new MicroVmImageNotReadyError(message, code, reason);
867
+ if (code === "quota_exceeded" || code === "plan_limit_exceeded") {
868
+ return new MicroVmQuotaExceededError(message, status, code, reason);
869
+ }
870
+ if (status === 409 && (code === "microvm_not_running" || code === void 0 && NOT_RUNNING.test(message))) {
871
+ return new MicroVmNotRunningError(message, code ?? "microvm_not_running", reason);
872
+ }
873
+ if (status === 404) {
874
+ const vmMissing = code === "not_found" || code === "microvm_not_found";
875
+ return filePath !== void 0 && !vmMissing ? new MicroVmFileNotFoundError(filePath, message, code) : new MicroVmNotFoundError(message, code, reason);
876
+ }
877
+ return error;
878
+ };
879
+
880
+ // src/queues/encoding.ts
881
+ var DURATION_PATTERN = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)$/;
882
+ var UNIT_SECONDS = { ms: 1e-3, s: 1, m: 60, h: 3600, d: 86400 };
883
+ var durationToSeconds = (value) => {
884
+ let seconds;
885
+ if (typeof value === "number") {
886
+ seconds = value;
887
+ } else {
888
+ const match = DURATION_PATTERN.exec(value.trim());
889
+ if (!match) throw new Error(`Invalid duration "${value}". Use seconds or a string such as 30s, 10m, 12h or 7d.`);
890
+ seconds = Number(match[1]) * UNIT_SECONDS[match[2]];
891
+ }
892
+ if (!Number.isFinite(seconds) || seconds < 0) throw new Error(`Invalid duration "${String(value)}"`);
893
+ return Math.ceil(seconds);
894
+ };
895
+ var textEncoder = new TextEncoder();
896
+ var textDecoder = new TextDecoder();
897
+ var utf8Encode = (value) => textEncoder.encode(value);
898
+ var utf8Decode = (bytes) => textDecoder.decode(bytes);
899
+ var bytesToBase64 = (bytes) => {
900
+ let binary = "";
901
+ for (let index = 0; index < bytes.length; index += 32768) {
902
+ binary += String.fromCharCode(...bytes.subarray(index, index + 32768));
903
+ }
904
+ return btoa(binary);
905
+ };
906
+ var base64ToBytes = (value) => {
907
+ const binary = atob(value);
908
+ const bytes = new Uint8Array(binary.length);
909
+ for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index);
910
+ return bytes;
911
+ };
912
+ var toHex = (buffer) => Array.from(new Uint8Array(buffer), (byte) => byte.toString(16).padStart(2, "0")).join("");
913
+ var toArrayBuffer = (bytes) => {
914
+ const copy = new ArrayBuffer(bytes.byteLength);
915
+ new Uint8Array(copy).set(bytes);
916
+ return copy;
917
+ };
918
+
643
919
  // src/streaming.ts
644
- var extractData = (rawEvent) => {
645
- const dataLines = rawEvent.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice("data:".length).replace(/^ /, ""));
646
- if (dataLines.length === 0) return null;
647
- const joined = dataLines.join("\n");
648
- return joined.trim().length === 0 ? null : joined;
920
+ var parseBlock = (rawEvent) => {
921
+ let event = "message";
922
+ let id;
923
+ let retry;
924
+ const data = [];
925
+ for (const line of rawEvent.split("\n")) {
926
+ if (line === "" || line.startsWith(":")) continue;
927
+ const colon = line.indexOf(":");
928
+ const field = colon === -1 ? line : line.slice(0, colon);
929
+ const value = colon === -1 ? "" : line.slice(colon + 1).replace(/^ /, "");
930
+ if (field === "data") data.push(value);
931
+ else if (field === "event") event = value || "message";
932
+ else if (field === "id" && !value.includes("\0")) id = value;
933
+ else if (field === "retry" && /^\d+$/.test(value)) retry = Number(value);
934
+ }
935
+ if (data.length === 0) return null;
936
+ return { event, data: data.join("\n"), id, retry };
649
937
  };
650
938
  var parseEvent = (data) => {
651
939
  try {
@@ -655,7 +943,7 @@ var parseEvent = (data) => {
655
943
  throw new Error(`Failed to parse streamed SSE data as JSON: ${message}`);
656
944
  }
657
945
  };
658
- async function* parseSSEStream(response) {
946
+ async function* readEventBlocks(response) {
659
947
  const body = response.body;
660
948
  if (!body) {
661
949
  throw new Error("The response has no readable body to stream.");
@@ -663,44 +951,546 @@ async function* parseSSEStream(response) {
663
951
  const reader = body.getReader();
664
952
  const decoder = new TextDecoder();
665
953
  let buffer = "";
666
- const drain = function* (flush) {
667
- let separator = buffer.indexOf("\n\n");
668
- while (separator !== -1) {
669
- const rawEvent = buffer.slice(0, separator);
670
- buffer = buffer.slice(separator + 2);
671
- const data = extractData(rawEvent);
672
- if (data !== null && data !== "[DONE]") {
673
- yield parseEvent(data);
674
- }
675
- separator = buffer.indexOf("\n\n");
676
- }
677
- if (flush) {
678
- const data = extractData(buffer);
679
- buffer = "";
680
- if (data !== null && data !== "[DONE]") {
681
- yield parseEvent(data);
682
- }
683
- }
684
- };
685
954
  try {
686
955
  for (; ; ) {
687
956
  const { done, value } = await reader.read();
688
957
  if (done) break;
689
958
  buffer = (buffer + decoder.decode(value, { stream: true })).replace(/\r\n/g, "\n");
690
- yield* drain(false);
959
+ let separator = buffer.indexOf("\n\n");
960
+ while (separator !== -1) {
961
+ yield buffer.slice(0, separator);
962
+ buffer = buffer.slice(separator + 2);
963
+ separator = buffer.indexOf("\n\n");
964
+ }
691
965
  }
692
- yield* drain(true);
966
+ buffer += decoder.decode();
967
+ if (buffer.length > 0) yield buffer;
693
968
  } finally {
694
969
  await reader.cancel().catch(() => void 0);
695
970
  }
696
971
  }
972
+ async function* parseSSEEvents(response) {
973
+ for await (const block of readEventBlocks(response)) {
974
+ const event = parseBlock(block);
975
+ if (event) yield event;
976
+ }
977
+ }
978
+ async function* parseSSEStream(response) {
979
+ for await (const event of parseSSEEvents(response)) {
980
+ if (event.data.trim().length === 0 || event.data === "[DONE]") continue;
981
+ yield parseEvent(event.data);
982
+ }
983
+ }
697
984
 
698
- // src/resources/base-resource.ts
699
- var BaseResource = class {
700
- constructor(httpClient) {
701
- this.httpClient = httpClient;
985
+ // src/microvms/logs.ts
986
+ var RECONNECT_DELAYS_MS = [100, 250, 500, 1e3, 2e3];
987
+ var isRetryable = (error) => {
988
+ if (error instanceof ApiError) return error.status === 408 || error.status === 429 || error.status >= 500;
989
+ if (error instanceof Error && error.name === "AbortError") return false;
990
+ return true;
991
+ };
992
+ var parseJson = (data) => {
993
+ try {
994
+ const value = JSON.parse(data);
995
+ return value !== null && typeof value === "object" ? value : null;
996
+ } catch {
997
+ return null;
998
+ }
999
+ };
1000
+ async function* streamCommandLogs(open, options = {}) {
1001
+ const follow = options.follow ?? true;
1002
+ const reconnect = options.reconnect ?? true;
1003
+ const maxAttempts = options.maxReconnectAttempts ?? 5;
1004
+ const next = {
1005
+ stdout: options.stdoutOffset ?? 0,
1006
+ stderr: options.stderrOffset ?? 0
1007
+ };
1008
+ let failures = 0;
1009
+ for (; ; ) {
1010
+ let progressed = false;
1011
+ let serverError;
1012
+ try {
1013
+ const response = await open({
1014
+ follow,
1015
+ stdoutOffset: next.stdout,
1016
+ stderrOffset: next.stderr,
1017
+ signal: options.signal
1018
+ });
1019
+ for await (const event of parseSSEEvents(response)) {
1020
+ if (event.event === "stdout" || event.event === "stderr") {
1021
+ const payload = parseJson(event.data);
1022
+ if (!payload || typeof payload.data !== "string") continue;
1023
+ const stream = event.event;
1024
+ let data = base64ToBytes(payload.data);
1025
+ let offset = typeof payload.offset === "number" ? payload.offset : next[stream];
1026
+ progressed = true;
1027
+ if (offset < next[stream]) {
1028
+ const skip = next[stream] - offset;
1029
+ if (skip >= data.length) continue;
1030
+ data = data.subarray(skip);
1031
+ offset = next[stream];
1032
+ }
1033
+ next[stream] = offset + data.length;
1034
+ if (data.length > 0) yield { type: "output", stream, data, offset };
1035
+ } else if (event.event === "truncated") {
1036
+ progressed = true;
1037
+ yield { type: "truncated" };
1038
+ } else if (event.event === "end") {
1039
+ const payload = parseJson(event.data);
1040
+ yield { type: "end", reason: typeof payload?.reason === "string" ? payload.reason : "snapshot" };
1041
+ return;
1042
+ } else if (event.event === "exit") {
1043
+ const payload = parseJson(event.data) ?? {};
1044
+ yield {
1045
+ type: "exit",
1046
+ exitCode: typeof payload.exitCode === "number" ? payload.exitCode : null,
1047
+ signal: typeof payload.signal === "string" ? payload.signal : null,
1048
+ ...typeof payload.timedOut === "boolean" && { timedOut: payload.timedOut },
1049
+ ...(typeof payload.finishedAt === "string" || payload.finishedAt === null) && {
1050
+ finishedAt: payload.finishedAt
1051
+ }
1052
+ };
1053
+ return;
1054
+ } else if (event.event === "error") {
1055
+ const payload = parseJson(event.data);
1056
+ const message = typeof payload?.message === "string" ? payload.message : typeof payload?.error === "string" ? payload.error : event.data;
1057
+ const failure = new ApiError(message, 502, typeof payload?.code === "string" ? payload.code : void 0);
1058
+ if (payload?.retryable !== true) serverError = failure;
1059
+ throw failure;
1060
+ }
1061
+ }
1062
+ if (!follow || !reconnect) return;
1063
+ const exit = progressed ? null : await options.resolveExit?.();
1064
+ if (exit) {
1065
+ yield { type: "exit", exitCode: exit.exitCode, signal: exit.signal };
1066
+ return;
1067
+ }
1068
+ } catch (error) {
1069
+ if (error === serverError || options.signal?.aborted || !reconnect || !isRetryable(error)) throw error;
1070
+ if (!progressed && failures >= maxAttempts) throw error;
1071
+ }
1072
+ failures = progressed ? 0 : failures + 1;
1073
+ if (failures > maxAttempts) {
1074
+ throw new ApiError("The command log stream kept closing without output", 502, "log_stream_closed");
1075
+ }
1076
+ await sleep(RECONNECT_DELAYS_MS[Math.min(failures, RECONNECT_DELAYS_MS.length - 1)], options.signal);
1077
+ }
1078
+ }
1079
+
1080
+ // src/microvms/resource.ts
1081
+ var vmPath = (microvmId) => `/microvms/${encodeURIComponent(microvmId)}`;
1082
+ var commandPath = (microvmId, commandId) => `${vmPath(microvmId)}/commands/${encodeURIComponent(commandId)}`;
1083
+ var typed = (promise, filePath) => promise.catch((error) => {
1084
+ throw toMicroVmError(error, filePath);
1085
+ });
1086
+ var BUSY_RETRY_DELAYS_MS = [250, 500, 1e3, 2e3, 4e3];
1087
+ var read = async (run, signal, filePath) => {
1088
+ for (let attempt = 0; ; attempt += 1) {
1089
+ try {
1090
+ return await typed(run(), filePath);
1091
+ } catch (error) {
1092
+ if (!(error instanceof MicroVmBusyError) || attempt >= BUSY_RETRY_DELAYS_MS.length || signal?.aborted) {
1093
+ throw error;
1094
+ }
1095
+ await sleep(BUSY_RETRY_DELAYS_MS[attempt], signal);
1096
+ }
1097
+ }
1098
+ };
1099
+ var unwrapList = (body, key = "data") => {
1100
+ if (Array.isArray(body)) return body;
1101
+ if (body && typeof body === "object") {
1102
+ const record = body;
1103
+ const list = record[key] ?? record.data ?? record.items;
1104
+ if (Array.isArray(list)) return list;
1105
+ }
1106
+ return [];
1107
+ };
1108
+ var normalizeCommand = (raw) => {
1109
+ const withId = raw.id === void 0 && raw.commandId !== void 0 ? { ...raw, id: raw.commandId } : raw;
1110
+ return typeof withId.timedOut === "boolean" ? withId : { ...withId, timedOut: false };
1111
+ };
1112
+ var MicroVmCommandsResource = class extends BaseResource {
1113
+ /**
1114
+ * Run a command and wait for it to exit (`POST /microvms/:id/commands`, `wait: true`).
1115
+ *
1116
+ * @example
1117
+ * ```ts
1118
+ * const { exitCode, stdout } = await client.microvms.commands.run(id, { cmd: 'node', args: ['-v'] });
1119
+ * ```
1120
+ */
1121
+ run(microvmId, input, options = {}) {
1122
+ return typed(
1123
+ this.httpClient.post(`${vmPath(microvmId)}/commands`, { ...input, wait: true }, options)
1124
+ );
1125
+ }
1126
+ /**
1127
+ * Start a detached command and return at once (`POST /microvms/:id/commands`, `wait: false`).
1128
+ * Follow its output with {@link logs}.
1129
+ */
1130
+ async start(microvmId, input, options = {}) {
1131
+ const raw = await typed(
1132
+ this.httpClient.post(`${vmPath(microvmId)}/commands`, { ...input, wait: false }, options)
1133
+ );
1134
+ return normalizeCommand(raw);
1135
+ }
1136
+ /** List the microVM's commands, newest first (`GET /microvms/:id/commands`). */
1137
+ async list(microvmId, options = {}) {
1138
+ const body = await read(
1139
+ () => this.httpClient.get(`${vmPath(microvmId)}/commands`, options),
1140
+ options.signal
1141
+ );
1142
+ return unwrapList(body).map(normalizeCommand);
1143
+ }
1144
+ /** Get one command (`GET /microvms/:id/commands/:cmdId`). */
1145
+ async get(microvmId, commandId, options = {}) {
1146
+ return normalizeCommand(
1147
+ await read(() => this.httpClient.get(commandPath(microvmId, commandId), options), options.signal)
1148
+ );
1149
+ }
1150
+ /**
1151
+ * Stream a command's output (`GET /microvms/:id/commands/:cmdId/logs`, SSE).
1152
+ *
1153
+ * Yields `output` events with the raw bytes and a final `exit` event. A
1154
+ * dropped connection is reopened from the last received offsets, so no byte
1155
+ * is lost or repeated; pass `reconnect: false` to disable that.
1156
+ *
1157
+ * @example
1158
+ * ```ts
1159
+ * for await (const event of client.microvms.commands.logs(id, commandId)) {
1160
+ * if (event.type === 'output') process.stdout.write(event.data);
1161
+ * else console.log('exit', event.exitCode);
1162
+ * }
1163
+ * ```
1164
+ */
1165
+ logs(microvmId, commandId, options = {}) {
1166
+ return streamCommandLogs(
1167
+ ({ follow, stdoutOffset, stderrOffset, signal }) => typed(
1168
+ this.httpClient.requestStream("GET", `${commandPath(microvmId, commandId)}/logs`, {
1169
+ query: { follow, stdoutOffset, stderrOffset },
1170
+ headers: { Accept: "text/event-stream" },
1171
+ signal
1172
+ })
1173
+ ),
1174
+ {
1175
+ ...options,
1176
+ resolveExit: options.resolveExit ?? (async () => {
1177
+ const info = await this.get(microvmId, commandId);
1178
+ return info.status === "exited" ? { exitCode: info.exitCode, signal: info.signal } : null;
1179
+ })
1180
+ }
1181
+ );
1182
+ }
1183
+ /**
1184
+ * Wait up to `timeoutMs` (at most 60000) for the command to exit
1185
+ * (`POST /microvms/:id/commands/:cmdId/wait`). The returned command is still
1186
+ * `running` when the wait timed out.
1187
+ */
1188
+ async wait(microvmId, commandId, options = {}) {
1189
+ const { signal, timeoutMs } = options;
1190
+ const raw = await read(
1191
+ () => this.httpClient.post(
1192
+ `${commandPath(microvmId, commandId)}/wait`,
1193
+ timeoutMs === void 0 ? {} : { timeoutMs },
1194
+ { signal }
1195
+ ),
1196
+ signal
1197
+ );
1198
+ return normalizeCommand(raw);
1199
+ }
1200
+ /**
1201
+ * Write to the command's stdin (`POST /microvms/:id/commands/:cmdId/stdin`).
1202
+ * The command must have been started with `stdin: true` or a `pty`.
1203
+ *
1204
+ * @param options.eof - Close stdin after writing.
1205
+ */
1206
+ async stdin(microvmId, commandId, data, options = {}) {
1207
+ await typed(
1208
+ this.httpClient.post(`${commandPath(microvmId, commandId)}/stdin`, data, {
1209
+ headers: { "Content-Type": "application/octet-stream" },
1210
+ query: { eof: options.eof ? true : void 0 },
1211
+ signal: options.signal
1212
+ })
1213
+ );
1214
+ }
1215
+ /** Send a signal to the command's process group (`POST /microvms/:id/commands/:cmdId/signal`). */
1216
+ async signal(microvmId, commandId, signal, options = {}) {
1217
+ await typed(this.httpClient.post(`${commandPath(microvmId, commandId)}/signal`, { signal }, options));
1218
+ }
1219
+ /** Resize a PTY command's terminal (`POST /microvms/:id/commands/:cmdId/resize`). */
1220
+ async resize(microvmId, commandId, size, options = {}) {
1221
+ await typed(
1222
+ this.httpClient.post(
1223
+ `${commandPath(microvmId, commandId)}/resize`,
1224
+ { cols: size.cols, rows: size.rows },
1225
+ options
1226
+ )
1227
+ );
1228
+ }
1229
+ };
1230
+ var modeString = (mode) => typeof mode === "number" ? mode.toString(8).padStart(4, "0") : mode;
1231
+ var MicroVmFilesResource = class extends BaseResource {
1232
+ /** Read a file as bytes (`GET /microvms/:id/files/content`). */
1233
+ async read(microvmId, path, options = {}) {
1234
+ const response = await this.open(microvmId, path, options);
1235
+ return new Uint8Array(await response.arrayBuffer());
1236
+ }
1237
+ /** Read a file as a stream of bytes, without buffering it (`GET /microvms/:id/files/content`). */
1238
+ async readStream(microvmId, path, options = {}) {
1239
+ const response = await this.open(microvmId, path, options);
1240
+ if (!response.body) return new Blob([]).stream();
1241
+ return response.body;
1242
+ }
1243
+ /**
1244
+ * Write a file, replacing it atomically (`PUT /microvms/:id/files/content`).
1245
+ * Missing parent directories are created unless `mkdirs: false`.
1246
+ */
1247
+ write(microvmId, path, content, options = {}) {
1248
+ return typed(
1249
+ this.httpClient.put(`${vmPath(microvmId)}/files/content`, content, {
1250
+ headers: { "Content-Type": "application/octet-stream" },
1251
+ query: { path, mode: modeString(options.mode), mkdirs: options.mkdirs === false ? false : void 0 },
1252
+ signal: options.signal
1253
+ }),
1254
+ path
1255
+ );
1256
+ }
1257
+ /**
1258
+ * List a directory (`GET /microvms/:id/files`). `truncated` is set when the
1259
+ * listing hit its size limit; list a narrower directory then.
1260
+ */
1261
+ async list(microvmId, path, options = {}) {
1262
+ const body = await read(
1263
+ () => this.httpClient.get(`${vmPath(microvmId)}/files`, {
1264
+ query: { path, recursive: options.recursive ? true : void 0, depth: options.depth },
1265
+ signal: options.signal
1266
+ }),
1267
+ options.signal,
1268
+ path
1269
+ );
1270
+ const truncated = body?.truncated === true;
1271
+ return { entries: unwrapList(body, "entries"), truncated };
1272
+ }
1273
+ /** Describe a file or directory without following a final symlink (`GET /microvms/:id/files/stat`). */
1274
+ stat(microvmId, path, options = {}) {
1275
+ return read(
1276
+ () => this.httpClient.get(`${vmPath(microvmId)}/files/stat`, { query: { path }, signal: options.signal }),
1277
+ options.signal,
1278
+ path
1279
+ );
1280
+ }
1281
+ /** Create a directory, with its parents unless `parents: false` (`POST /microvms/:id/files/mkdir`). */
1282
+ mkdir(microvmId, path, options = {}) {
1283
+ const body = options.parents === void 0 ? { path } : { path, parents: options.parents };
1284
+ return typed(this.httpClient.post(`${vmPath(microvmId)}/files/mkdir`, body, options), path);
1285
+ }
1286
+ /** Move or rename a file or directory (`POST /microvms/:id/files/move`). */
1287
+ move(microvmId, from, to, options = {}) {
1288
+ return typed(this.httpClient.post(`${vmPath(microvmId)}/files/move`, { from, to }, options), from);
1289
+ }
1290
+ /** Delete a file, or a directory (non-empty ones need `recursive`) (`DELETE /microvms/:id/files`). */
1291
+ async remove(microvmId, path, options = {}) {
1292
+ await typed(
1293
+ this.httpClient.delete(`${vmPath(microvmId)}/files`, {
1294
+ query: { path, recursive: options.recursive ? true : void 0 },
1295
+ signal: options.signal
1296
+ }),
1297
+ path
1298
+ );
1299
+ }
1300
+ open(microvmId, path, options) {
1301
+ return read(
1302
+ () => this.httpClient.requestStream("GET", `${vmPath(microvmId)}/files/content`, {
1303
+ query: { path, offset: options.offset, length: options.length },
1304
+ signal: options.signal
1305
+ }),
1306
+ options.signal,
1307
+ path
1308
+ );
1309
+ }
1310
+ };
1311
+ var toImageSource = (input) => {
1312
+ if (input.source) return input.source;
1313
+ const from = input.from;
1314
+ if (typeof from === "string") return { type: "registry", ref: from };
1315
+ if (from && "dockerfile" in from) return { type: "dockerfile", dockerfile: from.dockerfile };
1316
+ if (from && "registry" in from) return { type: "registry", ref: from.registry };
1317
+ throw new TypeError("Pass `from` (a registry reference or { dockerfile }) or `source` to create an image.");
1318
+ };
1319
+ var MicroVmImagesResource = class extends BaseResource {
1320
+ defaultApplicationId;
1321
+ constructor(httpClient, defaultApplicationId) {
1322
+ super(httpClient);
1323
+ this.defaultApplicationId = defaultApplicationId || void 0;
1324
+ }
1325
+ /** List the built-in images and the application's custom images (`GET /microvm-images`). */
1326
+ async list(query = {}, options = {}) {
1327
+ const body = await read(
1328
+ () => this.httpClient.get("/microvm-images", {
1329
+ query: { applicationId: query.applicationId ?? this.defaultApplicationId },
1330
+ signal: options.signal
1331
+ }),
1332
+ options.signal
1333
+ );
1334
+ return unwrapList(body);
1335
+ }
1336
+ /**
1337
+ * Start building a custom image (`POST /microvm-images`). The image is
1338
+ * `building` until {@link waitUntilReady} resolves.
1339
+ *
1340
+ * @example
1341
+ * ```ts
1342
+ * const image = await client.microvms.images.create({ name: 'py-ml', from: 'python:3.13' });
1343
+ * await client.microvms.images.waitUntilReady(image.id);
1344
+ * const vm = await MicroVM.create({ image: 'py-ml' });
1345
+ * ```
1346
+ */
1347
+ async create(input, options = {}) {
1348
+ const applicationId = input.applicationId ?? this.defaultApplicationId;
1349
+ const source = toImageSource(input);
1350
+ return await typed(
1351
+ this.httpClient.post(
1352
+ "/microvm-images",
1353
+ { ...applicationId ? { applicationId } : {}, name: input.name, source },
1354
+ options
1355
+ )
1356
+ );
1357
+ }
1358
+ /** Get one image (`GET /microvm-images/:id`). */
1359
+ get(imageId, options = {}) {
1360
+ return read(
1361
+ () => this.httpClient.get(`/microvm-images/${encodeURIComponent(imageId)}`, options),
1362
+ options.signal
1363
+ );
1364
+ }
1365
+ /** Delete a custom image (`DELETE /microvm-images/:id`). */
1366
+ async delete(imageId, options = {}) {
1367
+ await typed(this.httpClient.delete(`/microvm-images/${encodeURIComponent(imageId)}`, options));
1368
+ }
1369
+ /**
1370
+ * Poll an image until its build finished.
1371
+ *
1372
+ * @throws {@link MicroVmImageBuildError} when the build failed.
1373
+ * @throws {@link MicroVmTimeoutError} when it is still building after `timeout`.
1374
+ */
1375
+ async waitUntilReady(imageId, options = {}) {
1376
+ const deadline = Date.now() + durationToMs(options.timeout ?? "15m");
1377
+ const interval = durationToMs(options.interval ?? "2s");
1378
+ let lastStatus;
1379
+ for (; ; ) {
1380
+ if (options.signal?.aborted) throw abortReason(options.signal);
1381
+ const image = await this.get(imageId, { signal: options.signal });
1382
+ if (image.status !== lastStatus) {
1383
+ lastStatus = image.status;
1384
+ options.onStatus?.(image);
1385
+ }
1386
+ if (image.status === "ready") return image;
1387
+ if (image.status === "failed") throw new MicroVmImageBuildError(image.id, image.error);
1388
+ if (Date.now() + interval > deadline) {
1389
+ throw new MicroVmTimeoutError(`The image ${image.name} is still ${image.status}; gave up waiting.`);
1390
+ }
1391
+ await sleep(interval, options.signal);
1392
+ }
1393
+ }
1394
+ };
1395
+ var MicroVmsResource = class extends BaseResource {
1396
+ /** Commands: run, start, logs, wait, stdin, signal, resize. */
1397
+ commands;
1398
+ /** Files: read, write, list, stat, mkdir, move, remove. */
1399
+ files;
1400
+ /** Built-in and custom images. */
1401
+ images;
1402
+ defaultApplicationId;
1403
+ constructor(httpClient, defaultApplicationId) {
1404
+ super(httpClient);
1405
+ this.defaultApplicationId = defaultApplicationId || void 0;
1406
+ this.commands = new MicroVmCommandsResource(httpClient);
1407
+ this.files = new MicroVmFilesResource(httpClient);
1408
+ this.images = new MicroVmImagesResource(httpClient, defaultApplicationId);
1409
+ }
1410
+ /**
1411
+ * Create a microVM (`POST /microvms`). Resolves once it is `running`; with
1412
+ * `wait: false` it resolves at once with a `starting` microVM.
1413
+ *
1414
+ * `applicationId` defaults to the client's `applicationId` or
1415
+ * `GIGADRIVE_APPLICATION_ID`, and may be omitted entirely with application
1416
+ * or deployment credentials.
1417
+ */
1418
+ create(input = {}, options = {}) {
1419
+ const applicationId = input.applicationId ?? this.defaultApplicationId;
1420
+ return typed(
1421
+ this.httpClient.post("/microvms", applicationId ? { ...input, applicationId } : input, {
1422
+ query: { wait: options.wait === false ? false : void 0 },
1423
+ signal: options.signal
1424
+ })
1425
+ );
1426
+ }
1427
+ /** List one page of microVMs, newest first (`GET /microvms`). */
1428
+ async list(query = {}, options = {}) {
1429
+ const body = await read(
1430
+ () => this.httpClient.get("/microvms", {
1431
+ query: {
1432
+ applicationId: query.applicationId ?? this.defaultApplicationId,
1433
+ status: query.status,
1434
+ cursor: query.cursor,
1435
+ limit: query.limit
1436
+ },
1437
+ signal: options.signal
1438
+ }),
1439
+ options.signal
1440
+ );
1441
+ const nextCursor = body.pagination?.nextCursor ?? body.nextCursor ?? void 0;
1442
+ const hasMore = body.pagination?.hasMore ?? nextCursor !== void 0;
1443
+ return {
1444
+ items: body.data ?? body.items ?? [],
1445
+ ...nextCursor && hasMore ? { nextCursor } : {},
1446
+ hasMore: hasMore && nextCursor !== void 0
1447
+ };
1448
+ }
1449
+ /**
1450
+ * Iterate every microVM across all pages.
1451
+ *
1452
+ * @example
1453
+ * ```ts
1454
+ * for await (const vm of client.microvms.listAll({ status: 'running' })) console.log(vm.id);
1455
+ * ```
1456
+ */
1457
+ async *listAll(query = {}, options = {}) {
1458
+ let cursor;
1459
+ do {
1460
+ const page = await this.list({ ...query, cursor }, options);
1461
+ yield* page.items;
1462
+ cursor = page.nextCursor;
1463
+ } while (cursor);
1464
+ }
1465
+ /** Get one microVM (`GET /microvms/:id`). */
1466
+ get(microvmId, options = {}) {
1467
+ return read(() => this.httpClient.get(vmPath(microvmId), options), options.signal);
1468
+ }
1469
+ /**
1470
+ * Set the remaining lifetime to `timeoutMs` from now (`POST /microvms/:id/timeout`),
1471
+ * up to the plan's maximum lifetime.
1472
+ */
1473
+ extendTimeout(microvmId, timeoutMs, options = {}) {
1474
+ return typed(this.httpClient.post(`${vmPath(microvmId)}/timeout`, { timeoutMs }, options));
1475
+ }
1476
+ /** Stop a microVM (`POST /microvms/:id/stop`). Its disk and memory are discarded. */
1477
+ stop(microvmId, options = {}) {
1478
+ return typed(this.httpClient.post(`${vmPath(microvmId)}/stop`, void 0, options));
1479
+ }
1480
+ /** Delete a microVM, stopping it first if needed (`DELETE /microvms/:id`). */
1481
+ async delete(microvmId, options = {}) {
1482
+ await typed(this.httpClient.delete(vmPath(microvmId), options));
1483
+ }
1484
+ /**
1485
+ * Request a public preview URL with the client's `fetch`, without
1486
+ * credentials and without throwing on error statuses. Used by
1487
+ * `MicroVM.waitForPort`.
1488
+ *
1489
+ * @internal
1490
+ */
1491
+ fetchPreview(url, init) {
1492
+ return this.httpClient.fetchPublic(url, init);
702
1493
  }
703
- httpClient;
704
1494
  };
705
1495
 
706
1496
  // src/resources/ai-gateway/index.ts
@@ -985,7 +1775,7 @@ var ApplicationRequestsResource = class extends BaseResource {
985
1775
 
986
1776
  // src/upload/checksum.ts
987
1777
  var EMPTY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
988
- var toHex = (buffer) => {
1778
+ var toHex2 = (buffer) => {
989
1779
  const bytes = new Uint8Array(buffer);
990
1780
  let hex = "";
991
1781
  for (let i = 0; i < bytes.length; i++) {
@@ -993,7 +1783,7 @@ var toHex = (buffer) => {
993
1783
  }
994
1784
  return hex;
995
1785
  };
996
- var subtleDigest = async (algorithm, bytes) => toHex(await crypto.subtle.digest(algorithm, bytes));
1786
+ var subtleDigest = async (algorithm, bytes) => toHex2(await crypto.subtle.digest(algorithm, bytes));
997
1787
  var importNodeCrypto = async () => {
998
1788
  if (typeof process === "undefined" || !process.versions?.node) return null;
999
1789
  try {
@@ -1802,7 +2592,7 @@ var pollSignal = (ms, signal) => {
1802
2592
  }
1803
2593
  };
1804
2594
  };
1805
- var sleep = (ms, signal) => new Promise((resolve, reject) => {
2595
+ var sleep2 = (ms, signal) => new Promise((resolve, reject) => {
1806
2596
  if (signal?.aborted) {
1807
2597
  reject(signal.reason instanceof Error ? signal.reason : new GigadriveError("Aborted"));
1808
2598
  return;
@@ -1926,7 +2716,7 @@ var ApplicationDomainsResource = class extends BaseResource {
1926
2716
  if (last) throw new DomainNotActiveError(last, "timeout");
1927
2717
  throw new GigadriveError("Timed out before the domain could be read");
1928
2718
  }
1929
- await sleep(Math.min(intervalMs, untilDeadline), signal);
2719
+ await sleep2(Math.min(intervalMs, untilDeadline), signal);
1930
2720
  }
1931
2721
  }
1932
2722
  };
@@ -2712,48 +3502,9 @@ var OrganizationsResource = class extends BaseResource {
2712
3502
  * console.log(org.id, org.slug);
2713
3503
  * ```
2714
3504
  */
2715
- async create(input) {
2716
- return this.httpClient.post("/organizations", input);
2717
- }
2718
- };
2719
-
2720
- // src/queues/encoding.ts
2721
- var DURATION_PATTERN = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)$/;
2722
- var UNIT_SECONDS = { ms: 1e-3, s: 1, m: 60, h: 3600, d: 86400 };
2723
- var durationToSeconds = (value) => {
2724
- let seconds;
2725
- if (typeof value === "number") {
2726
- seconds = value;
2727
- } else {
2728
- const match = DURATION_PATTERN.exec(value.trim());
2729
- if (!match) throw new Error(`Invalid duration "${value}". Use seconds or a string such as 30s, 10m, 12h or 7d.`);
2730
- seconds = Number(match[1]) * UNIT_SECONDS[match[2]];
2731
- }
2732
- if (!Number.isFinite(seconds) || seconds < 0) throw new Error(`Invalid duration "${String(value)}"`);
2733
- return Math.ceil(seconds);
2734
- };
2735
- var textEncoder = new TextEncoder();
2736
- var textDecoder = new TextDecoder();
2737
- var utf8Encode = (value) => textEncoder.encode(value);
2738
- var utf8Decode = (bytes) => textDecoder.decode(bytes);
2739
- var bytesToBase64 = (bytes) => {
2740
- let binary = "";
2741
- for (let index = 0; index < bytes.length; index += 32768) {
2742
- binary += String.fromCharCode(...bytes.subarray(index, index + 32768));
3505
+ async create(input) {
3506
+ return this.httpClient.post("/organizations", input);
2743
3507
  }
2744
- return btoa(binary);
2745
- };
2746
- var base64ToBytes = (value) => {
2747
- const binary = atob(value);
2748
- const bytes = new Uint8Array(binary.length);
2749
- for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index);
2750
- return bytes;
2751
- };
2752
- var toHex2 = (buffer) => Array.from(new Uint8Array(buffer), (byte) => byte.toString(16).padStart(2, "0")).join("");
2753
- var toArrayBuffer = (bytes) => {
2754
- const copy = new ArrayBuffer(bytes.byteLength);
2755
- new Uint8Array(copy).set(bytes);
2756
- return copy;
2757
3508
  };
2758
3509
 
2759
3510
  // src/queues/errors.ts
@@ -2785,11 +3536,11 @@ var hmacSha256Hex = async (secret, payload) => {
2785
3536
  false,
2786
3537
  ["sign"]
2787
3538
  );
2788
- return toHex2(await crypto.subtle.sign("HMAC", key, toArrayBuffer(utf8Encode(payload))));
3539
+ return toHex(await crypto.subtle.sign("HMAC", key, toArrayBuffer(utf8Encode(payload))));
2789
3540
  };
2790
3541
  var signedPayload = async (input, timestamp) => {
2791
3542
  const body = typeof input.body === "string" ? utf8Encode(input.body) : input.body;
2792
- const bodyHash = toHex2(await crypto.subtle.digest("SHA-256", toArrayBuffer(body)));
3543
+ const bodyHash = toHex(await crypto.subtle.digest("SHA-256", toArrayBuffer(body)));
2793
3544
  return `${String(timestamp)}.${input.queue}.${input.messageId}.${String(input.attempt)}.${bodyHash}`;
2794
3545
  };
2795
3546
  var safeEqual = (left, right) => {
@@ -2872,7 +3623,7 @@ var ACK_FLUSH_MS = 25;
2872
3623
  var ACK_RETRY_DELAYS_MS = [200, 1e3];
2873
3624
  var EMPTY_POLL_MIN_MS = 1e3;
2874
3625
  var EMPTY_POLL_MAX_MS = 5e3;
2875
- var sleep2 = (ms, signal) => new Promise((resolve) => {
3626
+ var sleep3 = (ms, signal) => new Promise((resolve) => {
2876
3627
  if (signal?.aborted) return resolve();
2877
3628
  const timer = setTimeout(done, ms);
2878
3629
  function done() {
@@ -3067,7 +3818,7 @@ var Queue = class {
3067
3818
  const retryable = !(error instanceof ApiError) || error.status >= 500 || error.status === 429;
3068
3819
  const delay2 = ACK_RETRY_DELAYS_MS[attempt];
3069
3820
  if (!retryable || delay2 === void 0) throw error;
3070
- await sleep2(delay2, void 0);
3821
+ await sleep3(delay2, void 0);
3071
3822
  }
3072
3823
  }
3073
3824
  };
@@ -3107,7 +3858,7 @@ var Queue = class {
3107
3858
  if (messages.length === 0) {
3108
3859
  if (options.stopWhenEmpty) return;
3109
3860
  const remaining = Math.max(waitMs, EMPTY_POLL_MIN_MS) - (Date.now() - started);
3110
- if (remaining > 0) await sleep2(Math.min(remaining, EMPTY_POLL_MAX_MS), options.signal);
3861
+ if (remaining > 0) await sleep3(Math.min(remaining, EMPTY_POLL_MAX_MS), options.signal);
3111
3862
  continue;
3112
3863
  }
3113
3864
  await Promise.all(
@@ -3543,6 +4294,12 @@ var GigadriveClient = class {
3543
4294
  * handle bound to one queue.
3544
4295
  */
3545
4296
  queues;
4297
+ /**
4298
+ * microVMs: isolated Linux machines to run commands in, move files to and
4299
+ * from, and expose ports from. This is the low-level REST surface; the
4300
+ * {@link MicroVM} class (`MicroVM.create({ client })`) is the friendlier API.
4301
+ */
4302
+ microvms;
3546
4303
  constructor(config = {}) {
3547
4304
  const baseUrl = config.baseUrl ?? readEnv("GIGADRIVE_API_BASE_URL") ?? DEFAULT_BASE_URL2;
3548
4305
  const applicationId = config.applicationId ?? readEnv("GIGADRIVE_APPLICATION_ID");
@@ -3559,6 +4316,687 @@ var GigadriveClient = class {
3559
4316
  this.stickySessions = new StickySessionsResource(httpClient);
3560
4317
  this.imageOptimization = new ImageOptimizationResource(httpClient);
3561
4318
  this.queues = new QueuesResource(httpClient, applicationId);
4319
+ this.microvms = new MicroVmsResource(httpClient, applicationId);
4320
+ }
4321
+ };
4322
+
4323
+ // src/microvms/command.ts
4324
+ var MAX_WAIT_MS = 6e4;
4325
+ var toMs = (value) => {
4326
+ if (value === null || value === void 0) return null;
4327
+ const ms = typeof value === "number" ? value : Date.parse(value);
4328
+ return Number.isFinite(ms) ? ms : null;
4329
+ };
4330
+ var commandDurationMs = (info) => {
4331
+ const started = toMs(info.startedAt);
4332
+ const finished = toMs(info.finishedAt);
4333
+ return started !== null && finished !== null ? Math.max(0, finished - started) : null;
4334
+ };
4335
+ var Command = class {
4336
+ /** @internal Use {@link MicroVM.spawn} or `vm.commands.get(id)`. */
4337
+ constructor(resource, microvmId, info) {
4338
+ this.resource = resource;
4339
+ this.microvmId = microvmId;
4340
+ this.current = info;
4341
+ }
4342
+ resource;
4343
+ microvmId;
4344
+ current;
4345
+ pump;
4346
+ pumpError;
4347
+ skippedOutput = false;
4348
+ /** Command id (`cmd_...`). Pass it to `vm.commands.get(id)` to reattach later. */
4349
+ get id() {
4350
+ return this.current.id;
4351
+ }
4352
+ /** The latest known state. Updated by {@link refresh}, {@link wait} and the end of {@link logs}. */
4353
+ get info() {
4354
+ return this.current;
4355
+ }
4356
+ get status() {
4357
+ return this.current.status;
4358
+ }
4359
+ /** Exit code once exited; `null` while running or when a signal killed it. */
4360
+ get exitCode() {
4361
+ return this.current.exitCode;
4362
+ }
4363
+ /** Signal that killed the process, if any. */
4364
+ get signal() {
4365
+ return this.current.signal;
4366
+ }
4367
+ get pid() {
4368
+ return this.current.pid;
4369
+ }
4370
+ /** Whether the command was killed because it ran past its `timeout`. */
4371
+ get timedOut() {
4372
+ return this.current.timedOut;
4373
+ }
4374
+ /**
4375
+ * Whether a {@link logs} read found older output already dropped (each
4376
+ * stream keeps its last 8 MiB), so what it delivered starts mid-stream.
4377
+ */
4378
+ get truncated() {
4379
+ return this.skippedOutput;
4380
+ }
4381
+ /** Fetch the latest state from the API. */
4382
+ async refresh() {
4383
+ this.current = await this.resource.get(this.microvmId, this.id);
4384
+ return this.current;
4385
+ }
4386
+ /**
4387
+ * Stream the command's output, from the beginning of the retained output by
4388
+ * default, until it exits. With `follow: false` it delivers what is
4389
+ * buffered and ends, even while the command keeps running. Reconnects transparently after a dropped
4390
+ * connection. Breaking out of the loop closes the stream; the command keeps
4391
+ * running.
4392
+ *
4393
+ * @example
4394
+ * ```ts
4395
+ * for await (const { stream, text } of command.logs()) {
4396
+ * (stream === 'stdout' ? process.stdout : process.stderr).write(text);
4397
+ * }
4398
+ * ```
4399
+ */
4400
+ async *logs(options = {}) {
4401
+ const decoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };
4402
+ for await (const event of this.resource.logs(this.microvmId, this.id, options)) {
4403
+ if (event.type === "exit") {
4404
+ this.current = {
4405
+ ...this.current,
4406
+ status: "exited",
4407
+ exitCode: event.exitCode,
4408
+ signal: event.signal,
4409
+ ...event.timedOut !== void 0 && { timedOut: event.timedOut },
4410
+ ...event.finishedAt !== void 0 && { finishedAt: event.finishedAt }
4411
+ };
4412
+ continue;
4413
+ }
4414
+ if (event.type === "truncated") {
4415
+ this.skippedOutput = true;
4416
+ continue;
4417
+ }
4418
+ if (event.type === "end") continue;
4419
+ const text = decoders[event.stream].decode(event.data, { stream: true });
4420
+ yield { stream: event.stream, data: event.data, text, offset: event.offset };
4421
+ }
4422
+ }
4423
+ /**
4424
+ * Collect the output from the start until the command exits.
4425
+ *
4426
+ * @example
4427
+ * ```ts
4428
+ * const { stdout } = await (await vm.spawn('ls', ['-la'])).output();
4429
+ * ```
4430
+ */
4431
+ async output(options = {}) {
4432
+ let stdout = "";
4433
+ let stderr = "";
4434
+ for await (const chunk of this.logs(options)) {
4435
+ if (chunk.stream === "stdout") stdout += chunk.text;
4436
+ else stderr += chunk.text;
4437
+ }
4438
+ return { stdout, stderr };
4439
+ }
4440
+ /** The complete stdout as text, once the command exited. */
4441
+ async stdout(options = {}) {
4442
+ return (await this.output(options)).stdout;
4443
+ }
4444
+ /** The complete stderr as text, once the command exited. */
4445
+ async stderr(options = {}) {
4446
+ return (await this.output(options)).stderr;
4447
+ }
4448
+ /**
4449
+ * Wait for the command to exit and return its final state. Output callbacks
4450
+ * given to `spawn` have received all output when this resolves.
4451
+ *
4452
+ * @throws {@link MicroVmTimeoutError} when `timeout` elapses first (the command keeps running).
4453
+ * @throws {@link CommandFailedError} with `throwOnError` when it exits non-zero or is killed.
4454
+ */
4455
+ async wait(options = {}) {
4456
+ const deadline = options.timeout === void 0 ? Infinity : Date.now() + durationToMs(options.timeout);
4457
+ while (this.current.status !== "exited") {
4458
+ const remaining = deadline - Date.now();
4459
+ if (remaining <= 0) {
4460
+ throw new MicroVmTimeoutError(`The command ${this.id} is still running; gave up waiting.`);
4461
+ }
4462
+ this.current = await this.resource.wait(this.microvmId, this.id, {
4463
+ timeoutMs: Math.min(MAX_WAIT_MS, remaining),
4464
+ signal: options.signal
4465
+ });
4466
+ }
4467
+ if (this.pump) {
4468
+ await this.pump;
4469
+ if (this.pumpError) throw this.pumpError;
4470
+ }
4471
+ if (options.throwOnError && this.current.exitCode !== 0) {
4472
+ throw CommandFailedError.fromInfo(this.current);
4473
+ }
4474
+ return this.current;
4475
+ }
4476
+ /**
4477
+ * Send a signal to the command's process group. Default `SIGTERM`; use
4478
+ * `SIGKILL` to stop it unconditionally.
4479
+ */
4480
+ async kill(signal = "SIGTERM") {
4481
+ await this.resource.signal(this.microvmId, this.id, signal);
4482
+ }
4483
+ /**
4484
+ * Write to stdin. The command must have been spawned with `stdin: true` or a `pty`.
4485
+ *
4486
+ * @example
4487
+ * ```ts
4488
+ * const cat = await vm.spawn('cat', [], { stdin: true });
4489
+ * await cat.write('hello\n');
4490
+ * await cat.end();
4491
+ * console.log(await cat.stdout()); // "hello\n"
4492
+ * ```
4493
+ */
4494
+ async write(data) {
4495
+ await this.resource.stdin(this.microvmId, this.id, data);
4496
+ }
4497
+ /** Close stdin, optionally writing a last piece of data first. */
4498
+ async end(data = new Uint8Array(0)) {
4499
+ await this.resource.stdin(this.microvmId, this.id, data, { eof: true });
4500
+ }
4501
+ /** Resize the terminal of a PTY command. */
4502
+ async resize(cols, rows) {
4503
+ await this.resource.resize(this.microvmId, this.id, { cols, rows });
4504
+ }
4505
+ /** JSON form: the command's latest state. */
4506
+ toJSON() {
4507
+ return this.current;
4508
+ }
4509
+ /**
4510
+ * Deliver output to callbacks in the background, for `spawn` with
4511
+ * `onStdout`/`onStderr`. {@link wait} waits for it and rethrows its error.
4512
+ *
4513
+ * @internal
4514
+ */
4515
+ startOutputPump(onStdout, onStderr) {
4516
+ this.pump = (async () => {
4517
+ for await (const chunk of this.logs()) {
4518
+ await (chunk.stream === "stdout" ? onStdout : onStderr)?.(chunk.text, chunk);
4519
+ }
4520
+ })().catch((error) => {
4521
+ this.pumpError = error instanceof Error ? error : new Error(String(error));
4522
+ });
4523
+ }
4524
+ };
4525
+
4526
+ // src/microvms/microvm.ts
4527
+ var defaultClient;
4528
+ var resolveClient = (client) => {
4529
+ if (client instanceof GigadriveClient) return client;
4530
+ if (client) return new GigadriveClient(client);
4531
+ defaultClient ??= new GigadriveClient();
4532
+ return defaultClient;
4533
+ };
4534
+ var CREATE_WAIT_TIMEOUT = "10m";
4535
+ var NOT_LISTENING = /* @__PURE__ */ new Set([502, 503, 504]);
4536
+ var PROBE_TIMEOUT_MS = 5e3;
4537
+ var TERMINAL = ["stopping", "stopped", "failed"];
4538
+ var toStartInput = (cmd, args, options) => {
4539
+ const user = options.sudo ? "root" : options.user;
4540
+ return {
4541
+ cmd,
4542
+ ...args ? { args } : {},
4543
+ ...options.cwd !== void 0 ? { cwd: options.cwd } : {},
4544
+ ...options.env ? { env: options.env } : {},
4545
+ ...user !== void 0 ? { user } : {},
4546
+ ...options.timeout !== void 0 ? { timeoutMs: durationToMs(options.timeout) } : {}
4547
+ };
4548
+ };
4549
+ var failIfRequested = (result, throwOnError) => {
4550
+ if (throwOnError && result.exitCode !== 0) throw new CommandFailedError(result);
4551
+ return result;
4552
+ };
4553
+ var MicroVmFiles = class {
4554
+ /** @internal */
4555
+ constructor(resource, microvmId) {
4556
+ this.resource = resource;
4557
+ this.microvmId = microvmId;
4558
+ }
4559
+ resource;
4560
+ microvmId;
4561
+ /** Read a file as bytes. @throws {@link MicroVmFileNotFoundError} */
4562
+ read(path, options) {
4563
+ return this.resource.read(this.microvmId, path, options);
4564
+ }
4565
+ /** Read a file as UTF-8 text. */
4566
+ async readText(path, options) {
4567
+ return new TextDecoder().decode(await this.read(path, options));
4568
+ }
4569
+ /** Read a file as a stream, for files too large to buffer. */
4570
+ readStream(path, options) {
4571
+ return this.resource.readStream(this.microvmId, path, options);
4572
+ }
4573
+ /**
4574
+ * Write a file atomically, creating parent directories.
4575
+ *
4576
+ * @example
4577
+ * ```ts
4578
+ * await vm.files.write('/app/index.js', 'console.log("hi")');
4579
+ * await vm.files.write('/app/logo.png', await (await fetch(url)).blob());
4580
+ * ```
4581
+ */
4582
+ write(path, content, options) {
4583
+ return this.resource.write(this.microvmId, path, content, options);
4584
+ }
4585
+ /**
4586
+ * Write several files, a few at a time. Resolves in input order once all
4587
+ * succeeded; rejects with the first failure.
4588
+ *
4589
+ * @example
4590
+ * ```ts
4591
+ * await vm.files.writeMany([
4592
+ * { path: 'app/package.json', content: JSON.stringify(pkg) },
4593
+ * { path: 'app/run.sh', content: '#!/bin/sh\nnode .', mode: '0755' },
4594
+ * ]);
4595
+ * ```
4596
+ */
4597
+ async writeMany(files, options = {}) {
4598
+ const results = new Array(files.length);
4599
+ let next = 0;
4600
+ const worker = async () => {
4601
+ while (next < files.length) {
4602
+ const index = next++;
4603
+ const file = files[index];
4604
+ results[index] = await this.write(file.path, file.content, { mode: file.mode, signal: options.signal });
4605
+ }
4606
+ };
4607
+ const workers = Math.max(1, Math.min(options.concurrency ?? 4, files.length));
4608
+ await Promise.all(Array.from({ length: workers }, worker));
4609
+ return results;
4610
+ }
4611
+ /**
4612
+ * List a directory. `recursive` descends into subdirectories (up to `depth`
4613
+ * levels). Very large listings are cut off; `client.microvms.files.list`
4614
+ * reports that as `truncated`.
4615
+ */
4616
+ async list(path = ".", options) {
4617
+ return (await this.resource.list(this.microvmId, path, options)).entries;
4618
+ }
4619
+ /** Describe a file or directory. @throws {@link MicroVmFileNotFoundError} */
4620
+ stat(path) {
4621
+ return this.resource.stat(this.microvmId, path);
4622
+ }
4623
+ /** Whether a file or directory exists. */
4624
+ async exists(path) {
4625
+ try {
4626
+ await this.stat(path);
4627
+ return true;
4628
+ } catch (error) {
4629
+ if (error instanceof MicroVmFileNotFoundError) return false;
4630
+ throw error;
4631
+ }
4632
+ }
4633
+ /** Create a directory and its parents (pass `parents: false` to require the parent). */
4634
+ mkdir(path, options) {
4635
+ return this.resource.mkdir(this.microvmId, path, options);
4636
+ }
4637
+ /** Delete a file, or a directory with `recursive: true`. */
4638
+ remove(path, options) {
4639
+ return this.resource.remove(this.microvmId, path, options);
4640
+ }
4641
+ /** Move or rename a file or directory. */
4642
+ move(from, to) {
4643
+ return this.resource.move(this.microvmId, from, to);
4644
+ }
4645
+ };
4646
+ var MicroVmCommands = class {
4647
+ /** @internal */
4648
+ constructor(resource, microvmId) {
4649
+ this.resource = resource;
4650
+ this.microvmId = microvmId;
4651
+ }
4652
+ resource;
4653
+ microvmId;
4654
+ /**
4655
+ * Reattach to a command, for example one spawned by another process.
4656
+ *
4657
+ * @example
4658
+ * ```ts
4659
+ * const command = await vm.commands.get(savedCommandId);
4660
+ * for await (const chunk of command.logs()) process.stdout.write(chunk.data);
4661
+ * ```
4662
+ */
4663
+ async get(commandId) {
4664
+ return new Command(this.resource, this.microvmId, await this.resource.get(this.microvmId, commandId));
4665
+ }
4666
+ /** All commands the microVM still remembers, newest first. */
4667
+ async list() {
4668
+ const infos = await this.resource.list(this.microvmId);
4669
+ return infos.map((info) => new Command(this.resource, this.microvmId, info));
4670
+ }
4671
+ };
4672
+ var MicroVM = class _MicroVM {
4673
+ /** @internal Use {@link MicroVM.create} or {@link MicroVM.get}. */
4674
+ constructor(resource, info) {
4675
+ this.resource = resource;
4676
+ this.current = info;
4677
+ this.files = new MicroVmFiles(resource.files, info.id);
4678
+ this.commands = new MicroVmCommands(resource.commands, info.id);
4679
+ }
4680
+ resource;
4681
+ /** Files inside the microVM. */
4682
+ files;
4683
+ /** Reattach to commands by id. */
4684
+ commands;
4685
+ current;
4686
+ /**
4687
+ * Create a microVM and wait until it runs.
4688
+ *
4689
+ * @throws {RangeError} When `timeout` is longer than 24 hours.
4690
+ *
4691
+ * The application is taken from `applicationId`, the client's
4692
+ * `applicationId` or `GIGADRIVE_APPLICATION_ID`; inside a Gigadrive
4693
+ * deployment or Function nothing needs configuring.
4694
+ *
4695
+ * @example
4696
+ * ```ts
4697
+ * const vm = await MicroVM.create({
4698
+ * image: 'python3.13',
4699
+ * vcpus: 2,
4700
+ * memoryMb: 4096,
4701
+ * timeout: '15m',
4702
+ * ports: [8000],
4703
+ * env: { PYTHONUNBUFFERED: '1' },
4704
+ * metadata: { user: 'u_123' },
4705
+ * network: 'deny',
4706
+ * });
4707
+ * ```
4708
+ */
4709
+ static async create(options = {}) {
4710
+ const { client, timeout, timeoutMs, network, wait, signal, ...input } = options;
4711
+ const resource = resolveClient(client).microvms;
4712
+ const info = await resource.create(
4713
+ {
4714
+ ...input,
4715
+ ...timeout !== void 0 ? { timeoutMs: microvmTimeoutMs(timeout) } : timeoutMs !== void 0 ? { timeoutMs } : {},
4716
+ ...network !== void 0 ? { network: typeof network === "string" ? { egress: network } : network } : {}
4717
+ },
4718
+ // Never hold one request open for the whole boot: a create can wait
4719
+ // minutes for capacity, longer than proxies in front of the API keep a
4720
+ // response open. Start it, then poll.
4721
+ { wait: false, signal }
4722
+ );
4723
+ const vm = new _MicroVM(resource, info);
4724
+ if (wait === false) return vm;
4725
+ try {
4726
+ return await vm.waitUntilRunning({ timeout: CREATE_WAIT_TIMEOUT, signal });
4727
+ } catch (error) {
4728
+ if (error instanceof MicroVmStartError) throw error;
4729
+ try {
4730
+ await vm.delete();
4731
+ } catch (cleanupError) {
4732
+ throw new MicroVmCleanupError(vm.id, error, cleanupError);
4733
+ }
4734
+ throw error;
4735
+ }
4736
+ }
4737
+ /**
4738
+ * Connect to an existing microVM by id.
4739
+ *
4740
+ * @throws {@link MicroVmNotFoundError} when it does not exist.
4741
+ */
4742
+ static async get(microvmId, options = {}) {
4743
+ const resource = resolveClient(options.client).microvms;
4744
+ return new _MicroVM(resource, await resource.get(microvmId));
4745
+ }
4746
+ /** Alias of {@link MicroVM.get}. */
4747
+ static connect(microvmId, options = {}) {
4748
+ return _MicroVM.get(microvmId, options);
4749
+ }
4750
+ /**
4751
+ * Iterate microVMs, newest first, across all pages.
4752
+ *
4753
+ * @example
4754
+ * ```ts
4755
+ * for await (const vm of MicroVM.list({ status: 'running' })) {
4756
+ * console.log(vm.id, vm.metadata);
4757
+ * }
4758
+ * ```
4759
+ */
4760
+ static async *list(options = {}) {
4761
+ const { client, ...query } = options;
4762
+ const resource = resolveClient(client).microvms;
4763
+ for await (const info of resource.listAll(query)) yield new _MicroVM(resource, info);
4764
+ }
4765
+ /** Stable UUID. */
4766
+ get id() {
4767
+ return this.current.id;
4768
+ }
4769
+ /** Label used in preview URLs. */
4770
+ get slug() {
4771
+ return this.current.slug;
4772
+ }
4773
+ get applicationId() {
4774
+ return this.current.applicationId;
4775
+ }
4776
+ /** Status as of the last API response; call {@link refresh} for the current one. */
4777
+ get status() {
4778
+ return this.current.status;
4779
+ }
4780
+ /** Exposed ports. */
4781
+ get ports() {
4782
+ return this.current.ports.map((entry) => entry.port);
4783
+ }
4784
+ get metadata() {
4785
+ return this.current.metadata;
4786
+ }
4787
+ /** When the microVM stops automatically. */
4788
+ get expiresAt() {
4789
+ return this.current.expiresAt ? new Date(this.current.expiresAt) : null;
4790
+ }
4791
+ /** The full API object as of the last response. */
4792
+ get info() {
4793
+ return this.current;
4794
+ }
4795
+ /**
4796
+ * The public preview URL of an exposed port.
4797
+ *
4798
+ * @throws {@link MicroVmPortNotExposedError} when the port was not in `ports` at create.
4799
+ *
4800
+ * @example
4801
+ * ```ts
4802
+ * vm.url(3000); // "https://3000-k3j9x2m1q8zt.vm.gigadrive.app"
4803
+ * ```
4804
+ */
4805
+ url(port) {
4806
+ const entry = this.current.ports.find((candidate) => candidate.port === port);
4807
+ if (!entry) throw new MicroVmPortNotExposedError(port, this.ports);
4808
+ return entry.url;
4809
+ }
4810
+ /** Fetch the latest state. */
4811
+ async refresh(options = {}) {
4812
+ this.current = await this.resource.get(this.id, { signal: options.signal });
4813
+ return this.current;
4814
+ }
4815
+ async run(cmd, argsOrOptions, maybeOptions) {
4816
+ const args = Array.isArray(argsOrOptions) ? argsOrOptions : void 0;
4817
+ const options = (Array.isArray(argsOrOptions) ? maybeOptions : argsOrOptions) ?? {};
4818
+ const input = toStartInput(cmd, args, options);
4819
+ const streaming = options.onStdout !== void 0 || options.onStderr !== void 0 || options.stdin !== void 0 || options.signal !== void 0;
4820
+ if (!streaming) {
4821
+ const raw = await this.resource.commands.run(this.id, input);
4822
+ return failIfRequested(
4823
+ {
4824
+ commandId: raw.id,
4825
+ exitCode: raw.exitCode,
4826
+ signal: raw.signal ?? null,
4827
+ timedOut: raw.timedOut === true,
4828
+ stdout: raw.stdout,
4829
+ stderr: raw.stderr,
4830
+ truncated: raw.truncated,
4831
+ durationMs: raw.durationMs
4832
+ },
4833
+ options.throwOnError
4834
+ );
4835
+ }
4836
+ const { signal } = options;
4837
+ if (signal?.aborted) throw abortReason(signal);
4838
+ const startedAt = Date.now();
4839
+ const info = await this.resource.commands.start(
4840
+ this.id,
4841
+ { ...input, ...options.stdin !== void 0 ? { stdin: true } : {} },
4842
+ { signal }
4843
+ );
4844
+ const command = new Command(this.resource.commands, this.id, info);
4845
+ const kill = () => void command.kill("SIGKILL").catch(() => void 0);
4846
+ signal?.addEventListener("abort", kill, { once: true });
4847
+ try {
4848
+ if (options.stdin !== void 0) await command.end(options.stdin);
4849
+ const output = { stdout: "", stderr: "" };
4850
+ const expected = { stdout: 0, stderr: 0 };
4851
+ let truncated = false;
4852
+ for await (const chunk of command.logs({ signal })) {
4853
+ if (chunk.offset > expected[chunk.stream]) truncated = true;
4854
+ expected[chunk.stream] = chunk.offset + chunk.data.length;
4855
+ output[chunk.stream] += chunk.text;
4856
+ await (chunk.stream === "stdout" ? options.onStdout : options.onStderr)?.(chunk.text, chunk);
4857
+ }
4858
+ const final = command.status === "exited" ? command.info : await command.wait({ signal });
4859
+ return failIfRequested(
4860
+ {
4861
+ commandId: command.id,
4862
+ exitCode: final.exitCode,
4863
+ signal: final.signal,
4864
+ timedOut: final.timedOut === true,
4865
+ ...output,
4866
+ truncated: truncated || command.truncated,
4867
+ durationMs: commandDurationMs(final) ?? Date.now() - startedAt
4868
+ },
4869
+ options.throwOnError
4870
+ );
4871
+ } finally {
4872
+ signal?.removeEventListener("abort", kill);
4873
+ }
4874
+ }
4875
+ async spawn(cmd, argsOrOptions, maybeOptions) {
4876
+ const args = Array.isArray(argsOrOptions) ? argsOrOptions : void 0;
4877
+ const options = (Array.isArray(argsOrOptions) ? maybeOptions : argsOrOptions) ?? {};
4878
+ const pty = options.pty === true ? { cols: 80, rows: 24 } : options.pty || void 0;
4879
+ const info = await this.resource.commands.start(this.id, {
4880
+ ...toStartInput(cmd, args, options),
4881
+ ...options.stdin ? { stdin: true } : {},
4882
+ ...pty ? { pty } : {}
4883
+ });
4884
+ const command = new Command(this.resource.commands, this.id, info);
4885
+ if (options.onStdout || options.onStderr) command.startOutputPump(options.onStdout, options.onStderr);
4886
+ return command;
4887
+ }
4888
+ /**
4889
+ * Set the remaining lifetime to `duration` from now (up to the plan's
4890
+ * maximum lifetime).
4891
+ *
4892
+ * @example
4893
+ * ```ts
4894
+ * await vm.extendTimeout('30m');
4895
+ * ```
4896
+ */
4897
+ async extendTimeout(duration) {
4898
+ this.current = await this.resource.extendTimeout(this.id, microvmTimeoutMs(duration));
4899
+ return this.current;
4900
+ }
4901
+ /** Stop the microVM. Its disk, memory and running commands are discarded. */
4902
+ async stop() {
4903
+ this.current = await this.resource.stop(this.id);
4904
+ return this.current;
4905
+ }
4906
+ /** Delete the microVM, stopping it first if needed. */
4907
+ async delete() {
4908
+ await this.resource.delete(this.id);
4909
+ }
4910
+ /**
4911
+ * Poll until the microVM is running, for microVMs created with `wait: false`.
4912
+ *
4913
+ * @throws {@link MicroVmStartError} when it failed or stopped instead.
4914
+ * @throws {@link MicroVmTimeoutError} when it is still starting after `timeout`.
4915
+ */
4916
+ async waitUntilRunning(options = {}) {
4917
+ const deadline = Date.now() + durationToMs(options.timeout ?? "2m");
4918
+ const interval = durationToMs(options.interval ?? "500ms");
4919
+ while (this.current.status !== "running") {
4920
+ if (TERMINAL.includes(this.current.status)) {
4921
+ throw new MicroVmStartError(this.id, this.current.status, this.current.stopReason);
4922
+ }
4923
+ if (Date.now() + interval > deadline) {
4924
+ throw new MicroVmTimeoutError(`The microVM ${this.id} is still ${this.current.status}; gave up waiting.`);
4925
+ }
4926
+ await sleep(interval, options.signal);
4927
+ await this.refresh({ signal: options.signal });
4928
+ if (options.signal?.aborted) throw abortReason(options.signal);
4929
+ }
4930
+ return this;
4931
+ }
4932
+ /**
4933
+ * Poll an exposed port's preview URL until a server answers (any status
4934
+ * other than 502, 503 or 504, which the gateway returns while nothing
4935
+ * listens).
4936
+ *
4937
+ * @returns The preview URL.
4938
+ * @throws {@link MicroVmPortNotExposedError} when the port is not exposed.
4939
+ * @throws {@link MicroVmNotRunningError} when the microVM has stopped.
4940
+ * @throws {@link MicroVmTimeoutError} when nothing answers within `timeout`.
4941
+ *
4942
+ * @example
4943
+ * ```ts
4944
+ * await vm.spawn('python3', ['-m', 'http.server', '8000']);
4945
+ * const url = await vm.waitForPort(8000);
4946
+ * ```
4947
+ */
4948
+ async waitForPort(port, options = {}) {
4949
+ const base = this.url(port);
4950
+ const path = options.path ?? "/";
4951
+ const target = `${base.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
4952
+ const deadline = Date.now() + durationToMs(options.timeout ?? "60s");
4953
+ const interval = durationToMs(options.interval ?? "500ms");
4954
+ for (; ; ) {
4955
+ if (options.signal?.aborted) throw abortReason(options.signal);
4956
+ const controller = new AbortController();
4957
+ const timer = setTimeout(
4958
+ () => controller.abort(),
4959
+ Math.min(PROBE_TIMEOUT_MS, Math.max(1, deadline - Date.now()))
4960
+ );
4961
+ const forward = () => controller.abort();
4962
+ options.signal?.addEventListener("abort", forward, { once: true });
4963
+ let stopped = false;
4964
+ try {
4965
+ const response = await this.resource.fetchPreview(target, {
4966
+ method: "GET",
4967
+ redirect: "manual",
4968
+ signal: controller.signal
4969
+ });
4970
+ await response.body?.cancel().catch(() => void 0);
4971
+ stopped = response.status === 410 && response.headers.get("x-gigadrive-error") === "EDGE_MICROVM_STOPPED";
4972
+ if (!stopped && !NOT_LISTENING.has(response.status)) return base;
4973
+ } catch {
4974
+ if (options.signal?.aborted) throw abortReason(options.signal);
4975
+ } finally {
4976
+ clearTimeout(timer);
4977
+ options.signal?.removeEventListener("abort", forward);
4978
+ }
4979
+ if (stopped) {
4980
+ throw new MicroVmNotRunningError(`The microVM ${this.id} has stopped; nothing will answer on port ${port}.`);
4981
+ }
4982
+ if (Date.now() + interval > deadline) {
4983
+ throw new MicroVmTimeoutError(`Nothing answered on port ${port} of the microVM ${this.id}.`);
4984
+ }
4985
+ await sleep(interval, options.signal);
4986
+ }
4987
+ }
4988
+ /** Stops the microVM at the end of an `await using` block. Already stopped microVMs are fine. */
4989
+ async [Symbol.asyncDispose]() {
4990
+ try {
4991
+ await this.stop();
4992
+ } catch (error) {
4993
+ if (error instanceof MicroVmNotRunningError || error instanceof MicroVmNotFoundError) return;
4994
+ throw error;
4995
+ }
4996
+ }
4997
+ /** JSON form: the API object. */
4998
+ toJSON() {
4999
+ return this.current;
3562
5000
  }
3563
5001
  };
3564
5002
 
@@ -3575,10 +5013,10 @@ async function* paginate(fetchPage) {
3575
5013
  }
3576
5014
 
3577
5015
  // src/queues/default-client.ts
3578
- var defaultClient;
5016
+ var defaultClient2;
3579
5017
  var defaultQueuesResource = () => {
3580
- defaultClient ??= new GigadriveClient();
3581
- return defaultClient.queues;
5018
+ defaultClient2 ??= new GigadriveClient();
5019
+ return defaultClient2.queues;
3582
5020
  };
3583
5021
  function queue(name, options = {}) {
3584
5022
  const { client, ...queueOptions } = options;
@@ -3602,7 +5040,7 @@ var decodeWorkflowMessage = (text) => JSON.parse(text, (_key, value) => {
3602
5040
  return value;
3603
5041
  });
3604
5042
  var readEnv2 = (name) => globalThis.process?.env?.[name] || void 0;
3605
- var deduplicationKeyFor = async (idempotencyKey) => idempotencyKey.length <= 256 ? idempotencyKey : `wkf:sha256:${toHex2(await crypto.subtle.digest("SHA-256", toArrayBuffer(utf8Encode(idempotencyKey))))}`;
5043
+ var deduplicationKeyFor = async (idempotencyKey) => idempotencyKey.length <= 256 ? idempotencyKey : `wkf:sha256:${toHex(await crypto.subtle.digest("SHA-256", toArrayBuffer(utf8Encode(idempotencyKey))))}`;
3606
5044
  function createWorkflowQueue(options = {}) {
3607
5045
  const resource = () => options.client?.queues ?? defaultQueuesResource();
3608
5046
  const scope = { applicationId: options.applicationId, environment: options.environment };
@@ -3744,12 +5182,34 @@ function createWorkflowQueue(options = {}) {
3744
5182
  ApplicationStorageResource,
3745
5183
  ApplicationsResource,
3746
5184
  AuthenticationError,
5185
+ Command,
5186
+ CommandFailedError,
3747
5187
  ConfigurationError,
3748
5188
  DeploymentsResource,
3749
5189
  DomainNotActiveError,
3750
5190
  GigadriveClient,
3751
5191
  GigadriveError,
3752
5192
  ImageOptimizationResource,
5193
+ MICROVM_MAX_TIMEOUT_MS,
5194
+ MicroVM,
5195
+ MicroVmBusyError,
5196
+ MicroVmCleanupError,
5197
+ MicroVmCommands,
5198
+ MicroVmCommandsResource,
5199
+ MicroVmFileNotFoundError,
5200
+ MicroVmFiles,
5201
+ MicroVmFilesResource,
5202
+ MicroVmImageBuildError,
5203
+ MicroVmImageNotReadyError,
5204
+ MicroVmImagesResource,
5205
+ MicroVmNotFoundError,
5206
+ MicroVmNotRunningError,
5207
+ MicroVmPortNotExposedError,
5208
+ MicroVmQuotaExceededError,
5209
+ MicroVmStartError,
5210
+ MicroVmTimeoutError,
5211
+ MicroVmUnreachableError,
5212
+ MicroVmsResource,
3753
5213
  NonRetryableError,
3754
5214
  OrganizationAiGatewayResource,
3755
5215
  OrganizationDomainsResource,
@@ -3777,8 +5237,10 @@ function createWorkflowQueue(options = {}) {
3777
5237
  createManagedImageUrl,
3778
5238
  createWorkflowQueue,
3779
5239
  defaultQueueCodec,
5240
+ durationToMs,
3780
5241
  durationToSeconds,
3781
5242
  paginate,
5243
+ parseSSEEvents,
3782
5244
  parseSSEStream,
3783
5245
  queue,
3784
5246
  signQueueDelivery,