freestyle-sandboxes 0.1.41 → 0.1.43

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/index.cjs CHANGED
@@ -3763,6 +3763,9 @@ class ApiClient {
3763
3763
  };
3764
3764
  return this.fetchFn(url, finalOptions);
3765
3765
  }
3766
+ resolveUrl(path, params, query) {
3767
+ return this.buildUrl(path, params, query);
3768
+ }
3766
3769
  getRaw(path, options) {
3767
3770
  const url = this.buildUrl(path, options?.params, options?.query);
3768
3771
  return this.requestRaw("GET", url, void 0, options?.headers);
@@ -3777,6 +3780,11 @@ class ApiClient {
3777
3780
  const url = this.buildUrl(path, options?.params, options?.query);
3778
3781
  return this.request("POST", url, options?.body, options?.headers);
3779
3782
  }
3783
+ postRaw(path, ...args) {
3784
+ const options = args[0];
3785
+ const url = this.buildUrl(path, options?.params, options?.query);
3786
+ return this.requestRaw("POST", url, options?.body, options?.headers);
3787
+ }
3780
3788
  put(path, ...args) {
3781
3789
  const options = args[0];
3782
3790
  const url = this.buildUrl(path, options?.params, options?.query);
@@ -5648,6 +5656,7 @@ function composeCreateVmOptions(arr) {
5648
5656
  result.activityThresholdBytes = options.activityThresholdBytes;
5649
5657
  if (options.discriminator !== void 0)
5650
5658
  result.discriminator = options.discriminator;
5659
+ if (options.skipCache !== void 0) result.skipCache = options.skipCache;
5651
5660
  if (options.persistence !== void 0)
5652
5661
  result.persistence = options.persistence;
5653
5662
  if (options.ports !== void 0) {
@@ -5795,6 +5804,7 @@ function composeCreateVmOptions(arr) {
5795
5804
  aptDeps: newTemplate.aptDeps ?? baseTemplate.aptDeps,
5796
5805
  vcpuCount: newTemplate.vcpuCount !== void 0 ? newTemplate.vcpuCount : baseTemplate.vcpuCount,
5797
5806
  discriminator: newTemplate.discriminator ?? baseTemplate.discriminator,
5807
+ skipCache: newTemplate.skipCache ?? baseTemplate.skipCache,
5798
5808
  workdir: newTemplate.workdir !== void 0 ? newTemplate.workdir : baseTemplate.workdir,
5799
5809
  idleTimeoutSeconds: newTemplate.idleTimeoutSeconds !== void 0 ? newTemplate.idleTimeoutSeconds : baseTemplate.idleTimeoutSeconds,
5800
5810
  waitForReadySignal: newTemplate.waitForReadySignal !== void 0 ? newTemplate.waitForReadySignal : baseTemplate.waitForReadySignal,
@@ -6011,6 +6021,94 @@ function composeVmSpecs(specs) {
6011
6021
  const DEFAULT_CONFIGURE_BASE_IMAGE = "FROM debian:trixie-slim";
6012
6022
  const RUN_COMMANDS_SYSTEMD_SERVICE_PREFIX = "freestyle-run-command";
6013
6023
  const WAIT_FOR_SYSTEMD_SERVICE_PREFIX = "freestyle-wait-for";
6024
+ const BACKGROUND_AFTER_SECS_HEADER = "x-freestyle-background-after-secs";
6025
+ const BACKGROUND_REQUEST_ID_HEADER = "x-freestyle-background-request-id";
6026
+ const DEFAULT_BACKGROUND_AFTER_SECS = 5;
6027
+ const DEFAULT_BACKGROUND_POLL_INTERVAL_MS = 2e3;
6028
+ function delay(ms) {
6029
+ const timerApi = globalThis;
6030
+ return new Promise((resolve) => timerApi.setTimeout(resolve, ms));
6031
+ }
6032
+ function extractBackgroundRequestId(response, body) {
6033
+ return response.headers.get(BACKGROUND_REQUEST_ID_HEADER) ?? body?.requestId ?? body?.request_id;
6034
+ }
6035
+ async function parseJsonResponse(response) {
6036
+ return await response.json();
6037
+ }
6038
+ async function readResponseError(response, fallbackMessage) {
6039
+ const responseText = await response.text();
6040
+ if (!responseText) {
6041
+ return fallbackMessage;
6042
+ }
6043
+ try {
6044
+ const errorBody = JSON.parse(responseText);
6045
+ return errorBody.message ?? errorBody.error ?? responseText;
6046
+ } catch {
6047
+ return responseText;
6048
+ }
6049
+ }
6050
+ async function emitBackgroundLogs(apiClient, requestId, logger, seenLogs) {
6051
+ const response = await apiClient.fetch(
6052
+ apiClient.resolveUrl("/observability/v1/logs", void 0, { requestId }),
6053
+ { method: "GET" }
6054
+ );
6055
+ if (!response.ok) {
6056
+ return;
6057
+ }
6058
+ const payload = await response.json();
6059
+ for (const entry of payload.logs ?? []) {
6060
+ const key = `${entry.timestamp} ${entry.message}`;
6061
+ if (seenLogs.has(key)) {
6062
+ continue;
6063
+ }
6064
+ seenLogs.add(key);
6065
+ logger(`[${entry.timestamp}] ${entry.message}`);
6066
+ }
6067
+ }
6068
+ async function waitForBackgroundRequest(apiClient, requestId, logger) {
6069
+ const seenLogs = /* @__PURE__ */ new Set();
6070
+ const resultUrl = apiClient.resolveUrl(
6071
+ `/auth/v1/background-requests/${encodeURIComponent(requestId)}`
6072
+ );
6073
+ while (true) {
6074
+ await emitBackgroundLogs(apiClient, requestId, logger, seenLogs);
6075
+ const response = await apiClient.fetch(resultUrl, { method: "GET" });
6076
+ if (response.status === 202) {
6077
+ await delay(DEFAULT_BACKGROUND_POLL_INTERVAL_MS);
6078
+ continue;
6079
+ }
6080
+ if (!response.ok) {
6081
+ const message = await readResponseError(
6082
+ response,
6083
+ `Background request ${requestId} failed`
6084
+ );
6085
+ throw new Error(message);
6086
+ }
6087
+ return parseJsonResponse(response);
6088
+ }
6089
+ }
6090
+ async function postWithBackgroundLogger(apiClient, path, options, logger) {
6091
+ const response = await apiClient.postRaw(path, {
6092
+ ...options,
6093
+ headers: logger ? {
6094
+ ...options.headers ?? {},
6095
+ [BACKGROUND_AFTER_SECS_HEADER]: String(DEFAULT_BACKGROUND_AFTER_SECS)
6096
+ } : options.headers
6097
+ });
6098
+ if (response.status !== 202 || !logger) {
6099
+ return parseJsonResponse(response);
6100
+ }
6101
+ const accepted = await parseJsonResponse(
6102
+ response
6103
+ );
6104
+ const requestId = extractBackgroundRequestId(response, accepted);
6105
+ if (!requestId) {
6106
+ throw new Error(
6107
+ `Background request response for ${path} did not include a request ID`
6108
+ );
6109
+ }
6110
+ return waitForBackgroundRequest(apiClient, requestId, logger);
6111
+ }
6014
6112
  function escapeRegExp(value) {
6015
6113
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6016
6114
  }
@@ -6392,9 +6490,9 @@ class Vm {
6392
6490
  ref({ vmId }) {
6393
6491
  return new Vm({ vmId, freestyle: this._freestyle });
6394
6492
  }
6395
- async delete({ vmId }) {
6493
+ async delete() {
6396
6494
  return this.apiClient.delete("/v1/vms/{vm_id}", {
6397
- params: { vm_id: vmId }
6495
+ params: { vm_id: this.vmId }
6398
6496
  });
6399
6497
  }
6400
6498
  }
@@ -6545,7 +6643,10 @@ class VmSpec {
6545
6643
  }
6546
6644
  ensureSnapshot() {
6547
6645
  if (!isVmSpecLike$1(this.raw.snapshot)) {
6548
- this.raw.snapshot = this.raw.discriminator !== void 0 ? new VmSpec({ discriminator: this.raw.discriminator }) : new VmSpec();
6646
+ this.raw.snapshot = new VmSpec({
6647
+ discriminator: this.raw.discriminator,
6648
+ skipCache: this.raw.skipCache
6649
+ });
6549
6650
  }
6550
6651
  return this.raw.snapshot;
6551
6652
  }
@@ -6574,6 +6675,9 @@ class VmSpec {
6574
6675
  discriminator(value) {
6575
6676
  return this.mergeRaw({ discriminator: value });
6576
6677
  }
6678
+ skipCache() {
6679
+ return this.mergeRaw({ skipCache: true });
6680
+ }
6577
6681
  workdir(path) {
6578
6682
  return this.mergeRaw({ workdir: path });
6579
6683
  }
@@ -6800,7 +6904,10 @@ async function processOuterSpecBuilders(spec) {
6800
6904
  snapshotSpec = spec.raw.snapshot;
6801
6905
  } else {
6802
6906
  const inheritedDiscriminator = spec.getBuilderDiscriminator(key) ?? spec.raw.discriminator;
6803
- snapshotSpec = inheritedDiscriminator !== void 0 ? new VmSpec({ discriminator: inheritedDiscriminator }) : new VmSpec({});
6907
+ snapshotSpec = new VmSpec({
6908
+ discriminator: inheritedDiscriminator,
6909
+ skipCache: spec.raw.skipCache
6910
+ });
6804
6911
  spec.raw.snapshot = snapshotSpec;
6805
6912
  }
6806
6913
  spec.raw.snapshot = await builder.configureSnapshotSpec(snapshotSpec);
@@ -6827,7 +6934,10 @@ async function processSpecTree(spec) {
6827
6934
  snapshotSpec = spec.raw.snapshot;
6828
6935
  } else {
6829
6936
  const inheritedDiscriminator = spec.getBuilderDiscriminator(key) ?? spec.raw.discriminator;
6830
- snapshotSpec = inheritedDiscriminator !== void 0 ? new VmSpec({ discriminator: inheritedDiscriminator }) : new VmSpec({});
6937
+ snapshotSpec = new VmSpec({
6938
+ discriminator: inheritedDiscriminator,
6939
+ skipCache: spec.raw.skipCache
6940
+ });
6831
6941
  spec.raw.snapshot = snapshotSpec;
6832
6942
  }
6833
6943
  spec.raw.snapshot = await builder.configureSnapshotSpec(snapshotSpec);
@@ -6872,9 +6982,15 @@ class VmsNamespace {
6872
6982
  }
6873
6983
  snapshots;
6874
6984
  async create(options = {}) {
6985
+ let logger;
6875
6986
  if (isVmSpecLike$1(options)) {
6876
6987
  options = { spec: cloneVmSpecTree(options) };
6877
6988
  } else {
6989
+ logger = options.logger;
6990
+ if ("logger" in options) {
6991
+ const { logger: _logger, ...rest } = options;
6992
+ options = rest;
6993
+ }
6878
6994
  if (isVmSpecLike$1(options.spec)) {
6879
6995
  options.spec = cloneVmSpecTree(options.spec);
6880
6996
  }
@@ -6954,7 +7070,8 @@ class VmsNamespace {
6954
7070
  if (isVmTemplateLike$1(config.template)) {
6955
7071
  config.template = await ensureNestedTemplates(
6956
7072
  config.template,
6957
- this.snapshots
7073
+ this.snapshots,
7074
+ logger
6958
7075
  );
6959
7076
  }
6960
7077
  const keys = Object.keys(builders);
@@ -6995,16 +7112,21 @@ class VmsNamespace {
6995
7112
  template: _template,
6996
7113
  ...requestConfig
6997
7114
  } = config;
6998
- const response = await this.freestyle._apiClient.post("/v1/vms", {
6999
- body: {
7000
- ...requestConfig,
7001
- template: normalizedRequestTemplate,
7002
- // Cast systemd since we've processed SystemdServiceInput[] to RawSystemdService[]
7003
- systemd: config.systemd,
7004
- // Normalize git options - default config to {}
7005
- git: normalizeGitOptions(config.git)
7006
- }
7007
- }).catch((e) => {
7115
+ const response = await postWithBackgroundLogger(
7116
+ this.freestyle._apiClient,
7117
+ "/v1/vms",
7118
+ {
7119
+ body: {
7120
+ ...requestConfig,
7121
+ template: normalizedRequestTemplate,
7122
+ // Cast systemd since we've processed SystemdServiceInput[] to RawSystemdService[]
7123
+ systemd: config.systemd,
7124
+ // Normalize git options - default config to {}
7125
+ git: normalizeGitOptions(config.git)
7126
+ }
7127
+ },
7128
+ logger
7129
+ ).catch((e) => {
7008
7130
  enhanceError(e);
7009
7131
  throw e;
7010
7132
  });
@@ -7160,11 +7282,12 @@ class VmSnapshotsNamespace {
7160
7282
  this.apiClient = apiClient;
7161
7283
  }
7162
7284
  async ensure(options) {
7285
+ const { logger, ...restOptions } = options;
7163
7286
  let requestOptions = {
7164
- ...options,
7165
- spec: isVmSpecLike$1(options.spec) ? cloneVmSpecTree(options.spec) : void 0,
7166
- snapshot: isVmSpecLike$1(options.snapshot) ? cloneVmSpecTree(options.snapshot) : void 0,
7167
- template: isVmTemplateLike$1(options.template) ? cloneVmTemplateTree(options.template) : options.template
7287
+ ...restOptions,
7288
+ spec: isVmSpecLike$1(restOptions.spec) ? cloneVmSpecTree(restOptions.spec) : void 0,
7289
+ snapshot: isVmSpecLike$1(restOptions.snapshot) ? cloneVmSpecTree(restOptions.snapshot) : void 0,
7290
+ template: isVmTemplateLike$1(restOptions.template) ? cloneVmTemplateTree(restOptions.template) : restOptions.template
7168
7291
  };
7169
7292
  if (isVmSpecLike$1(requestOptions.snapshot)) {
7170
7293
  if (isVmSpecLike$1(requestOptions.spec)) {
@@ -7202,7 +7325,8 @@ class VmSnapshotsNamespace {
7202
7325
  if (isVmTemplateLike$1(processedTemplate.raw.template)) {
7203
7326
  requestOptions.template = await ensureNestedTemplates(
7204
7327
  processedTemplate,
7205
- this
7328
+ this,
7329
+ logger
7206
7330
  );
7207
7331
  }
7208
7332
  }
@@ -7211,18 +7335,43 @@ class VmSnapshotsNamespace {
7211
7335
  "snapshots.ensure requires a template or spec to build a snapshot"
7212
7336
  );
7213
7337
  }
7214
- return this.apiClient.post("/v1/vms/snapshots", {
7215
- body: {
7216
- ...requestOptions,
7217
- template: normalizeTemplateForRequest(
7218
- isVmTemplateLike$1(requestOptions.template) ? requestOptions.template.raw : requestOptions.template
7219
- )
7220
- }
7221
- }).catch((e) => {
7338
+ return postWithBackgroundLogger(
7339
+ this.apiClient,
7340
+ "/v1/vms/snapshots",
7341
+ {
7342
+ body: {
7343
+ ...requestOptions,
7344
+ template: normalizeTemplateForRequest(
7345
+ isVmTemplateLike$1(requestOptions.template) ? requestOptions.template.raw : requestOptions.template
7346
+ )
7347
+ }
7348
+ },
7349
+ logger
7350
+ ).catch((e) => {
7222
7351
  enhanceError(e);
7223
7352
  throw e;
7224
7353
  });
7225
7354
  }
7355
+ async create(options) {
7356
+ return this.ensure(options);
7357
+ }
7358
+ async delete({ snapshotId }) {
7359
+ const path = "/v1/vms/snapshots/{snapshot_id}";
7360
+ const response = await this.apiClient.fetch(
7361
+ this.apiClient.resolveUrl(path, { snapshot_id: snapshotId }),
7362
+ { method: "DELETE" }
7363
+ );
7364
+ if (!response.ok) {
7365
+ const errorBody = await response.json().catch(() => null);
7366
+ if (errorBody?.code) {
7367
+ throw errorFromJSON(errorBody);
7368
+ }
7369
+ throw new Error(
7370
+ `Failed to delete snapshot: ${response.status} ${response.statusText}`
7371
+ );
7372
+ }
7373
+ return await response.json();
7374
+ }
7226
7375
  }
7227
7376
  async function processTemplateTree(template) {
7228
7377
  if (template.raw.baseImage) {
@@ -7294,24 +7443,24 @@ async function processTemplateTree(template) {
7294
7443
  }
7295
7444
  return template;
7296
7445
  }
7297
- async function ensureNestedTemplates(template, snapshots) {
7446
+ async function ensureNestedTemplates(template, snapshots, logger) {
7298
7447
  const templates = [template];
7299
7448
  while (isVmTemplateLike$1(templates.at(-1)?.raw.template)) {
7300
7449
  const innerTemplate = templates.at(-1).raw.template;
7301
7450
  templates.at(-1).raw.template = void 0;
7302
7451
  templates.push(innerTemplate);
7303
7452
  }
7304
- return await layerTemplates(templates, snapshots);
7453
+ return await layerTemplates(templates, snapshots, logger);
7305
7454
  }
7306
- async function layerTemplates(templates, snapshots) {
7455
+ async function layerTemplates(templates, snapshots, logger) {
7307
7456
  if (templates.length === 1) {
7308
7457
  return templates[0];
7309
7458
  }
7310
- let lastSnapshotId = (await snapshots.ensure({ template: templates.pop() })).snapshotId;
7459
+ let lastSnapshotId = (await snapshots.ensure({ template: templates.pop(), logger })).snapshotId;
7311
7460
  while (templates.length > 1) {
7312
7461
  const template = templates.pop();
7313
7462
  template.raw.snapshotId = lastSnapshotId;
7314
- lastSnapshotId = (await snapshots.ensure({ template })).snapshotId;
7463
+ lastSnapshotId = (await snapshots.ensure({ template, logger })).snapshotId;
7315
7464
  }
7316
7465
  const outermost = templates.pop();
7317
7466
  outermost.raw.snapshotId = lastSnapshotId;
package/index.d.cts CHANGED
@@ -4467,6 +4467,12 @@ interface PostV1VmsRequestBody {
4467
4467
  * These packages will be installed using `apt-get install` on VM startup.
4468
4468
  */
4469
4469
  aptDeps?: string[] | null;
4470
+ /**
4471
+ * When true, bypasses the snapshot cache and always creates a new snapshot.
4472
+ * The new snapshot still stores the template hash, so it becomes the updated
4473
+ * cache entry for future requests that do not set skipCache.
4474
+ */
4475
+ skipCache?: boolean | null;
4470
4476
  };
4471
4477
  /**
4472
4478
  * @deprecated
@@ -4883,6 +4889,12 @@ interface PostV1VmsSnapshotsRequestBody {
4883
4889
  * These packages will be installed using `apt-get install` on VM startup.
4884
4890
  */
4885
4891
  aptDeps?: string[] | null;
4892
+ /**
4893
+ * When true, bypasses the snapshot cache and always creates a new snapshot.
4894
+ * The new snapshot still stores the template hash, so it becomes the updated
4895
+ * cache entry for future requests that do not set skipCache.
4896
+ */
4897
+ skipCache?: boolean | null;
4886
4898
  };
4887
4899
  persistence?: null | ({
4888
4900
  priority?: number | null;
@@ -6695,9 +6707,11 @@ declare class ApiClient {
6695
6707
  private requestRaw;
6696
6708
  private request;
6697
6709
  fetch(url: string, options?: RequestInit): Promise<Response>;
6710
+ resolveUrl(path: string, params?: Record<string, string>, query?: Record<string, any>): string;
6698
6711
  getRaw<P extends keyof GetPathMap>(path: P, options?: GetPathMap[P]["options"]): Promise<Response>;
6699
6712
  get<P extends keyof GetPathMap>(path: P, ...args: GetPathMap[P]["options"] extends undefined ? [options?: GetPathMap[P]["options"]] : [options: GetPathMap[P]["options"]]): Promise<GetPathMap[P]["response"]>;
6700
6713
  post<P extends keyof PostPathMap>(path: P, ...args: PostPathMap[P]["options"] extends undefined ? [options?: PostPathMap[P]["options"]] : [options: PostPathMap[P]["options"]]): Promise<PostPathMap[P]["response"]>;
6714
+ postRaw<P extends keyof PostPathMap>(path: P, ...args: PostPathMap[P]["options"] extends undefined ? [options?: PostPathMap[P]["options"]] : [options: PostPathMap[P]["options"]]): Promise<Response>;
6701
6715
  put<P extends keyof PutPathMap>(path: P, ...args: PutPathMap[P]["options"] extends undefined ? [options?: PutPathMap[P]["options"]] : [options: PutPathMap[P]["options"]]): Promise<PutPathMap[P]["response"]>;
6702
6716
  delete<P extends keyof DeletePathMap>(path: P, ...args: DeletePathMap[P]["options"] extends undefined ? [options?: DeletePathMap[P]["options"]] : [options: DeletePathMap[P]["options"]]): Promise<DeletePathMap[P]["response"]>;
6703
6717
  patch<P extends keyof PatchPathMap>(path: P, ...args: PatchPathMap[P]["options"] extends undefined ? [options?: PatchPathMap[P]["options"]] : [options: PatchPathMap[P]["options"]]): Promise<PatchPathMap[P]["response"]>;
@@ -11909,6 +11923,12 @@ interface CreateSnapshotRequest {
11909
11923
  * These packages will be installed using `apt-get install` on VM startup.
11910
11924
  */
11911
11925
  aptDeps?: string[] | null;
11926
+ /**
11927
+ * When true, bypasses the snapshot cache and always creates a new snapshot.
11928
+ * The new snapshot still stores the template hash, so it becomes the updated
11929
+ * cache entry for future requests that do not set skipCache.
11930
+ */
11931
+ skipCache?: boolean | null;
11912
11932
  };
11913
11933
  persistence?: null | ({
11914
11934
  priority?: number | null;
@@ -12295,6 +12315,16 @@ type SystemdServiceInput = Omit<RawSystemdService, "mode" | "exec"> & {
12295
12315
  type VmWaitForConfig = Omit<SystemdServiceInput, "mode" | "exec" | "bash" | "deleteAfterSuccess"> & {
12296
12316
  intervalSeconds?: number;
12297
12317
  };
12318
+ type BackgroundRequestLogger = (message: string) => void;
12319
+ type SnapshotCreateOptions<T extends Record<string, VmWithLike> = {}> = Omit<PostV1VmsSnapshotsRequestBody, "template"> & {
12320
+ template?: VmTemplate<T> | PostV1VmsSnapshotsRequestBody["template"];
12321
+ spec?: VmSpec<T>;
12322
+ snapshot?: VmSpec<T>;
12323
+ logger?: BackgroundRequestLogger;
12324
+ };
12325
+ type SnapshotDeleteResponse = {
12326
+ snapshotId: string;
12327
+ };
12298
12328
  /**
12299
12329
  * Terminal management operations for a VM.
12300
12330
  */
@@ -12428,9 +12458,7 @@ declare class Vm {
12428
12458
  ref({ vmId }: {
12429
12459
  vmId: string;
12430
12460
  }): Vm;
12431
- delete({ vmId }: {
12432
- vmId: string;
12433
- }): Promise<ResponseDeleteV1VmsVmId200>;
12461
+ delete(): Promise<ResponseDeleteV1VmsVmId200>;
12434
12462
  }
12435
12463
  /**
12436
12464
  * Git configuration with optional config (defaults to {})
@@ -12496,6 +12524,7 @@ declare class VmSpec<T extends Record<string, VmWithLike> = {}> {
12496
12524
  readySignalTimeoutSeconds(value: number): this;
12497
12525
  waitForReadySignal(value: boolean): this;
12498
12526
  discriminator(value: string): this;
12527
+ skipCache(): this;
12499
12528
  workdir(path: string): this;
12500
12529
  aptDeps(...deps: string[]): this;
12501
12530
  users(users: NonNullable<CreateVmOptions["users"]>): this;
@@ -12532,6 +12561,7 @@ declare class VmsNamespace {
12532
12561
  template?: VmTemplate<T>;
12533
12562
  spec?: VmSpec<T>;
12534
12563
  snapshot?: VmSpec<T>;
12564
+ logger?: BackgroundRequestLogger;
12535
12565
  }): Promise<Omit<ResponsePostV1Vms200, "consoleUrl"> & {
12536
12566
  vmId: string;
12537
12567
  vm: Vm & {
@@ -12575,11 +12605,11 @@ declare class VmsNamespace {
12575
12605
  declare class VmSnapshotsNamespace {
12576
12606
  private apiClient;
12577
12607
  constructor(apiClient: ApiClient);
12578
- ensure<T extends Record<string, VmWithLike>>(options: Omit<PostV1VmsSnapshotsRequestBody, "template"> & {
12579
- template?: VmTemplate<T> | PostV1VmsSnapshotsRequestBody["template"];
12580
- spec?: VmSpec<T>;
12581
- snapshot?: VmSpec<T>;
12582
- }): Promise<ResponsePostV1VmsSnapshots200>;
12608
+ ensure<T extends Record<string, VmWithLike>>(options: SnapshotCreateOptions<T>): Promise<ResponsePostV1VmsSnapshots200>;
12609
+ create<T extends Record<string, VmWithLike>>(options: SnapshotCreateOptions<T>): Promise<ResponsePostV1VmsSnapshots200>;
12610
+ delete({ snapshotId }: {
12611
+ snapshotId: string;
12612
+ }): Promise<SnapshotDeleteResponse>;
12583
12613
  }
12584
12614
  type CreateVmOptions = Omit<PostV1VmsRequestBody, "template" | "systemd" | "git"> & {
12585
12615
  rootfsSizeGb?: number | null;
@@ -12595,6 +12625,8 @@ type CreateVmOptions = Omit<PostV1VmsRequestBody, "template" | "systemd" | "git"
12595
12625
  };
12596
12626
  git?: null | GitOptions;
12597
12627
  discriminator?: CreateSnapshotRequest["template"]["discriminator"];
12628
+ skipCache?: CreateSnapshotRequest["template"]["skipCache"];
12629
+ logger?: BackgroundRequestLogger;
12598
12630
  };
12599
12631
 
12600
12632
  type CronSchedule = {
@@ -12743,4 +12775,4 @@ declare class Freestyle {
12743
12775
  declare const freestyle: Freestyle;
12744
12776
 
12745
12777
  export { CronNamespace, Deployment, errors as Errors, FileSystem, Freestyle, GitRepo, Identity, requests as Requests, responses as Responses, Systemd, SystemdService, Vm, VmBaseImage, VmBuilder, VmService, VmSpec, VmTemplate, VmWith, VmWithInstance, debugCreateRequests, freestyle, readFiles };
12746
- export type { CreateVmOptions, CronSchedule, FreestyleOptions, SystemdServiceInput, VmWaitForConfig, VmWithDefaultFieldRecord };
12778
+ export type { BackgroundRequestLogger, CreateVmOptions, CronSchedule, FreestyleOptions, SystemdServiceInput, VmWaitForConfig, VmWithDefaultFieldRecord };
package/index.d.mts CHANGED
@@ -4467,6 +4467,12 @@ interface PostV1VmsRequestBody {
4467
4467
  * These packages will be installed using `apt-get install` on VM startup.
4468
4468
  */
4469
4469
  aptDeps?: string[] | null;
4470
+ /**
4471
+ * When true, bypasses the snapshot cache and always creates a new snapshot.
4472
+ * The new snapshot still stores the template hash, so it becomes the updated
4473
+ * cache entry for future requests that do not set skipCache.
4474
+ */
4475
+ skipCache?: boolean | null;
4470
4476
  };
4471
4477
  /**
4472
4478
  * @deprecated
@@ -4883,6 +4889,12 @@ interface PostV1VmsSnapshotsRequestBody {
4883
4889
  * These packages will be installed using `apt-get install` on VM startup.
4884
4890
  */
4885
4891
  aptDeps?: string[] | null;
4892
+ /**
4893
+ * When true, bypasses the snapshot cache and always creates a new snapshot.
4894
+ * The new snapshot still stores the template hash, so it becomes the updated
4895
+ * cache entry for future requests that do not set skipCache.
4896
+ */
4897
+ skipCache?: boolean | null;
4886
4898
  };
4887
4899
  persistence?: null | ({
4888
4900
  priority?: number | null;
@@ -6695,9 +6707,11 @@ declare class ApiClient {
6695
6707
  private requestRaw;
6696
6708
  private request;
6697
6709
  fetch(url: string, options?: RequestInit): Promise<Response>;
6710
+ resolveUrl(path: string, params?: Record<string, string>, query?: Record<string, any>): string;
6698
6711
  getRaw<P extends keyof GetPathMap>(path: P, options?: GetPathMap[P]["options"]): Promise<Response>;
6699
6712
  get<P extends keyof GetPathMap>(path: P, ...args: GetPathMap[P]["options"] extends undefined ? [options?: GetPathMap[P]["options"]] : [options: GetPathMap[P]["options"]]): Promise<GetPathMap[P]["response"]>;
6700
6713
  post<P extends keyof PostPathMap>(path: P, ...args: PostPathMap[P]["options"] extends undefined ? [options?: PostPathMap[P]["options"]] : [options: PostPathMap[P]["options"]]): Promise<PostPathMap[P]["response"]>;
6714
+ postRaw<P extends keyof PostPathMap>(path: P, ...args: PostPathMap[P]["options"] extends undefined ? [options?: PostPathMap[P]["options"]] : [options: PostPathMap[P]["options"]]): Promise<Response>;
6701
6715
  put<P extends keyof PutPathMap>(path: P, ...args: PutPathMap[P]["options"] extends undefined ? [options?: PutPathMap[P]["options"]] : [options: PutPathMap[P]["options"]]): Promise<PutPathMap[P]["response"]>;
6702
6716
  delete<P extends keyof DeletePathMap>(path: P, ...args: DeletePathMap[P]["options"] extends undefined ? [options?: DeletePathMap[P]["options"]] : [options: DeletePathMap[P]["options"]]): Promise<DeletePathMap[P]["response"]>;
6703
6717
  patch<P extends keyof PatchPathMap>(path: P, ...args: PatchPathMap[P]["options"] extends undefined ? [options?: PatchPathMap[P]["options"]] : [options: PatchPathMap[P]["options"]]): Promise<PatchPathMap[P]["response"]>;
@@ -11909,6 +11923,12 @@ interface CreateSnapshotRequest {
11909
11923
  * These packages will be installed using `apt-get install` on VM startup.
11910
11924
  */
11911
11925
  aptDeps?: string[] | null;
11926
+ /**
11927
+ * When true, bypasses the snapshot cache and always creates a new snapshot.
11928
+ * The new snapshot still stores the template hash, so it becomes the updated
11929
+ * cache entry for future requests that do not set skipCache.
11930
+ */
11931
+ skipCache?: boolean | null;
11912
11932
  };
11913
11933
  persistence?: null | ({
11914
11934
  priority?: number | null;
@@ -12295,6 +12315,16 @@ type SystemdServiceInput = Omit<RawSystemdService, "mode" | "exec"> & {
12295
12315
  type VmWaitForConfig = Omit<SystemdServiceInput, "mode" | "exec" | "bash" | "deleteAfterSuccess"> & {
12296
12316
  intervalSeconds?: number;
12297
12317
  };
12318
+ type BackgroundRequestLogger = (message: string) => void;
12319
+ type SnapshotCreateOptions<T extends Record<string, VmWithLike> = {}> = Omit<PostV1VmsSnapshotsRequestBody, "template"> & {
12320
+ template?: VmTemplate<T> | PostV1VmsSnapshotsRequestBody["template"];
12321
+ spec?: VmSpec<T>;
12322
+ snapshot?: VmSpec<T>;
12323
+ logger?: BackgroundRequestLogger;
12324
+ };
12325
+ type SnapshotDeleteResponse = {
12326
+ snapshotId: string;
12327
+ };
12298
12328
  /**
12299
12329
  * Terminal management operations for a VM.
12300
12330
  */
@@ -12428,9 +12458,7 @@ declare class Vm {
12428
12458
  ref({ vmId }: {
12429
12459
  vmId: string;
12430
12460
  }): Vm;
12431
- delete({ vmId }: {
12432
- vmId: string;
12433
- }): Promise<ResponseDeleteV1VmsVmId200>;
12461
+ delete(): Promise<ResponseDeleteV1VmsVmId200>;
12434
12462
  }
12435
12463
  /**
12436
12464
  * Git configuration with optional config (defaults to {})
@@ -12496,6 +12524,7 @@ declare class VmSpec<T extends Record<string, VmWithLike> = {}> {
12496
12524
  readySignalTimeoutSeconds(value: number): this;
12497
12525
  waitForReadySignal(value: boolean): this;
12498
12526
  discriminator(value: string): this;
12527
+ skipCache(): this;
12499
12528
  workdir(path: string): this;
12500
12529
  aptDeps(...deps: string[]): this;
12501
12530
  users(users: NonNullable<CreateVmOptions["users"]>): this;
@@ -12532,6 +12561,7 @@ declare class VmsNamespace {
12532
12561
  template?: VmTemplate<T>;
12533
12562
  spec?: VmSpec<T>;
12534
12563
  snapshot?: VmSpec<T>;
12564
+ logger?: BackgroundRequestLogger;
12535
12565
  }): Promise<Omit<ResponsePostV1Vms200, "consoleUrl"> & {
12536
12566
  vmId: string;
12537
12567
  vm: Vm & {
@@ -12575,11 +12605,11 @@ declare class VmsNamespace {
12575
12605
  declare class VmSnapshotsNamespace {
12576
12606
  private apiClient;
12577
12607
  constructor(apiClient: ApiClient);
12578
- ensure<T extends Record<string, VmWithLike>>(options: Omit<PostV1VmsSnapshotsRequestBody, "template"> & {
12579
- template?: VmTemplate<T> | PostV1VmsSnapshotsRequestBody["template"];
12580
- spec?: VmSpec<T>;
12581
- snapshot?: VmSpec<T>;
12582
- }): Promise<ResponsePostV1VmsSnapshots200>;
12608
+ ensure<T extends Record<string, VmWithLike>>(options: SnapshotCreateOptions<T>): Promise<ResponsePostV1VmsSnapshots200>;
12609
+ create<T extends Record<string, VmWithLike>>(options: SnapshotCreateOptions<T>): Promise<ResponsePostV1VmsSnapshots200>;
12610
+ delete({ snapshotId }: {
12611
+ snapshotId: string;
12612
+ }): Promise<SnapshotDeleteResponse>;
12583
12613
  }
12584
12614
  type CreateVmOptions = Omit<PostV1VmsRequestBody, "template" | "systemd" | "git"> & {
12585
12615
  rootfsSizeGb?: number | null;
@@ -12595,6 +12625,8 @@ type CreateVmOptions = Omit<PostV1VmsRequestBody, "template" | "systemd" | "git"
12595
12625
  };
12596
12626
  git?: null | GitOptions;
12597
12627
  discriminator?: CreateSnapshotRequest["template"]["discriminator"];
12628
+ skipCache?: CreateSnapshotRequest["template"]["skipCache"];
12629
+ logger?: BackgroundRequestLogger;
12598
12630
  };
12599
12631
 
12600
12632
  type CronSchedule = {
@@ -12743,4 +12775,4 @@ declare class Freestyle {
12743
12775
  declare const freestyle: Freestyle;
12744
12776
 
12745
12777
  export { CronNamespace, Deployment, errors as Errors, FileSystem, Freestyle, GitRepo, Identity, requests as Requests, responses as Responses, Systemd, SystemdService, Vm, VmBaseImage, VmBuilder, VmService, VmSpec, VmTemplate, VmWith, VmWithInstance, debugCreateRequests, freestyle, readFiles };
12746
- export type { CreateVmOptions, CronSchedule, FreestyleOptions, SystemdServiceInput, VmWaitForConfig, VmWithDefaultFieldRecord };
12778
+ export type { BackgroundRequestLogger, CreateVmOptions, CronSchedule, FreestyleOptions, SystemdServiceInput, VmWaitForConfig, VmWithDefaultFieldRecord };
package/index.mjs CHANGED
@@ -3761,6 +3761,9 @@ class ApiClient {
3761
3761
  };
3762
3762
  return this.fetchFn(url, finalOptions);
3763
3763
  }
3764
+ resolveUrl(path, params, query) {
3765
+ return this.buildUrl(path, params, query);
3766
+ }
3764
3767
  getRaw(path, options) {
3765
3768
  const url = this.buildUrl(path, options?.params, options?.query);
3766
3769
  return this.requestRaw("GET", url, void 0, options?.headers);
@@ -3775,6 +3778,11 @@ class ApiClient {
3775
3778
  const url = this.buildUrl(path, options?.params, options?.query);
3776
3779
  return this.request("POST", url, options?.body, options?.headers);
3777
3780
  }
3781
+ postRaw(path, ...args) {
3782
+ const options = args[0];
3783
+ const url = this.buildUrl(path, options?.params, options?.query);
3784
+ return this.requestRaw("POST", url, options?.body, options?.headers);
3785
+ }
3778
3786
  put(path, ...args) {
3779
3787
  const options = args[0];
3780
3788
  const url = this.buildUrl(path, options?.params, options?.query);
@@ -5646,6 +5654,7 @@ function composeCreateVmOptions(arr) {
5646
5654
  result.activityThresholdBytes = options.activityThresholdBytes;
5647
5655
  if (options.discriminator !== void 0)
5648
5656
  result.discriminator = options.discriminator;
5657
+ if (options.skipCache !== void 0) result.skipCache = options.skipCache;
5649
5658
  if (options.persistence !== void 0)
5650
5659
  result.persistence = options.persistence;
5651
5660
  if (options.ports !== void 0) {
@@ -5793,6 +5802,7 @@ function composeCreateVmOptions(arr) {
5793
5802
  aptDeps: newTemplate.aptDeps ?? baseTemplate.aptDeps,
5794
5803
  vcpuCount: newTemplate.vcpuCount !== void 0 ? newTemplate.vcpuCount : baseTemplate.vcpuCount,
5795
5804
  discriminator: newTemplate.discriminator ?? baseTemplate.discriminator,
5805
+ skipCache: newTemplate.skipCache ?? baseTemplate.skipCache,
5796
5806
  workdir: newTemplate.workdir !== void 0 ? newTemplate.workdir : baseTemplate.workdir,
5797
5807
  idleTimeoutSeconds: newTemplate.idleTimeoutSeconds !== void 0 ? newTemplate.idleTimeoutSeconds : baseTemplate.idleTimeoutSeconds,
5798
5808
  waitForReadySignal: newTemplate.waitForReadySignal !== void 0 ? newTemplate.waitForReadySignal : baseTemplate.waitForReadySignal,
@@ -6009,6 +6019,94 @@ function composeVmSpecs(specs) {
6009
6019
  const DEFAULT_CONFIGURE_BASE_IMAGE = "FROM debian:trixie-slim";
6010
6020
  const RUN_COMMANDS_SYSTEMD_SERVICE_PREFIX = "freestyle-run-command";
6011
6021
  const WAIT_FOR_SYSTEMD_SERVICE_PREFIX = "freestyle-wait-for";
6022
+ const BACKGROUND_AFTER_SECS_HEADER = "x-freestyle-background-after-secs";
6023
+ const BACKGROUND_REQUEST_ID_HEADER = "x-freestyle-background-request-id";
6024
+ const DEFAULT_BACKGROUND_AFTER_SECS = 5;
6025
+ const DEFAULT_BACKGROUND_POLL_INTERVAL_MS = 2e3;
6026
+ function delay(ms) {
6027
+ const timerApi = globalThis;
6028
+ return new Promise((resolve) => timerApi.setTimeout(resolve, ms));
6029
+ }
6030
+ function extractBackgroundRequestId(response, body) {
6031
+ return response.headers.get(BACKGROUND_REQUEST_ID_HEADER) ?? body?.requestId ?? body?.request_id;
6032
+ }
6033
+ async function parseJsonResponse(response) {
6034
+ return await response.json();
6035
+ }
6036
+ async function readResponseError(response, fallbackMessage) {
6037
+ const responseText = await response.text();
6038
+ if (!responseText) {
6039
+ return fallbackMessage;
6040
+ }
6041
+ try {
6042
+ const errorBody = JSON.parse(responseText);
6043
+ return errorBody.message ?? errorBody.error ?? responseText;
6044
+ } catch {
6045
+ return responseText;
6046
+ }
6047
+ }
6048
+ async function emitBackgroundLogs(apiClient, requestId, logger, seenLogs) {
6049
+ const response = await apiClient.fetch(
6050
+ apiClient.resolveUrl("/observability/v1/logs", void 0, { requestId }),
6051
+ { method: "GET" }
6052
+ );
6053
+ if (!response.ok) {
6054
+ return;
6055
+ }
6056
+ const payload = await response.json();
6057
+ for (const entry of payload.logs ?? []) {
6058
+ const key = `${entry.timestamp} ${entry.message}`;
6059
+ if (seenLogs.has(key)) {
6060
+ continue;
6061
+ }
6062
+ seenLogs.add(key);
6063
+ logger(`[${entry.timestamp}] ${entry.message}`);
6064
+ }
6065
+ }
6066
+ async function waitForBackgroundRequest(apiClient, requestId, logger) {
6067
+ const seenLogs = /* @__PURE__ */ new Set();
6068
+ const resultUrl = apiClient.resolveUrl(
6069
+ `/auth/v1/background-requests/${encodeURIComponent(requestId)}`
6070
+ );
6071
+ while (true) {
6072
+ await emitBackgroundLogs(apiClient, requestId, logger, seenLogs);
6073
+ const response = await apiClient.fetch(resultUrl, { method: "GET" });
6074
+ if (response.status === 202) {
6075
+ await delay(DEFAULT_BACKGROUND_POLL_INTERVAL_MS);
6076
+ continue;
6077
+ }
6078
+ if (!response.ok) {
6079
+ const message = await readResponseError(
6080
+ response,
6081
+ `Background request ${requestId} failed`
6082
+ );
6083
+ throw new Error(message);
6084
+ }
6085
+ return parseJsonResponse(response);
6086
+ }
6087
+ }
6088
+ async function postWithBackgroundLogger(apiClient, path, options, logger) {
6089
+ const response = await apiClient.postRaw(path, {
6090
+ ...options,
6091
+ headers: logger ? {
6092
+ ...options.headers ?? {},
6093
+ [BACKGROUND_AFTER_SECS_HEADER]: String(DEFAULT_BACKGROUND_AFTER_SECS)
6094
+ } : options.headers
6095
+ });
6096
+ if (response.status !== 202 || !logger) {
6097
+ return parseJsonResponse(response);
6098
+ }
6099
+ const accepted = await parseJsonResponse(
6100
+ response
6101
+ );
6102
+ const requestId = extractBackgroundRequestId(response, accepted);
6103
+ if (!requestId) {
6104
+ throw new Error(
6105
+ `Background request response for ${path} did not include a request ID`
6106
+ );
6107
+ }
6108
+ return waitForBackgroundRequest(apiClient, requestId, logger);
6109
+ }
6012
6110
  function escapeRegExp(value) {
6013
6111
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6014
6112
  }
@@ -6390,9 +6488,9 @@ class Vm {
6390
6488
  ref({ vmId }) {
6391
6489
  return new Vm({ vmId, freestyle: this._freestyle });
6392
6490
  }
6393
- async delete({ vmId }) {
6491
+ async delete() {
6394
6492
  return this.apiClient.delete("/v1/vms/{vm_id}", {
6395
- params: { vm_id: vmId }
6493
+ params: { vm_id: this.vmId }
6396
6494
  });
6397
6495
  }
6398
6496
  }
@@ -6543,7 +6641,10 @@ class VmSpec {
6543
6641
  }
6544
6642
  ensureSnapshot() {
6545
6643
  if (!isVmSpecLike$1(this.raw.snapshot)) {
6546
- this.raw.snapshot = this.raw.discriminator !== void 0 ? new VmSpec({ discriminator: this.raw.discriminator }) : new VmSpec();
6644
+ this.raw.snapshot = new VmSpec({
6645
+ discriminator: this.raw.discriminator,
6646
+ skipCache: this.raw.skipCache
6647
+ });
6547
6648
  }
6548
6649
  return this.raw.snapshot;
6549
6650
  }
@@ -6572,6 +6673,9 @@ class VmSpec {
6572
6673
  discriminator(value) {
6573
6674
  return this.mergeRaw({ discriminator: value });
6574
6675
  }
6676
+ skipCache() {
6677
+ return this.mergeRaw({ skipCache: true });
6678
+ }
6575
6679
  workdir(path) {
6576
6680
  return this.mergeRaw({ workdir: path });
6577
6681
  }
@@ -6798,7 +6902,10 @@ async function processOuterSpecBuilders(spec) {
6798
6902
  snapshotSpec = spec.raw.snapshot;
6799
6903
  } else {
6800
6904
  const inheritedDiscriminator = spec.getBuilderDiscriminator(key) ?? spec.raw.discriminator;
6801
- snapshotSpec = inheritedDiscriminator !== void 0 ? new VmSpec({ discriminator: inheritedDiscriminator }) : new VmSpec({});
6905
+ snapshotSpec = new VmSpec({
6906
+ discriminator: inheritedDiscriminator,
6907
+ skipCache: spec.raw.skipCache
6908
+ });
6802
6909
  spec.raw.snapshot = snapshotSpec;
6803
6910
  }
6804
6911
  spec.raw.snapshot = await builder.configureSnapshotSpec(snapshotSpec);
@@ -6825,7 +6932,10 @@ async function processSpecTree(spec) {
6825
6932
  snapshotSpec = spec.raw.snapshot;
6826
6933
  } else {
6827
6934
  const inheritedDiscriminator = spec.getBuilderDiscriminator(key) ?? spec.raw.discriminator;
6828
- snapshotSpec = inheritedDiscriminator !== void 0 ? new VmSpec({ discriminator: inheritedDiscriminator }) : new VmSpec({});
6935
+ snapshotSpec = new VmSpec({
6936
+ discriminator: inheritedDiscriminator,
6937
+ skipCache: spec.raw.skipCache
6938
+ });
6829
6939
  spec.raw.snapshot = snapshotSpec;
6830
6940
  }
6831
6941
  spec.raw.snapshot = await builder.configureSnapshotSpec(snapshotSpec);
@@ -6870,9 +6980,15 @@ class VmsNamespace {
6870
6980
  }
6871
6981
  snapshots;
6872
6982
  async create(options = {}) {
6983
+ let logger;
6873
6984
  if (isVmSpecLike$1(options)) {
6874
6985
  options = { spec: cloneVmSpecTree(options) };
6875
6986
  } else {
6987
+ logger = options.logger;
6988
+ if ("logger" in options) {
6989
+ const { logger: _logger, ...rest } = options;
6990
+ options = rest;
6991
+ }
6876
6992
  if (isVmSpecLike$1(options.spec)) {
6877
6993
  options.spec = cloneVmSpecTree(options.spec);
6878
6994
  }
@@ -6952,7 +7068,8 @@ class VmsNamespace {
6952
7068
  if (isVmTemplateLike$1(config.template)) {
6953
7069
  config.template = await ensureNestedTemplates(
6954
7070
  config.template,
6955
- this.snapshots
7071
+ this.snapshots,
7072
+ logger
6956
7073
  );
6957
7074
  }
6958
7075
  const keys = Object.keys(builders);
@@ -6993,16 +7110,21 @@ class VmsNamespace {
6993
7110
  template: _template,
6994
7111
  ...requestConfig
6995
7112
  } = config;
6996
- const response = await this.freestyle._apiClient.post("/v1/vms", {
6997
- body: {
6998
- ...requestConfig,
6999
- template: normalizedRequestTemplate,
7000
- // Cast systemd since we've processed SystemdServiceInput[] to RawSystemdService[]
7001
- systemd: config.systemd,
7002
- // Normalize git options - default config to {}
7003
- git: normalizeGitOptions(config.git)
7004
- }
7005
- }).catch((e) => {
7113
+ const response = await postWithBackgroundLogger(
7114
+ this.freestyle._apiClient,
7115
+ "/v1/vms",
7116
+ {
7117
+ body: {
7118
+ ...requestConfig,
7119
+ template: normalizedRequestTemplate,
7120
+ // Cast systemd since we've processed SystemdServiceInput[] to RawSystemdService[]
7121
+ systemd: config.systemd,
7122
+ // Normalize git options - default config to {}
7123
+ git: normalizeGitOptions(config.git)
7124
+ }
7125
+ },
7126
+ logger
7127
+ ).catch((e) => {
7006
7128
  enhanceError(e);
7007
7129
  throw e;
7008
7130
  });
@@ -7158,11 +7280,12 @@ class VmSnapshotsNamespace {
7158
7280
  this.apiClient = apiClient;
7159
7281
  }
7160
7282
  async ensure(options) {
7283
+ const { logger, ...restOptions } = options;
7161
7284
  let requestOptions = {
7162
- ...options,
7163
- spec: isVmSpecLike$1(options.spec) ? cloneVmSpecTree(options.spec) : void 0,
7164
- snapshot: isVmSpecLike$1(options.snapshot) ? cloneVmSpecTree(options.snapshot) : void 0,
7165
- template: isVmTemplateLike$1(options.template) ? cloneVmTemplateTree(options.template) : options.template
7285
+ ...restOptions,
7286
+ spec: isVmSpecLike$1(restOptions.spec) ? cloneVmSpecTree(restOptions.spec) : void 0,
7287
+ snapshot: isVmSpecLike$1(restOptions.snapshot) ? cloneVmSpecTree(restOptions.snapshot) : void 0,
7288
+ template: isVmTemplateLike$1(restOptions.template) ? cloneVmTemplateTree(restOptions.template) : restOptions.template
7166
7289
  };
7167
7290
  if (isVmSpecLike$1(requestOptions.snapshot)) {
7168
7291
  if (isVmSpecLike$1(requestOptions.spec)) {
@@ -7200,7 +7323,8 @@ class VmSnapshotsNamespace {
7200
7323
  if (isVmTemplateLike$1(processedTemplate.raw.template)) {
7201
7324
  requestOptions.template = await ensureNestedTemplates(
7202
7325
  processedTemplate,
7203
- this
7326
+ this,
7327
+ logger
7204
7328
  );
7205
7329
  }
7206
7330
  }
@@ -7209,18 +7333,43 @@ class VmSnapshotsNamespace {
7209
7333
  "snapshots.ensure requires a template or spec to build a snapshot"
7210
7334
  );
7211
7335
  }
7212
- return this.apiClient.post("/v1/vms/snapshots", {
7213
- body: {
7214
- ...requestOptions,
7215
- template: normalizeTemplateForRequest(
7216
- isVmTemplateLike$1(requestOptions.template) ? requestOptions.template.raw : requestOptions.template
7217
- )
7218
- }
7219
- }).catch((e) => {
7336
+ return postWithBackgroundLogger(
7337
+ this.apiClient,
7338
+ "/v1/vms/snapshots",
7339
+ {
7340
+ body: {
7341
+ ...requestOptions,
7342
+ template: normalizeTemplateForRequest(
7343
+ isVmTemplateLike$1(requestOptions.template) ? requestOptions.template.raw : requestOptions.template
7344
+ )
7345
+ }
7346
+ },
7347
+ logger
7348
+ ).catch((e) => {
7220
7349
  enhanceError(e);
7221
7350
  throw e;
7222
7351
  });
7223
7352
  }
7353
+ async create(options) {
7354
+ return this.ensure(options);
7355
+ }
7356
+ async delete({ snapshotId }) {
7357
+ const path = "/v1/vms/snapshots/{snapshot_id}";
7358
+ const response = await this.apiClient.fetch(
7359
+ this.apiClient.resolveUrl(path, { snapshot_id: snapshotId }),
7360
+ { method: "DELETE" }
7361
+ );
7362
+ if (!response.ok) {
7363
+ const errorBody = await response.json().catch(() => null);
7364
+ if (errorBody?.code) {
7365
+ throw errorFromJSON(errorBody);
7366
+ }
7367
+ throw new Error(
7368
+ `Failed to delete snapshot: ${response.status} ${response.statusText}`
7369
+ );
7370
+ }
7371
+ return await response.json();
7372
+ }
7224
7373
  }
7225
7374
  async function processTemplateTree(template) {
7226
7375
  if (template.raw.baseImage) {
@@ -7292,24 +7441,24 @@ async function processTemplateTree(template) {
7292
7441
  }
7293
7442
  return template;
7294
7443
  }
7295
- async function ensureNestedTemplates(template, snapshots) {
7444
+ async function ensureNestedTemplates(template, snapshots, logger) {
7296
7445
  const templates = [template];
7297
7446
  while (isVmTemplateLike$1(templates.at(-1)?.raw.template)) {
7298
7447
  const innerTemplate = templates.at(-1).raw.template;
7299
7448
  templates.at(-1).raw.template = void 0;
7300
7449
  templates.push(innerTemplate);
7301
7450
  }
7302
- return await layerTemplates(templates, snapshots);
7451
+ return await layerTemplates(templates, snapshots, logger);
7303
7452
  }
7304
- async function layerTemplates(templates, snapshots) {
7453
+ async function layerTemplates(templates, snapshots, logger) {
7305
7454
  if (templates.length === 1) {
7306
7455
  return templates[0];
7307
7456
  }
7308
- let lastSnapshotId = (await snapshots.ensure({ template: templates.pop() })).snapshotId;
7457
+ let lastSnapshotId = (await snapshots.ensure({ template: templates.pop(), logger })).snapshotId;
7309
7458
  while (templates.length > 1) {
7310
7459
  const template = templates.pop();
7311
7460
  template.raw.snapshotId = lastSnapshotId;
7312
- lastSnapshotId = (await snapshots.ensure({ template })).snapshotId;
7461
+ lastSnapshotId = (await snapshots.ensure({ template, logger })).snapshotId;
7313
7462
  }
7314
7463
  const outermost = templates.pop();
7315
7464
  outermost.raw.snapshotId = lastSnapshotId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freestyle-sandboxes",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "require": {