freestyle-sandboxes 0.1.6 → 0.1.7

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.
Files changed (5) hide show
  1. package/index.cjs +308 -46
  2. package/index.d.cts +107 -16
  3. package/index.d.mts +107 -16
  4. package/index.mjs +308 -47
  5. package/package.json +1 -1
package/index.cjs CHANGED
@@ -4624,6 +4624,32 @@ class VmWith {
4624
4624
  compose(...configs) {
4625
4625
  return composeCreateVmOptions(configs);
4626
4626
  }
4627
+ /**
4628
+ * Helper method to compose multiple VmSpecs together.
4629
+ * Uses the same merging strategy as the main compose function.
4630
+ * Useful in configureSpec and configureSnapshotSpec methods.
4631
+ */
4632
+ composeSpecs(...specs) {
4633
+ return composeVmSpecs(specs);
4634
+ }
4635
+ /**
4636
+ * @deprecated Use composeSpecs instead
4637
+ * Helper method to compose multiple VmTemplates together.
4638
+ * Uses the same merging strategy as the main compose function.
4639
+ * Useful in configureTemplate and configureNestedTemplate methods.
4640
+ */
4641
+ composeTemplates(...templates) {
4642
+ return composeVmTemplates(templates);
4643
+ }
4644
+ // Add a new method to VmWith to configure returned VMs with helpers
4645
+ async configureReturnedVm(vm, template) {
4646
+ if (template.raw) {
4647
+ await this.configure(template.raw);
4648
+ const instance = this.createInstance();
4649
+ instance._init(vm);
4650
+ this.instance = instance;
4651
+ }
4652
+ }
4627
4653
  }
4628
4654
  const VmService = VmWith;
4629
4655
  const VmBuilder = VmWith;
@@ -4875,6 +4901,73 @@ function composeCreateVmOptions(arr) {
4875
4901
  }
4876
4902
  return result;
4877
4903
  }
4904
+ function composeVmTemplates(templates) {
4905
+ if (templates.length === 0) {
4906
+ return new VmTemplate({});
4907
+ }
4908
+ if (templates.length === 1) {
4909
+ return templates[0];
4910
+ }
4911
+ const rawConfigs = templates.map((t) => t.raw);
4912
+ const composedRaw = composeCreateVmOptions(
4913
+ rawConfigs
4914
+ );
4915
+ const mergedWith = {};
4916
+ for (const template of templates) {
4917
+ for (const key in template.with) {
4918
+ const builder = template.with[key];
4919
+ if (builder) {
4920
+ mergedWith[key] = builder;
4921
+ }
4922
+ }
4923
+ }
4924
+ const nestedTemplates = templates.map((t) => t.raw.template).filter((t) => t instanceof VmTemplate);
4925
+ let nestedTemplate;
4926
+ if (nestedTemplates.length > 0) {
4927
+ nestedTemplate = composeVmTemplates(nestedTemplates);
4928
+ }
4929
+ const result = new VmTemplate({
4930
+ ...composedRaw,
4931
+ template: nestedTemplate,
4932
+ with: mergedWith
4933
+ });
4934
+ return result;
4935
+ }
4936
+ function composeVmSpecs(specs) {
4937
+ if (specs.length === 0) {
4938
+ return new VmSpec({});
4939
+ }
4940
+ if (specs.length === 1) {
4941
+ return specs[0];
4942
+ }
4943
+ const rawConfigs = specs.map((s) => {
4944
+ const { snapshot, ...rest } = s.raw;
4945
+ return rest;
4946
+ });
4947
+ const composedRaw = composeCreateVmOptions(
4948
+ rawConfigs
4949
+ );
4950
+ const mergedWith = {};
4951
+ for (const spec of specs) {
4952
+ for (const key in spec.with) {
4953
+ const builder = spec.with[key];
4954
+ if (builder) {
4955
+ mergedWith[key] = builder;
4956
+ }
4957
+ }
4958
+ }
4959
+ const nestedSpecs = specs.map((s) => s.raw.snapshot).filter((s) => s instanceof VmSpec);
4960
+ let nestedSpec;
4961
+ if (nestedSpecs.length > 0) {
4962
+ nestedSpec = composeVmSpecs(nestedSpecs);
4963
+ }
4964
+ const result = new VmSpec({
4965
+ ...composedRaw,
4966
+ snapshot: nestedSpec,
4967
+ with: mergedWith
4968
+ });
4969
+ return result;
4970
+ }
4878
4971
 
4879
4972
  class VmTerminals {
4880
4973
  constructor(vmId, client) {
@@ -5054,51 +5147,121 @@ class Vm {
5054
5147
  return this.apiClient.post("/v1/vms/{id}/resize", {
5055
5148
  params: { id: this.vmId },
5056
5149
  body: { memSizeGb, rootfsSizeGb, vcpuCount }
5150
+ // Temporarily cast to any
5057
5151
  });
5058
5152
  }
5059
- }
5060
- class VmSnapshotsNamespace {
5061
- constructor(apiClient) {
5062
- this.apiClient = apiClient;
5153
+ /**
5154
+ * Create a VM instance by ID without making an api call.
5155
+ */
5156
+ ref({ vmId }) {
5157
+ return new Vm(vmId, this.apiClient);
5063
5158
  }
5064
- async ensure(options) {
5065
- if (options.template.raw.template instanceof VmTemplate) {
5066
- options.template = await ensureNestedTemplates(options.template, this);
5067
- }
5068
- return this.apiClient.post("/v1/vms/snapshots", {
5069
- body: {
5070
- ...options,
5071
- // @ts-ignore
5072
- template: options.template?.raw
5073
- }
5159
+ async delete({ vmId }) {
5160
+ return this.apiClient.delete("/v1/vms/{vm_id}", {
5161
+ params: { vm_id: vmId }
5074
5162
  });
5075
5163
  }
5076
5164
  }
5077
- async function ensureNestedTemplates(template, snapshots) {
5078
- let templates = [template];
5079
- while (templates.at(-1)?.raw.template instanceof VmTemplate) {
5080
- let innerTemplate = templates.at(-1).raw.template;
5081
- templates.at(-1).raw.template = void 0;
5082
- templates.push(innerTemplate);
5165
+ class VmTemplate {
5166
+ raw;
5167
+ with;
5168
+ constructor(template) {
5169
+ const { with: withBuilders, ...rest } = template;
5170
+ this.raw = rest;
5171
+ this.with = withBuilders ?? {};
5083
5172
  }
5084
- return await layerTemplates(templates, snapshots);
5085
5173
  }
5086
- async function layerTemplates(templates, snapshots) {
5087
- let lastTemplate = templates.pop();
5088
- const { snapshotId } = await snapshots.ensure({
5089
- template: lastTemplate
5174
+ class VmSpec {
5175
+ raw;
5176
+ with;
5177
+ constructor(spec) {
5178
+ const { with: withBuilders, ...rest } = spec;
5179
+ this.raw = rest;
5180
+ this.with = withBuilders ?? {};
5181
+ }
5182
+ }
5183
+ async function convertSpecSnapshotsToTemplates(spec, processBuilders = true) {
5184
+ if (!spec.raw.snapshot) {
5185
+ return void 0;
5186
+ }
5187
+ let innerSpec = spec.raw.snapshot;
5188
+ if (processBuilders) {
5189
+ innerSpec = await processSpecTree(innerSpec);
5190
+ }
5191
+ const { snapshot: nestedSnapshot, ...innerRaw } = innerSpec.raw;
5192
+ let nestedTemplate;
5193
+ if (nestedSnapshot instanceof VmSpec) {
5194
+ nestedTemplate = await convertSpecSnapshotsToTemplates(innerSpec, false);
5195
+ }
5196
+ return new VmTemplate({
5197
+ ...innerRaw,
5198
+ template: nestedTemplate,
5199
+ with: innerSpec.with
5090
5200
  });
5091
- let lastSnapshotId = snapshotId;
5092
- while (templates.length > 0) {
5093
- const template = templates.pop();
5094
- template.raw.snapshotId = lastSnapshotId;
5095
- const { snapshotId: snapshotId2 } = await snapshots.ensure({
5096
- template
5097
- });
5098
- lastSnapshotId = snapshotId2;
5099
- lastTemplate = template;
5201
+ }
5202
+ async function processOuterSpecBuilders(spec) {
5203
+ for (const key in spec.with) {
5204
+ const builder = spec.with[key];
5205
+ if (builder) {
5206
+ if (builder.configureSpec) {
5207
+ spec = await builder.configureSpec(spec);
5208
+ }
5209
+ if (builder.configureSnapshotSpec) {
5210
+ let snapshotSpec;
5211
+ if (spec.raw.snapshot instanceof VmSpec) {
5212
+ snapshotSpec = spec.raw.snapshot;
5213
+ } else {
5214
+ snapshotSpec = new VmSpec({});
5215
+ spec.raw.snapshot = snapshotSpec;
5216
+ }
5217
+ spec.raw.snapshot = await builder.configureSnapshotSpec(snapshotSpec);
5218
+ }
5219
+ }
5100
5220
  }
5101
- return lastTemplate;
5221
+ return spec;
5222
+ }
5223
+ async function processSpecTree(spec) {
5224
+ for (const key in spec.with) {
5225
+ const builder = spec.with[key];
5226
+ if (builder) {
5227
+ if (builder.configureSpec) {
5228
+ spec = await builder.configureSpec(spec);
5229
+ }
5230
+ if (builder.configureSnapshotSpec) {
5231
+ let snapshotSpec;
5232
+ if (spec.raw.snapshot instanceof VmSpec) {
5233
+ snapshotSpec = spec.raw.snapshot;
5234
+ } else {
5235
+ snapshotSpec = new VmSpec({});
5236
+ spec.raw.snapshot = snapshotSpec;
5237
+ }
5238
+ spec.raw.snapshot = await builder.configureSnapshotSpec(snapshotSpec);
5239
+ }
5240
+ }
5241
+ }
5242
+ if (spec.raw.snapshot instanceof VmSpec) {
5243
+ spec.raw.snapshot = await processSpecTree(spec.raw.snapshot);
5244
+ }
5245
+ return spec;
5246
+ }
5247
+ function collectSpecBuilders(spec) {
5248
+ const builders = {};
5249
+ if (spec.with) {
5250
+ for (const [key, builder] of Object.entries(spec.with)) {
5251
+ if (builder) {
5252
+ builders[key] = builder;
5253
+ }
5254
+ }
5255
+ }
5256
+ if (spec.raw.snapshot instanceof VmSpec) {
5257
+ const nestedBuilders = collectSpecBuilders(spec.raw.snapshot);
5258
+ for (const [key, builder] of Object.entries(nestedBuilders)) {
5259
+ if (!builders[key] && builder) {
5260
+ builders[key] = builder;
5261
+ }
5262
+ }
5263
+ }
5264
+ return builders;
5102
5265
  }
5103
5266
  class VmsNamespace {
5104
5267
  constructor(apiClient) {
@@ -5112,9 +5275,32 @@ class VmsNamespace {
5112
5275
  * @returns A VM instance representing the created VM
5113
5276
  */
5114
5277
  async create(options = {}) {
5115
- const builders = options.with || {};
5278
+ if (options.spec instanceof VmSpec) {
5279
+ let spec = options.spec;
5280
+ spec = await processOuterSpecBuilders(spec);
5281
+ const { snapshot, ...outerSpecOptions } = spec.raw;
5282
+ const innerTemplate = await convertSpecSnapshotsToTemplates(spec);
5283
+ if (innerTemplate) {
5284
+ options.template = innerTemplate;
5285
+ }
5286
+ const mergedOptions = composeCreateVmOptions([
5287
+ outerSpecOptions,
5288
+ options
5289
+ ]);
5290
+ options = {
5291
+ ...options,
5292
+ ...mergedOptions
5293
+ };
5294
+ if (!options.with && spec.with) {
5295
+ options.with = spec.with;
5296
+ }
5297
+ }
5298
+ const builders = options.with || options.template?.with || {};
5116
5299
  const { with: _, ...baseConfig } = options;
5117
5300
  let config = baseConfig;
5301
+ if (config.template instanceof VmTemplate) {
5302
+ config.template = await processTemplateTree(config.template);
5303
+ }
5118
5304
  if (config.template instanceof VmTemplate) {
5119
5305
  config.template = await ensureNestedTemplates(
5120
5306
  config.template,
@@ -5140,12 +5326,6 @@ class VmsNamespace {
5140
5326
  const builder = builders[key];
5141
5327
  if (builder) {
5142
5328
  const instance = builder.createInstance();
5143
- builder.createInstance = () => {
5144
- throw new Error(
5145
- `Attempted to create multiple instances from the same VmWith ${key}: ${builder.constructor.name}`
5146
- );
5147
- };
5148
- builder.instance = instance;
5149
5329
  instance._init(vm);
5150
5330
  vm[key] = instance;
5151
5331
  }
@@ -5164,6 +5344,30 @@ class VmsNamespace {
5164
5344
  async list() {
5165
5345
  return this.apiClient.get("/v1/vms", void 0);
5166
5346
  }
5347
+ /**
5348
+ * Get VM by ID.
5349
+ * @param vmId The ID of the VM
5350
+ * @param allowedSpecs Array of allowed specs (currently only 1 supported, always uses the first)
5351
+ * @returns The VM instance configured with builders from the spec
5352
+ */
5353
+ async get({
5354
+ vmId,
5355
+ allowedSpecs
5356
+ }) {
5357
+ const vm = new Vm(vmId, this.apiClient);
5358
+ const spec = allowedSpecs?.[0];
5359
+ if (spec) {
5360
+ const allBuilders = collectSpecBuilders(spec);
5361
+ for (const [key, builder] of Object.entries(allBuilders)) {
5362
+ if (builder) {
5363
+ const instance = builder.createInstance();
5364
+ instance._init(vm);
5365
+ vm[key] = instance;
5366
+ }
5367
+ }
5368
+ }
5369
+ return { vm, spec };
5370
+ }
5167
5371
  /**
5168
5372
  * Create a VM instance by ID without making an api call.
5169
5373
  */
@@ -5176,11 +5380,68 @@ class VmsNamespace {
5176
5380
  });
5177
5381
  }
5178
5382
  }
5179
- class VmTemplate {
5180
- raw;
5181
- constructor(template) {
5182
- this.raw = template;
5383
+ class VmSnapshotsNamespace {
5384
+ constructor(apiClient) {
5385
+ this.apiClient = apiClient;
5386
+ }
5387
+ async ensure(options) {
5388
+ return this.apiClient.post("/v1/vms/snapshots", {
5389
+ body: {
5390
+ ...options,
5391
+ template: options.template?.raw
5392
+ }
5393
+ });
5394
+ }
5395
+ }
5396
+ async function processTemplateTree(template) {
5397
+ for (const key in template.with) {
5398
+ const builder = template.with[key];
5399
+ if (builder) {
5400
+ if (builder.configureTemplate) {
5401
+ template = await builder.configureTemplate(template);
5402
+ }
5403
+ if (builder.configureNestedTemplate) {
5404
+ let nestedTemplate;
5405
+ if (template.raw.template instanceof VmTemplate) {
5406
+ nestedTemplate = template.raw.template;
5407
+ } else {
5408
+ nestedTemplate = new VmTemplate({});
5409
+ template.raw.template = nestedTemplate;
5410
+ }
5411
+ template.raw.template = await builder.configureNestedTemplate(nestedTemplate);
5412
+ }
5413
+ }
5414
+ }
5415
+ if (template.raw.template instanceof VmTemplate) {
5416
+ template.raw.template = await processTemplateTree(template.raw.template);
5183
5417
  }
5418
+ return template;
5419
+ }
5420
+ async function ensureNestedTemplates(template, snapshots) {
5421
+ let templates = [template];
5422
+ while (templates.at(-1)?.raw.template instanceof VmTemplate) {
5423
+ let innerTemplate = templates.at(-1).raw.template;
5424
+ templates.at(-1).raw.template = void 0;
5425
+ templates.push(innerTemplate);
5426
+ }
5427
+ return await layerTemplates(templates, snapshots);
5428
+ }
5429
+ async function layerTemplates(templates, snapshots) {
5430
+ let lastTemplate = templates.pop();
5431
+ const { snapshotId } = await snapshots.ensure({
5432
+ template: lastTemplate
5433
+ });
5434
+ let lastSnapshotId = snapshotId;
5435
+ while (templates.length > 0) {
5436
+ const template = templates.pop();
5437
+ template.raw.snapshotId = lastSnapshotId;
5438
+ const { snapshotId: snapshotId2 } = await snapshots.ensure({
5439
+ template
5440
+ });
5441
+ lastSnapshotId = snapshotId2;
5442
+ lastTemplate = template;
5443
+ }
5444
+ return lastTemplate;
5184
5445
  }
5185
5446
 
5186
5447
  class Freestyle {
@@ -5255,6 +5516,7 @@ exports.Identity = Identity;
5255
5516
  exports.Vm = Vm;
5256
5517
  exports.VmBuilder = VmBuilder;
5257
5518
  exports.VmService = VmService;
5519
+ exports.VmSpec = VmSpec;
5258
5520
  exports.VmTemplate = VmTemplate;
5259
5521
  exports.VmWith = VmWith;
5260
5522
  exports.VmWithInstance = VmWithInstance;
package/index.d.cts CHANGED
@@ -10439,7 +10439,9 @@ declare class RunsNamespace {
10439
10439
  */
10440
10440
  create<T = any>({ code, ...config }: {
10441
10441
  code: string;
10442
- } & PostExecuteV2ScriptRequestBody["config"]): Promise<ResponsePostExecuteV2Script200>;
10442
+ } & PostExecuteV2ScriptRequestBody["config"]): Promise<ResponsePostExecuteV2Script200 & {
10443
+ result: T;
10444
+ }>;
10443
10445
  /**
10444
10446
  * List execution runs.
10445
10447
  */
@@ -10904,6 +10906,42 @@ declare abstract class VmWith<TInstance extends VmWithInstance = VmWithInstance>
10904
10906
  * @returns The transformed VM configuration with this component's changes applied
10905
10907
  */
10906
10908
  abstract configure(existingConfig: CreateVmOptions): CreateVmOptions | Promise<CreateVmOptions>;
10909
+ /**
10910
+ * Configure the spec that this VmWith is directly attached to.
10911
+ * Override this to modify the parent spec's configuration.
10912
+ *
10913
+ * @param spec - The VmSpec this VmWith is attached to
10914
+ * @returns The transformed VmSpec with this component's changes applied
10915
+ */
10916
+ configureSpec?(spec: VmSpec): VmSpec | Promise<VmSpec>;
10917
+ /**
10918
+ * Configure the snapshot spec inside the current spec.
10919
+ * If no snapshot spec exists, one will be created.
10920
+ * Override this to modify the inner snapshot layer's configuration.
10921
+ *
10922
+ * @param spec - The snapshot VmSpec (created if it doesn't exist)
10923
+ * @returns The transformed snapshot VmSpec with this component's changes applied
10924
+ */
10925
+ configureSnapshotSpec?(spec: VmSpec): VmSpec | Promise<VmSpec>;
10926
+ /**
10927
+ * @deprecated Use configureSpec instead
10928
+ * Configure the template that this VmWith is directly attached to.
10929
+ * Override this to modify the parent template's configuration.
10930
+ *
10931
+ * @param template - The VmTemplate this VmWith is attached to
10932
+ * @returns The transformed VmTemplate with this component's changes applied
10933
+ */
10934
+ configureTemplate?(template: VmTemplate): VmTemplate | Promise<VmTemplate>;
10935
+ /**
10936
+ * @deprecated Use configureSnapshotSpec instead
10937
+ * Configure the nested template inside the current template.
10938
+ * If no nested template exists, one will be created.
10939
+ * Override this to modify the inner template layer's configuration.
10940
+ *
10941
+ * @param template - The nested VmTemplate (created if it doesn't exist)
10942
+ * @returns The transformed nested VmTemplate with this component's changes applied
10943
+ */
10944
+ configureNestedTemplate?(template: VmTemplate): VmTemplate | Promise<VmTemplate>;
10907
10945
  /**
10908
10946
  * Create an instance of this component that will be attached to the VM.
10909
10947
  * Override this to provide custom interaction methods for your component.
@@ -10916,6 +10954,20 @@ declare abstract class VmWith<TInstance extends VmWithInstance = VmWithInstance>
10916
10954
  * Uses the same merging strategy as the main compose function.
10917
10955
  */
10918
10956
  protected compose(...configs: CreateVmOptions[]): CreateVmOptions;
10957
+ /**
10958
+ * Helper method to compose multiple VmSpecs together.
10959
+ * Uses the same merging strategy as the main compose function.
10960
+ * Useful in configureSpec and configureSnapshotSpec methods.
10961
+ */
10962
+ protected composeSpecs(...specs: VmSpec[]): VmSpec;
10963
+ /**
10964
+ * @deprecated Use composeSpecs instead
10965
+ * Helper method to compose multiple VmTemplates together.
10966
+ * Uses the same merging strategy as the main compose function.
10967
+ * Useful in configureTemplate and configureNestedTemplate methods.
10968
+ */
10969
+ protected composeTemplates(...templates: VmTemplate[]): VmTemplate;
10970
+ configureReturnedVm(vm: Vm, template: VmTemplate): Promise<void>;
10919
10971
  }
10920
10972
  declare const VmService: typeof VmWith;
10921
10973
  declare const VmBuilder: typeof VmWith;
@@ -11032,16 +11084,35 @@ declare class Vm {
11032
11084
  rootfsSizeGb: number;
11033
11085
  vcpuCount: number;
11034
11086
  }): Promise<ResponsePostV1VmsIdResize200>;
11087
+ /**
11088
+ * Create a VM instance by ID without making an api call.
11089
+ */
11090
+ ref({ vmId }: {
11091
+ vmId: string;
11092
+ }): Vm;
11093
+ delete({ vmId }: {
11094
+ vmId: string;
11095
+ }): Promise<ResponseDeleteV1VmsVmId200>;
11035
11096
  }
11036
- type CreateVmOptions = Exclude<PostV1VmsRequestBody, "template"> & {
11037
- template?: VmTemplate | Exclude<PostV1VmsRequestBody, "template">["template"];
11097
+ type TemplateOptions = CreateSnapshotRequest["template"] & {
11098
+ template?: VmTemplate<Record<string, VmWith>>;
11038
11099
  };
11039
- declare class VmSnapshotsNamespace {
11040
- private apiClient;
11041
- constructor(apiClient: ApiClient);
11042
- ensure(options: Exclude<PostV1VmsSnapshotsRequestBody, "template"> & {
11043
- template?: VmTemplate;
11044
- }): Promise<ResponsePostV1VmsSnapshots200>;
11100
+ declare class VmTemplate<T extends Record<string, VmWith> = Record<string, VmWith>> {
11101
+ raw: TemplateOptions;
11102
+ readonly with: T;
11103
+ constructor(template: TemplateOptions & {
11104
+ with?: T;
11105
+ });
11106
+ }
11107
+ type VmSpecOptions = TemplateOptions & {
11108
+ snapshot?: VmSpec;
11109
+ };
11110
+ declare class VmSpec<T extends Record<string, VmWith> = Record<string, VmWith>> {
11111
+ raw: VmSpecOptions;
11112
+ readonly with: T;
11113
+ constructor(spec: VmSpecOptions & {
11114
+ with?: T;
11115
+ });
11045
11116
  }
11046
11117
  declare class VmsNamespace {
11047
11118
  private apiClient;
@@ -11054,6 +11125,8 @@ declare class VmsNamespace {
11054
11125
  */
11055
11126
  create<T extends Record<string, VmWith>>(options?: CreateVmOptions & {
11056
11127
  with?: T;
11128
+ template?: VmTemplate<T>;
11129
+ spec?: VmSpec<T>;
11057
11130
  }): Promise<ResponsePostV1Vms200 & {
11058
11131
  vmId: string;
11059
11132
  vm: Vm & {
@@ -11066,6 +11139,21 @@ declare class VmsNamespace {
11066
11139
  * @returns List of VMs with their states and metadata
11067
11140
  */
11068
11141
  list(): Promise<ResponseGetV1Vms200>;
11142
+ /**
11143
+ * Get VM by ID.
11144
+ * @param vmId The ID of the VM
11145
+ * @param allowedSpecs Array of allowed specs (currently only 1 supported, always uses the first)
11146
+ * @returns The VM instance configured with builders from the spec
11147
+ */
11148
+ get<T extends Record<string, VmWith>>({ vmId, allowedSpecs, }: {
11149
+ vmId: string;
11150
+ allowedSpecs?: [VmSpec<T>];
11151
+ }): Promise<{
11152
+ vm: Vm & {
11153
+ [K in keyof T]: ReturnType<T[K]["createInstance"]>;
11154
+ };
11155
+ spec?: VmSpec<T>;
11156
+ }>;
11069
11157
  /**
11070
11158
  * Create a VM instance by ID without making an api call.
11071
11159
  */
@@ -11076,13 +11164,16 @@ declare class VmsNamespace {
11076
11164
  vmId: string;
11077
11165
  }): Promise<ResponseDeleteV1VmsVmId200>;
11078
11166
  }
11079
- type TemplateOptions = CreateSnapshotRequest["template"] & {
11080
- template?: VmTemplate;
11081
- };
11082
- declare class VmTemplate {
11083
- raw: TemplateOptions;
11084
- constructor(template: TemplateOptions);
11167
+ declare class VmSnapshotsNamespace {
11168
+ private apiClient;
11169
+ constructor(apiClient: ApiClient);
11170
+ ensure(options: Exclude<PostV1VmsSnapshotsRequestBody, "template"> & {
11171
+ template?: VmTemplate;
11172
+ }): Promise<ResponsePostV1VmsSnapshots200>;
11085
11173
  }
11174
+ type CreateVmOptions = Omit<PostV1VmsRequestBody, "template"> & {
11175
+ template?: VmTemplate | Exclude<PostV1VmsRequestBody, "template">["template"];
11176
+ };
11086
11177
 
11087
11178
  type FreestyleOptions = ApiClientConfig;
11088
11179
  declare class Freestyle {
@@ -11105,5 +11196,5 @@ declare class Freestyle {
11105
11196
  */
11106
11197
  declare const freestyle: Freestyle;
11107
11198
 
11108
- export { Deployment, errors as Errors, FileSystem, Freestyle, GitRepo, Identity, requests as Requests, responses as Responses, Vm, VmBuilder, VmService, VmTemplate, VmWith, VmWithInstance, freestyle };
11199
+ export { Deployment, errors as Errors, FileSystem, Freestyle, GitRepo, Identity, requests as Requests, responses as Responses, Vm, VmBuilder, VmService, VmSpec, VmTemplate, VmWith, VmWithInstance, freestyle };
11109
11200
  export type { CreateVmOptions, FreestyleOptions };
package/index.d.mts CHANGED
@@ -10439,7 +10439,9 @@ declare class RunsNamespace {
10439
10439
  */
10440
10440
  create<T = any>({ code, ...config }: {
10441
10441
  code: string;
10442
- } & PostExecuteV2ScriptRequestBody["config"]): Promise<ResponsePostExecuteV2Script200>;
10442
+ } & PostExecuteV2ScriptRequestBody["config"]): Promise<ResponsePostExecuteV2Script200 & {
10443
+ result: T;
10444
+ }>;
10443
10445
  /**
10444
10446
  * List execution runs.
10445
10447
  */
@@ -10904,6 +10906,42 @@ declare abstract class VmWith<TInstance extends VmWithInstance = VmWithInstance>
10904
10906
  * @returns The transformed VM configuration with this component's changes applied
10905
10907
  */
10906
10908
  abstract configure(existingConfig: CreateVmOptions): CreateVmOptions | Promise<CreateVmOptions>;
10909
+ /**
10910
+ * Configure the spec that this VmWith is directly attached to.
10911
+ * Override this to modify the parent spec's configuration.
10912
+ *
10913
+ * @param spec - The VmSpec this VmWith is attached to
10914
+ * @returns The transformed VmSpec with this component's changes applied
10915
+ */
10916
+ configureSpec?(spec: VmSpec): VmSpec | Promise<VmSpec>;
10917
+ /**
10918
+ * Configure the snapshot spec inside the current spec.
10919
+ * If no snapshot spec exists, one will be created.
10920
+ * Override this to modify the inner snapshot layer's configuration.
10921
+ *
10922
+ * @param spec - The snapshot VmSpec (created if it doesn't exist)
10923
+ * @returns The transformed snapshot VmSpec with this component's changes applied
10924
+ */
10925
+ configureSnapshotSpec?(spec: VmSpec): VmSpec | Promise<VmSpec>;
10926
+ /**
10927
+ * @deprecated Use configureSpec instead
10928
+ * Configure the template that this VmWith is directly attached to.
10929
+ * Override this to modify the parent template's configuration.
10930
+ *
10931
+ * @param template - The VmTemplate this VmWith is attached to
10932
+ * @returns The transformed VmTemplate with this component's changes applied
10933
+ */
10934
+ configureTemplate?(template: VmTemplate): VmTemplate | Promise<VmTemplate>;
10935
+ /**
10936
+ * @deprecated Use configureSnapshotSpec instead
10937
+ * Configure the nested template inside the current template.
10938
+ * If no nested template exists, one will be created.
10939
+ * Override this to modify the inner template layer's configuration.
10940
+ *
10941
+ * @param template - The nested VmTemplate (created if it doesn't exist)
10942
+ * @returns The transformed nested VmTemplate with this component's changes applied
10943
+ */
10944
+ configureNestedTemplate?(template: VmTemplate): VmTemplate | Promise<VmTemplate>;
10907
10945
  /**
10908
10946
  * Create an instance of this component that will be attached to the VM.
10909
10947
  * Override this to provide custom interaction methods for your component.
@@ -10916,6 +10954,20 @@ declare abstract class VmWith<TInstance extends VmWithInstance = VmWithInstance>
10916
10954
  * Uses the same merging strategy as the main compose function.
10917
10955
  */
10918
10956
  protected compose(...configs: CreateVmOptions[]): CreateVmOptions;
10957
+ /**
10958
+ * Helper method to compose multiple VmSpecs together.
10959
+ * Uses the same merging strategy as the main compose function.
10960
+ * Useful in configureSpec and configureSnapshotSpec methods.
10961
+ */
10962
+ protected composeSpecs(...specs: VmSpec[]): VmSpec;
10963
+ /**
10964
+ * @deprecated Use composeSpecs instead
10965
+ * Helper method to compose multiple VmTemplates together.
10966
+ * Uses the same merging strategy as the main compose function.
10967
+ * Useful in configureTemplate and configureNestedTemplate methods.
10968
+ */
10969
+ protected composeTemplates(...templates: VmTemplate[]): VmTemplate;
10970
+ configureReturnedVm(vm: Vm, template: VmTemplate): Promise<void>;
10919
10971
  }
10920
10972
  declare const VmService: typeof VmWith;
10921
10973
  declare const VmBuilder: typeof VmWith;
@@ -11032,16 +11084,35 @@ declare class Vm {
11032
11084
  rootfsSizeGb: number;
11033
11085
  vcpuCount: number;
11034
11086
  }): Promise<ResponsePostV1VmsIdResize200>;
11087
+ /**
11088
+ * Create a VM instance by ID without making an api call.
11089
+ */
11090
+ ref({ vmId }: {
11091
+ vmId: string;
11092
+ }): Vm;
11093
+ delete({ vmId }: {
11094
+ vmId: string;
11095
+ }): Promise<ResponseDeleteV1VmsVmId200>;
11035
11096
  }
11036
- type CreateVmOptions = Exclude<PostV1VmsRequestBody, "template"> & {
11037
- template?: VmTemplate | Exclude<PostV1VmsRequestBody, "template">["template"];
11097
+ type TemplateOptions = CreateSnapshotRequest["template"] & {
11098
+ template?: VmTemplate<Record<string, VmWith>>;
11038
11099
  };
11039
- declare class VmSnapshotsNamespace {
11040
- private apiClient;
11041
- constructor(apiClient: ApiClient);
11042
- ensure(options: Exclude<PostV1VmsSnapshotsRequestBody, "template"> & {
11043
- template?: VmTemplate;
11044
- }): Promise<ResponsePostV1VmsSnapshots200>;
11100
+ declare class VmTemplate<T extends Record<string, VmWith> = Record<string, VmWith>> {
11101
+ raw: TemplateOptions;
11102
+ readonly with: T;
11103
+ constructor(template: TemplateOptions & {
11104
+ with?: T;
11105
+ });
11106
+ }
11107
+ type VmSpecOptions = TemplateOptions & {
11108
+ snapshot?: VmSpec;
11109
+ };
11110
+ declare class VmSpec<T extends Record<string, VmWith> = Record<string, VmWith>> {
11111
+ raw: VmSpecOptions;
11112
+ readonly with: T;
11113
+ constructor(spec: VmSpecOptions & {
11114
+ with?: T;
11115
+ });
11045
11116
  }
11046
11117
  declare class VmsNamespace {
11047
11118
  private apiClient;
@@ -11054,6 +11125,8 @@ declare class VmsNamespace {
11054
11125
  */
11055
11126
  create<T extends Record<string, VmWith>>(options?: CreateVmOptions & {
11056
11127
  with?: T;
11128
+ template?: VmTemplate<T>;
11129
+ spec?: VmSpec<T>;
11057
11130
  }): Promise<ResponsePostV1Vms200 & {
11058
11131
  vmId: string;
11059
11132
  vm: Vm & {
@@ -11066,6 +11139,21 @@ declare class VmsNamespace {
11066
11139
  * @returns List of VMs with their states and metadata
11067
11140
  */
11068
11141
  list(): Promise<ResponseGetV1Vms200>;
11142
+ /**
11143
+ * Get VM by ID.
11144
+ * @param vmId The ID of the VM
11145
+ * @param allowedSpecs Array of allowed specs (currently only 1 supported, always uses the first)
11146
+ * @returns The VM instance configured with builders from the spec
11147
+ */
11148
+ get<T extends Record<string, VmWith>>({ vmId, allowedSpecs, }: {
11149
+ vmId: string;
11150
+ allowedSpecs?: [VmSpec<T>];
11151
+ }): Promise<{
11152
+ vm: Vm & {
11153
+ [K in keyof T]: ReturnType<T[K]["createInstance"]>;
11154
+ };
11155
+ spec?: VmSpec<T>;
11156
+ }>;
11069
11157
  /**
11070
11158
  * Create a VM instance by ID without making an api call.
11071
11159
  */
@@ -11076,13 +11164,16 @@ declare class VmsNamespace {
11076
11164
  vmId: string;
11077
11165
  }): Promise<ResponseDeleteV1VmsVmId200>;
11078
11166
  }
11079
- type TemplateOptions = CreateSnapshotRequest["template"] & {
11080
- template?: VmTemplate;
11081
- };
11082
- declare class VmTemplate {
11083
- raw: TemplateOptions;
11084
- constructor(template: TemplateOptions);
11167
+ declare class VmSnapshotsNamespace {
11168
+ private apiClient;
11169
+ constructor(apiClient: ApiClient);
11170
+ ensure(options: Exclude<PostV1VmsSnapshotsRequestBody, "template"> & {
11171
+ template?: VmTemplate;
11172
+ }): Promise<ResponsePostV1VmsSnapshots200>;
11085
11173
  }
11174
+ type CreateVmOptions = Omit<PostV1VmsRequestBody, "template"> & {
11175
+ template?: VmTemplate | Exclude<PostV1VmsRequestBody, "template">["template"];
11176
+ };
11086
11177
 
11087
11178
  type FreestyleOptions = ApiClientConfig;
11088
11179
  declare class Freestyle {
@@ -11105,5 +11196,5 @@ declare class Freestyle {
11105
11196
  */
11106
11197
  declare const freestyle: Freestyle;
11107
11198
 
11108
- export { Deployment, errors as Errors, FileSystem, Freestyle, GitRepo, Identity, requests as Requests, responses as Responses, Vm, VmBuilder, VmService, VmTemplate, VmWith, VmWithInstance, freestyle };
11199
+ export { Deployment, errors as Errors, FileSystem, Freestyle, GitRepo, Identity, requests as Requests, responses as Responses, Vm, VmBuilder, VmService, VmSpec, VmTemplate, VmWith, VmWithInstance, freestyle };
11109
11200
  export type { CreateVmOptions, FreestyleOptions };
package/index.mjs CHANGED
@@ -4622,6 +4622,32 @@ class VmWith {
4622
4622
  compose(...configs) {
4623
4623
  return composeCreateVmOptions(configs);
4624
4624
  }
4625
+ /**
4626
+ * Helper method to compose multiple VmSpecs together.
4627
+ * Uses the same merging strategy as the main compose function.
4628
+ * Useful in configureSpec and configureSnapshotSpec methods.
4629
+ */
4630
+ composeSpecs(...specs) {
4631
+ return composeVmSpecs(specs);
4632
+ }
4633
+ /**
4634
+ * @deprecated Use composeSpecs instead
4635
+ * Helper method to compose multiple VmTemplates together.
4636
+ * Uses the same merging strategy as the main compose function.
4637
+ * Useful in configureTemplate and configureNestedTemplate methods.
4638
+ */
4639
+ composeTemplates(...templates) {
4640
+ return composeVmTemplates(templates);
4641
+ }
4642
+ // Add a new method to VmWith to configure returned VMs with helpers
4643
+ async configureReturnedVm(vm, template) {
4644
+ if (template.raw) {
4645
+ await this.configure(template.raw);
4646
+ const instance = this.createInstance();
4647
+ instance._init(vm);
4648
+ this.instance = instance;
4649
+ }
4650
+ }
4625
4651
  }
4626
4652
  const VmService = VmWith;
4627
4653
  const VmBuilder = VmWith;
@@ -4873,6 +4899,73 @@ function composeCreateVmOptions(arr) {
4873
4899
  }
4874
4900
  return result;
4875
4901
  }
4902
+ function composeVmTemplates(templates) {
4903
+ if (templates.length === 0) {
4904
+ return new VmTemplate({});
4905
+ }
4906
+ if (templates.length === 1) {
4907
+ return templates[0];
4908
+ }
4909
+ const rawConfigs = templates.map((t) => t.raw);
4910
+ const composedRaw = composeCreateVmOptions(
4911
+ rawConfigs
4912
+ );
4913
+ const mergedWith = {};
4914
+ for (const template of templates) {
4915
+ for (const key in template.with) {
4916
+ const builder = template.with[key];
4917
+ if (builder) {
4918
+ mergedWith[key] = builder;
4919
+ }
4920
+ }
4921
+ }
4922
+ const nestedTemplates = templates.map((t) => t.raw.template).filter((t) => t instanceof VmTemplate);
4923
+ let nestedTemplate;
4924
+ if (nestedTemplates.length > 0) {
4925
+ nestedTemplate = composeVmTemplates(nestedTemplates);
4926
+ }
4927
+ const result = new VmTemplate({
4928
+ ...composedRaw,
4929
+ template: nestedTemplate,
4930
+ with: mergedWith
4931
+ });
4932
+ return result;
4933
+ }
4934
+ function composeVmSpecs(specs) {
4935
+ if (specs.length === 0) {
4936
+ return new VmSpec({});
4937
+ }
4938
+ if (specs.length === 1) {
4939
+ return specs[0];
4940
+ }
4941
+ const rawConfigs = specs.map((s) => {
4942
+ const { snapshot, ...rest } = s.raw;
4943
+ return rest;
4944
+ });
4945
+ const composedRaw = composeCreateVmOptions(
4946
+ rawConfigs
4947
+ );
4948
+ const mergedWith = {};
4949
+ for (const spec of specs) {
4950
+ for (const key in spec.with) {
4951
+ const builder = spec.with[key];
4952
+ if (builder) {
4953
+ mergedWith[key] = builder;
4954
+ }
4955
+ }
4956
+ }
4957
+ const nestedSpecs = specs.map((s) => s.raw.snapshot).filter((s) => s instanceof VmSpec);
4958
+ let nestedSpec;
4959
+ if (nestedSpecs.length > 0) {
4960
+ nestedSpec = composeVmSpecs(nestedSpecs);
4961
+ }
4962
+ const result = new VmSpec({
4963
+ ...composedRaw,
4964
+ snapshot: nestedSpec,
4965
+ with: mergedWith
4966
+ });
4967
+ return result;
4968
+ }
4876
4969
 
4877
4970
  class VmTerminals {
4878
4971
  constructor(vmId, client) {
@@ -5052,51 +5145,121 @@ class Vm {
5052
5145
  return this.apiClient.post("/v1/vms/{id}/resize", {
5053
5146
  params: { id: this.vmId },
5054
5147
  body: { memSizeGb, rootfsSizeGb, vcpuCount }
5148
+ // Temporarily cast to any
5055
5149
  });
5056
5150
  }
5057
- }
5058
- class VmSnapshotsNamespace {
5059
- constructor(apiClient) {
5060
- this.apiClient = apiClient;
5151
+ /**
5152
+ * Create a VM instance by ID without making an api call.
5153
+ */
5154
+ ref({ vmId }) {
5155
+ return new Vm(vmId, this.apiClient);
5061
5156
  }
5062
- async ensure(options) {
5063
- if (options.template.raw.template instanceof VmTemplate) {
5064
- options.template = await ensureNestedTemplates(options.template, this);
5065
- }
5066
- return this.apiClient.post("/v1/vms/snapshots", {
5067
- body: {
5068
- ...options,
5069
- // @ts-ignore
5070
- template: options.template?.raw
5071
- }
5157
+ async delete({ vmId }) {
5158
+ return this.apiClient.delete("/v1/vms/{vm_id}", {
5159
+ params: { vm_id: vmId }
5072
5160
  });
5073
5161
  }
5074
5162
  }
5075
- async function ensureNestedTemplates(template, snapshots) {
5076
- let templates = [template];
5077
- while (templates.at(-1)?.raw.template instanceof VmTemplate) {
5078
- let innerTemplate = templates.at(-1).raw.template;
5079
- templates.at(-1).raw.template = void 0;
5080
- templates.push(innerTemplate);
5163
+ class VmTemplate {
5164
+ raw;
5165
+ with;
5166
+ constructor(template) {
5167
+ const { with: withBuilders, ...rest } = template;
5168
+ this.raw = rest;
5169
+ this.with = withBuilders ?? {};
5081
5170
  }
5082
- return await layerTemplates(templates, snapshots);
5083
5171
  }
5084
- async function layerTemplates(templates, snapshots) {
5085
- let lastTemplate = templates.pop();
5086
- const { snapshotId } = await snapshots.ensure({
5087
- template: lastTemplate
5172
+ class VmSpec {
5173
+ raw;
5174
+ with;
5175
+ constructor(spec) {
5176
+ const { with: withBuilders, ...rest } = spec;
5177
+ this.raw = rest;
5178
+ this.with = withBuilders ?? {};
5179
+ }
5180
+ }
5181
+ async function convertSpecSnapshotsToTemplates(spec, processBuilders = true) {
5182
+ if (!spec.raw.snapshot) {
5183
+ return void 0;
5184
+ }
5185
+ let innerSpec = spec.raw.snapshot;
5186
+ if (processBuilders) {
5187
+ innerSpec = await processSpecTree(innerSpec);
5188
+ }
5189
+ const { snapshot: nestedSnapshot, ...innerRaw } = innerSpec.raw;
5190
+ let nestedTemplate;
5191
+ if (nestedSnapshot instanceof VmSpec) {
5192
+ nestedTemplate = await convertSpecSnapshotsToTemplates(innerSpec, false);
5193
+ }
5194
+ return new VmTemplate({
5195
+ ...innerRaw,
5196
+ template: nestedTemplate,
5197
+ with: innerSpec.with
5088
5198
  });
5089
- let lastSnapshotId = snapshotId;
5090
- while (templates.length > 0) {
5091
- const template = templates.pop();
5092
- template.raw.snapshotId = lastSnapshotId;
5093
- const { snapshotId: snapshotId2 } = await snapshots.ensure({
5094
- template
5095
- });
5096
- lastSnapshotId = snapshotId2;
5097
- lastTemplate = template;
5199
+ }
5200
+ async function processOuterSpecBuilders(spec) {
5201
+ for (const key in spec.with) {
5202
+ const builder = spec.with[key];
5203
+ if (builder) {
5204
+ if (builder.configureSpec) {
5205
+ spec = await builder.configureSpec(spec);
5206
+ }
5207
+ if (builder.configureSnapshotSpec) {
5208
+ let snapshotSpec;
5209
+ if (spec.raw.snapshot instanceof VmSpec) {
5210
+ snapshotSpec = spec.raw.snapshot;
5211
+ } else {
5212
+ snapshotSpec = new VmSpec({});
5213
+ spec.raw.snapshot = snapshotSpec;
5214
+ }
5215
+ spec.raw.snapshot = await builder.configureSnapshotSpec(snapshotSpec);
5216
+ }
5217
+ }
5098
5218
  }
5099
- return lastTemplate;
5219
+ return spec;
5220
+ }
5221
+ async function processSpecTree(spec) {
5222
+ for (const key in spec.with) {
5223
+ const builder = spec.with[key];
5224
+ if (builder) {
5225
+ if (builder.configureSpec) {
5226
+ spec = await builder.configureSpec(spec);
5227
+ }
5228
+ if (builder.configureSnapshotSpec) {
5229
+ let snapshotSpec;
5230
+ if (spec.raw.snapshot instanceof VmSpec) {
5231
+ snapshotSpec = spec.raw.snapshot;
5232
+ } else {
5233
+ snapshotSpec = new VmSpec({});
5234
+ spec.raw.snapshot = snapshotSpec;
5235
+ }
5236
+ spec.raw.snapshot = await builder.configureSnapshotSpec(snapshotSpec);
5237
+ }
5238
+ }
5239
+ }
5240
+ if (spec.raw.snapshot instanceof VmSpec) {
5241
+ spec.raw.snapshot = await processSpecTree(spec.raw.snapshot);
5242
+ }
5243
+ return spec;
5244
+ }
5245
+ function collectSpecBuilders(spec) {
5246
+ const builders = {};
5247
+ if (spec.with) {
5248
+ for (const [key, builder] of Object.entries(spec.with)) {
5249
+ if (builder) {
5250
+ builders[key] = builder;
5251
+ }
5252
+ }
5253
+ }
5254
+ if (spec.raw.snapshot instanceof VmSpec) {
5255
+ const nestedBuilders = collectSpecBuilders(spec.raw.snapshot);
5256
+ for (const [key, builder] of Object.entries(nestedBuilders)) {
5257
+ if (!builders[key] && builder) {
5258
+ builders[key] = builder;
5259
+ }
5260
+ }
5261
+ }
5262
+ return builders;
5100
5263
  }
5101
5264
  class VmsNamespace {
5102
5265
  constructor(apiClient) {
@@ -5110,9 +5273,32 @@ class VmsNamespace {
5110
5273
  * @returns A VM instance representing the created VM
5111
5274
  */
5112
5275
  async create(options = {}) {
5113
- const builders = options.with || {};
5276
+ if (options.spec instanceof VmSpec) {
5277
+ let spec = options.spec;
5278
+ spec = await processOuterSpecBuilders(spec);
5279
+ const { snapshot, ...outerSpecOptions } = spec.raw;
5280
+ const innerTemplate = await convertSpecSnapshotsToTemplates(spec);
5281
+ if (innerTemplate) {
5282
+ options.template = innerTemplate;
5283
+ }
5284
+ const mergedOptions = composeCreateVmOptions([
5285
+ outerSpecOptions,
5286
+ options
5287
+ ]);
5288
+ options = {
5289
+ ...options,
5290
+ ...mergedOptions
5291
+ };
5292
+ if (!options.with && spec.with) {
5293
+ options.with = spec.with;
5294
+ }
5295
+ }
5296
+ const builders = options.with || options.template?.with || {};
5114
5297
  const { with: _, ...baseConfig } = options;
5115
5298
  let config = baseConfig;
5299
+ if (config.template instanceof VmTemplate) {
5300
+ config.template = await processTemplateTree(config.template);
5301
+ }
5116
5302
  if (config.template instanceof VmTemplate) {
5117
5303
  config.template = await ensureNestedTemplates(
5118
5304
  config.template,
@@ -5138,12 +5324,6 @@ class VmsNamespace {
5138
5324
  const builder = builders[key];
5139
5325
  if (builder) {
5140
5326
  const instance = builder.createInstance();
5141
- builder.createInstance = () => {
5142
- throw new Error(
5143
- `Attempted to create multiple instances from the same VmWith ${key}: ${builder.constructor.name}`
5144
- );
5145
- };
5146
- builder.instance = instance;
5147
5327
  instance._init(vm);
5148
5328
  vm[key] = instance;
5149
5329
  }
@@ -5162,6 +5342,30 @@ class VmsNamespace {
5162
5342
  async list() {
5163
5343
  return this.apiClient.get("/v1/vms", void 0);
5164
5344
  }
5345
+ /**
5346
+ * Get VM by ID.
5347
+ * @param vmId The ID of the VM
5348
+ * @param allowedSpecs Array of allowed specs (currently only 1 supported, always uses the first)
5349
+ * @returns The VM instance configured with builders from the spec
5350
+ */
5351
+ async get({
5352
+ vmId,
5353
+ allowedSpecs
5354
+ }) {
5355
+ const vm = new Vm(vmId, this.apiClient);
5356
+ const spec = allowedSpecs?.[0];
5357
+ if (spec) {
5358
+ const allBuilders = collectSpecBuilders(spec);
5359
+ for (const [key, builder] of Object.entries(allBuilders)) {
5360
+ if (builder) {
5361
+ const instance = builder.createInstance();
5362
+ instance._init(vm);
5363
+ vm[key] = instance;
5364
+ }
5365
+ }
5366
+ }
5367
+ return { vm, spec };
5368
+ }
5165
5369
  /**
5166
5370
  * Create a VM instance by ID without making an api call.
5167
5371
  */
@@ -5174,11 +5378,68 @@ class VmsNamespace {
5174
5378
  });
5175
5379
  }
5176
5380
  }
5177
- class VmTemplate {
5178
- raw;
5179
- constructor(template) {
5180
- this.raw = template;
5381
+ class VmSnapshotsNamespace {
5382
+ constructor(apiClient) {
5383
+ this.apiClient = apiClient;
5384
+ }
5385
+ async ensure(options) {
5386
+ return this.apiClient.post("/v1/vms/snapshots", {
5387
+ body: {
5388
+ ...options,
5389
+ template: options.template?.raw
5390
+ }
5391
+ });
5392
+ }
5393
+ }
5394
+ async function processTemplateTree(template) {
5395
+ for (const key in template.with) {
5396
+ const builder = template.with[key];
5397
+ if (builder) {
5398
+ if (builder.configureTemplate) {
5399
+ template = await builder.configureTemplate(template);
5400
+ }
5401
+ if (builder.configureNestedTemplate) {
5402
+ let nestedTemplate;
5403
+ if (template.raw.template instanceof VmTemplate) {
5404
+ nestedTemplate = template.raw.template;
5405
+ } else {
5406
+ nestedTemplate = new VmTemplate({});
5407
+ template.raw.template = nestedTemplate;
5408
+ }
5409
+ template.raw.template = await builder.configureNestedTemplate(nestedTemplate);
5410
+ }
5411
+ }
5412
+ }
5413
+ if (template.raw.template instanceof VmTemplate) {
5414
+ template.raw.template = await processTemplateTree(template.raw.template);
5181
5415
  }
5416
+ return template;
5417
+ }
5418
+ async function ensureNestedTemplates(template, snapshots) {
5419
+ let templates = [template];
5420
+ while (templates.at(-1)?.raw.template instanceof VmTemplate) {
5421
+ let innerTemplate = templates.at(-1).raw.template;
5422
+ templates.at(-1).raw.template = void 0;
5423
+ templates.push(innerTemplate);
5424
+ }
5425
+ return await layerTemplates(templates, snapshots);
5426
+ }
5427
+ async function layerTemplates(templates, snapshots) {
5428
+ let lastTemplate = templates.pop();
5429
+ const { snapshotId } = await snapshots.ensure({
5430
+ template: lastTemplate
5431
+ });
5432
+ let lastSnapshotId = snapshotId;
5433
+ while (templates.length > 0) {
5434
+ const template = templates.pop();
5435
+ template.raw.snapshotId = lastSnapshotId;
5436
+ const { snapshotId: snapshotId2 } = await snapshots.ensure({
5437
+ template
5438
+ });
5439
+ lastSnapshotId = snapshotId2;
5440
+ lastTemplate = template;
5441
+ }
5442
+ return lastTemplate;
5182
5443
  }
5183
5444
 
5184
5445
  class Freestyle {
@@ -5244,4 +5505,4 @@ function createLazyFreestyle(options = {}) {
5244
5505
  }
5245
5506
  const freestyle = createLazyFreestyle();
5246
5507
 
5247
- export { Deployment, errors as Errors, FileSystem, Freestyle, GitRepo, Identity, Vm, VmBuilder, VmService, VmTemplate, VmWith, VmWithInstance, freestyle };
5508
+ export { Deployment, errors as Errors, FileSystem, Freestyle, GitRepo, Identity, Vm, VmBuilder, VmService, VmSpec, VmTemplate, VmWith, VmWithInstance, freestyle };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freestyle-sandboxes",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "require": {