freestyle-sandboxes 0.1.6 → 0.1.8

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 +313 -47
  2. package/index.d.cts +112 -17
  3. package/index.d.mts +112 -17
  4. package/index.mjs +313 -48
  5. package/package.json +1 -1
package/index.cjs CHANGED
@@ -4624,6 +4624,36 @@ 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
+ /**
4645
+ * @deprecated This method is no longer needed. Use vms.get with allowedSpecs instead.
4646
+ */
4647
+ async configureReturnedVm(vm, template) {
4648
+ if (template.raw) {
4649
+ if (this.configure) {
4650
+ await this.configure(template.raw);
4651
+ }
4652
+ const instance = this.createInstance();
4653
+ instance._init(vm);
4654
+ this.instance = instance;
4655
+ }
4656
+ }
4627
4657
  }
4628
4658
  const VmService = VmWith;
4629
4659
  const VmBuilder = VmWith;
@@ -4875,6 +4905,73 @@ function composeCreateVmOptions(arr) {
4875
4905
  }
4876
4906
  return result;
4877
4907
  }
4908
+ function composeVmTemplates(templates) {
4909
+ if (templates.length === 0) {
4910
+ return new VmTemplate({});
4911
+ }
4912
+ if (templates.length === 1) {
4913
+ return templates[0];
4914
+ }
4915
+ const rawConfigs = templates.map((t) => t.raw);
4916
+ const composedRaw = composeCreateVmOptions(
4917
+ rawConfigs
4918
+ );
4919
+ const mergedWith = {};
4920
+ for (const template of templates) {
4921
+ for (const key in template.with) {
4922
+ const builder = template.with[key];
4923
+ if (builder) {
4924
+ mergedWith[key] = builder;
4925
+ }
4926
+ }
4927
+ }
4928
+ const nestedTemplates = templates.map((t) => t.raw.template).filter((t) => t instanceof VmTemplate);
4929
+ let nestedTemplate;
4930
+ if (nestedTemplates.length > 0) {
4931
+ nestedTemplate = composeVmTemplates(nestedTemplates);
4932
+ }
4933
+ const result = new VmTemplate({
4934
+ ...composedRaw,
4935
+ template: nestedTemplate,
4936
+ with: mergedWith
4937
+ });
4938
+ return result;
4939
+ }
4940
+ function composeVmSpecs(specs) {
4941
+ if (specs.length === 0) {
4942
+ return new VmSpec({});
4943
+ }
4944
+ if (specs.length === 1) {
4945
+ return specs[0];
4946
+ }
4947
+ const rawConfigs = specs.map((s) => {
4948
+ const { snapshot, ...rest } = s.raw;
4949
+ return rest;
4950
+ });
4951
+ const composedRaw = composeCreateVmOptions(
4952
+ rawConfigs
4953
+ );
4954
+ const mergedWith = {};
4955
+ for (const spec of specs) {
4956
+ for (const key in spec.with) {
4957
+ const builder = spec.with[key];
4958
+ if (builder) {
4959
+ mergedWith[key] = builder;
4960
+ }
4961
+ }
4962
+ }
4963
+ const nestedSpecs = specs.map((s) => s.raw.snapshot).filter((s) => s instanceof VmSpec);
4964
+ let nestedSpec;
4965
+ if (nestedSpecs.length > 0) {
4966
+ nestedSpec = composeVmSpecs(nestedSpecs);
4967
+ }
4968
+ const result = new VmSpec({
4969
+ ...composedRaw,
4970
+ snapshot: nestedSpec,
4971
+ with: mergedWith
4972
+ });
4973
+ return result;
4974
+ }
4878
4975
 
4879
4976
  class VmTerminals {
4880
4977
  constructor(vmId, client) {
@@ -5054,51 +5151,121 @@ class Vm {
5054
5151
  return this.apiClient.post("/v1/vms/{id}/resize", {
5055
5152
  params: { id: this.vmId },
5056
5153
  body: { memSizeGb, rootfsSizeGb, vcpuCount }
5154
+ // Temporarily cast to any
5057
5155
  });
5058
5156
  }
5059
- }
5060
- class VmSnapshotsNamespace {
5061
- constructor(apiClient) {
5062
- this.apiClient = apiClient;
5157
+ /**
5158
+ * Create a VM instance by ID without making an api call.
5159
+ */
5160
+ ref({ vmId }) {
5161
+ return new Vm(vmId, this.apiClient);
5063
5162
  }
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
- }
5163
+ async delete({ vmId }) {
5164
+ return this.apiClient.delete("/v1/vms/{vm_id}", {
5165
+ params: { vm_id: vmId }
5074
5166
  });
5075
5167
  }
5076
5168
  }
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);
5169
+ class VmTemplate {
5170
+ raw;
5171
+ with;
5172
+ constructor(template) {
5173
+ const { with: withBuilders, ...rest } = template;
5174
+ this.raw = rest;
5175
+ this.with = withBuilders ?? {};
5083
5176
  }
5084
- return await layerTemplates(templates, snapshots);
5085
5177
  }
5086
- async function layerTemplates(templates, snapshots) {
5087
- let lastTemplate = templates.pop();
5088
- const { snapshotId } = await snapshots.ensure({
5089
- template: lastTemplate
5178
+ class VmSpec {
5179
+ raw;
5180
+ with;
5181
+ constructor(spec) {
5182
+ const { with: withBuilders, ...rest } = spec;
5183
+ this.raw = rest;
5184
+ this.with = withBuilders ?? {};
5185
+ }
5186
+ }
5187
+ async function convertSpecSnapshotsToTemplates(spec, processBuilders = true) {
5188
+ if (!spec.raw.snapshot) {
5189
+ return void 0;
5190
+ }
5191
+ let innerSpec = spec.raw.snapshot;
5192
+ if (processBuilders) {
5193
+ innerSpec = await processSpecTree(innerSpec);
5194
+ }
5195
+ const { snapshot: nestedSnapshot, ...innerRaw } = innerSpec.raw;
5196
+ let nestedTemplate;
5197
+ if (nestedSnapshot instanceof VmSpec) {
5198
+ nestedTemplate = await convertSpecSnapshotsToTemplates(innerSpec, false);
5199
+ }
5200
+ return new VmTemplate({
5201
+ ...innerRaw,
5202
+ template: nestedTemplate,
5203
+ with: innerSpec.with
5090
5204
  });
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;
5205
+ }
5206
+ async function processOuterSpecBuilders(spec) {
5207
+ for (const key in spec.with) {
5208
+ const builder = spec.with[key];
5209
+ if (builder) {
5210
+ if (builder.configureSpec) {
5211
+ spec = await builder.configureSpec(spec);
5212
+ }
5213
+ if (builder.configureSnapshotSpec) {
5214
+ let snapshotSpec;
5215
+ if (spec.raw.snapshot instanceof VmSpec) {
5216
+ snapshotSpec = spec.raw.snapshot;
5217
+ } else {
5218
+ snapshotSpec = new VmSpec({});
5219
+ spec.raw.snapshot = snapshotSpec;
5220
+ }
5221
+ spec.raw.snapshot = await builder.configureSnapshotSpec(snapshotSpec);
5222
+ }
5223
+ }
5100
5224
  }
5101
- return lastTemplate;
5225
+ return spec;
5226
+ }
5227
+ async function processSpecTree(spec) {
5228
+ for (const key in spec.with) {
5229
+ const builder = spec.with[key];
5230
+ if (builder) {
5231
+ if (builder.configureSpec) {
5232
+ spec = await builder.configureSpec(spec);
5233
+ }
5234
+ if (builder.configureSnapshotSpec) {
5235
+ let snapshotSpec;
5236
+ if (spec.raw.snapshot instanceof VmSpec) {
5237
+ snapshotSpec = spec.raw.snapshot;
5238
+ } else {
5239
+ snapshotSpec = new VmSpec({});
5240
+ spec.raw.snapshot = snapshotSpec;
5241
+ }
5242
+ spec.raw.snapshot = await builder.configureSnapshotSpec(snapshotSpec);
5243
+ }
5244
+ }
5245
+ }
5246
+ if (spec.raw.snapshot instanceof VmSpec) {
5247
+ spec.raw.snapshot = await processSpecTree(spec.raw.snapshot);
5248
+ }
5249
+ return spec;
5250
+ }
5251
+ function collectSpecBuilders(spec) {
5252
+ const builders = {};
5253
+ if (spec.with) {
5254
+ for (const [key, builder] of Object.entries(spec.with)) {
5255
+ if (builder) {
5256
+ builders[key] = builder;
5257
+ }
5258
+ }
5259
+ }
5260
+ if (spec.raw.snapshot instanceof VmSpec) {
5261
+ const nestedBuilders = collectSpecBuilders(spec.raw.snapshot);
5262
+ for (const [key, builder] of Object.entries(nestedBuilders)) {
5263
+ if (!builders[key] && builder) {
5264
+ builders[key] = builder;
5265
+ }
5266
+ }
5267
+ }
5268
+ return builders;
5102
5269
  }
5103
5270
  class VmsNamespace {
5104
5271
  constructor(apiClient) {
@@ -5112,9 +5279,32 @@ class VmsNamespace {
5112
5279
  * @returns A VM instance representing the created VM
5113
5280
  */
5114
5281
  async create(options = {}) {
5115
- const builders = options.with || {};
5282
+ if (options.spec instanceof VmSpec) {
5283
+ let spec = options.spec;
5284
+ spec = await processOuterSpecBuilders(spec);
5285
+ const { snapshot, ...outerSpecOptions } = spec.raw;
5286
+ const innerTemplate = await convertSpecSnapshotsToTemplates(spec);
5287
+ if (innerTemplate) {
5288
+ options.template = innerTemplate;
5289
+ }
5290
+ const mergedOptions = composeCreateVmOptions([
5291
+ outerSpecOptions,
5292
+ options
5293
+ ]);
5294
+ options = {
5295
+ ...options,
5296
+ ...mergedOptions
5297
+ };
5298
+ if (!options.with && spec.with) {
5299
+ options.with = spec.with;
5300
+ }
5301
+ }
5302
+ const builders = options.with || options.template?.with || {};
5116
5303
  const { with: _, ...baseConfig } = options;
5117
5304
  let config = baseConfig;
5305
+ if (config.template instanceof VmTemplate) {
5306
+ config.template = await processTemplateTree(config.template);
5307
+ }
5118
5308
  if (config.template instanceof VmTemplate) {
5119
5309
  config.template = await ensureNestedTemplates(
5120
5310
  config.template,
@@ -5124,7 +5314,7 @@ class VmsNamespace {
5124
5314
  const keys = Object.keys(builders);
5125
5315
  for (const key of keys) {
5126
5316
  const builder = builders[key];
5127
- if (builder) {
5317
+ if (builder?.configure) {
5128
5318
  config = await builder.configure(config);
5129
5319
  }
5130
5320
  }
@@ -5140,12 +5330,6 @@ class VmsNamespace {
5140
5330
  const builder = builders[key];
5141
5331
  if (builder) {
5142
5332
  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
5333
  instance._init(vm);
5150
5334
  vm[key] = instance;
5151
5335
  }
@@ -5164,6 +5348,30 @@ class VmsNamespace {
5164
5348
  async list() {
5165
5349
  return this.apiClient.get("/v1/vms", void 0);
5166
5350
  }
5351
+ /**
5352
+ * Get VM by ID.
5353
+ * @param vmId The ID of the VM
5354
+ * @param allowedSpecs Array of allowed specs (currently only 1 supported, always uses the first)
5355
+ * @returns The VM instance configured with builders from the spec
5356
+ */
5357
+ async get({
5358
+ vmId,
5359
+ allowedSpecs
5360
+ }) {
5361
+ const vm = new Vm(vmId, this.apiClient);
5362
+ const spec = allowedSpecs?.[0];
5363
+ if (spec) {
5364
+ const allBuilders = collectSpecBuilders(spec);
5365
+ for (const [key, builder] of Object.entries(allBuilders)) {
5366
+ if (builder) {
5367
+ const instance = builder.createInstance();
5368
+ instance._init(vm);
5369
+ vm[key] = instance;
5370
+ }
5371
+ }
5372
+ }
5373
+ return { vm, spec };
5374
+ }
5167
5375
  /**
5168
5376
  * Create a VM instance by ID without making an api call.
5169
5377
  */
@@ -5176,11 +5384,68 @@ class VmsNamespace {
5176
5384
  });
5177
5385
  }
5178
5386
  }
5179
- class VmTemplate {
5180
- raw;
5181
- constructor(template) {
5182
- this.raw = template;
5387
+ class VmSnapshotsNamespace {
5388
+ constructor(apiClient) {
5389
+ this.apiClient = apiClient;
5183
5390
  }
5391
+ async ensure(options) {
5392
+ return this.apiClient.post("/v1/vms/snapshots", {
5393
+ body: {
5394
+ ...options,
5395
+ template: options.template?.raw
5396
+ }
5397
+ });
5398
+ }
5399
+ }
5400
+ async function processTemplateTree(template) {
5401
+ for (const key in template.with) {
5402
+ const builder = template.with[key];
5403
+ if (builder) {
5404
+ if (builder.configureTemplate) {
5405
+ template = await builder.configureTemplate(template);
5406
+ }
5407
+ if (builder.configureNestedTemplate) {
5408
+ let nestedTemplate;
5409
+ if (template.raw.template instanceof VmTemplate) {
5410
+ nestedTemplate = template.raw.template;
5411
+ } else {
5412
+ nestedTemplate = new VmTemplate({});
5413
+ template.raw.template = nestedTemplate;
5414
+ }
5415
+ template.raw.template = await builder.configureNestedTemplate(nestedTemplate);
5416
+ }
5417
+ }
5418
+ }
5419
+ if (template.raw.template instanceof VmTemplate) {
5420
+ template.raw.template = await processTemplateTree(template.raw.template);
5421
+ }
5422
+ return template;
5423
+ }
5424
+ async function ensureNestedTemplates(template, snapshots) {
5425
+ let templates = [template];
5426
+ while (templates.at(-1)?.raw.template instanceof VmTemplate) {
5427
+ let innerTemplate = templates.at(-1).raw.template;
5428
+ templates.at(-1).raw.template = void 0;
5429
+ templates.push(innerTemplate);
5430
+ }
5431
+ return await layerTemplates(templates, snapshots);
5432
+ }
5433
+ async function layerTemplates(templates, snapshots) {
5434
+ let lastTemplate = templates.pop();
5435
+ const { snapshotId } = await snapshots.ensure({
5436
+ template: lastTemplate
5437
+ });
5438
+ let lastSnapshotId = snapshotId;
5439
+ while (templates.length > 0) {
5440
+ const template = templates.pop();
5441
+ template.raw.snapshotId = lastSnapshotId;
5442
+ const { snapshotId: snapshotId2 } = await snapshots.ensure({
5443
+ template
5444
+ });
5445
+ lastSnapshotId = snapshotId2;
5446
+ lastTemplate = template;
5447
+ }
5448
+ return lastTemplate;
5184
5449
  }
5185
5450
 
5186
5451
  class Freestyle {
@@ -5255,6 +5520,7 @@ exports.Identity = Identity;
5255
5520
  exports.Vm = Vm;
5256
5521
  exports.VmBuilder = VmBuilder;
5257
5522
  exports.VmService = VmService;
5523
+ exports.VmSpec = VmSpec;
5258
5524
  exports.VmTemplate = VmTemplate;
5259
5525
  exports.VmWith = VmWith;
5260
5526
  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
  */
@@ -10897,13 +10899,50 @@ declare class VmWithInstance {
10897
10899
  declare abstract class VmWith<TInstance extends VmWithInstance = VmWithInstance> {
10898
10900
  instance: TInstance;
10899
10901
  /**
10902
+ * @deprecated Use configureSpec instead.
10900
10903
  * Transform the VM configuration by applying this component's config.
10901
10904
  * Acts as middleware - receives existing config, returns transformed config.
10902
10905
  *
10903
10906
  * @param existingConfig - The current VM configuration
10904
10907
  * @returns The transformed VM configuration with this component's changes applied
10905
10908
  */
10906
- abstract configure(existingConfig: CreateVmOptions): CreateVmOptions | Promise<CreateVmOptions>;
10909
+ configure?(existingConfig: CreateVmOptions): CreateVmOptions | Promise<CreateVmOptions>;
10910
+ /**
10911
+ * Configure the spec that this VmWith is directly attached to.
10912
+ * Override this to modify the parent spec's configuration.
10913
+ *
10914
+ * @param spec - The VmSpec this VmWith is attached to
10915
+ * @returns The transformed VmSpec with this component's changes applied
10916
+ */
10917
+ configureSpec?(spec: VmSpec): VmSpec | Promise<VmSpec>;
10918
+ /**
10919
+ * Configure the snapshot spec inside the current spec.
10920
+ * If no snapshot spec exists, one will be created.
10921
+ * Override this to modify the inner snapshot layer's configuration.
10922
+ *
10923
+ * @param spec - The snapshot VmSpec (created if it doesn't exist)
10924
+ * @returns The transformed snapshot VmSpec with this component's changes applied
10925
+ */
10926
+ configureSnapshotSpec?(spec: VmSpec): VmSpec | Promise<VmSpec>;
10927
+ /**
10928
+ * @deprecated Use configureSpec instead
10929
+ * Configure the template that this VmWith is directly attached to.
10930
+ * Override this to modify the parent template's configuration.
10931
+ *
10932
+ * @param template - The VmTemplate this VmWith is attached to
10933
+ * @returns The transformed VmTemplate with this component's changes applied
10934
+ */
10935
+ configureTemplate?(template: VmTemplate): VmTemplate | Promise<VmTemplate>;
10936
+ /**
10937
+ * @deprecated Use configureSnapshotSpec instead
10938
+ * Configure the nested template inside the current template.
10939
+ * If no nested template exists, one will be created.
10940
+ * Override this to modify the inner template layer's configuration.
10941
+ *
10942
+ * @param template - The nested VmTemplate (created if it doesn't exist)
10943
+ * @returns The transformed nested VmTemplate with this component's changes applied
10944
+ */
10945
+ configureNestedTemplate?(template: VmTemplate): VmTemplate | Promise<VmTemplate>;
10907
10946
  /**
10908
10947
  * Create an instance of this component that will be attached to the VM.
10909
10948
  * Override this to provide custom interaction methods for your component.
@@ -10916,6 +10955,23 @@ declare abstract class VmWith<TInstance extends VmWithInstance = VmWithInstance>
10916
10955
  * Uses the same merging strategy as the main compose function.
10917
10956
  */
10918
10957
  protected compose(...configs: CreateVmOptions[]): CreateVmOptions;
10958
+ /**
10959
+ * Helper method to compose multiple VmSpecs together.
10960
+ * Uses the same merging strategy as the main compose function.
10961
+ * Useful in configureSpec and configureSnapshotSpec methods.
10962
+ */
10963
+ protected composeSpecs(...specs: VmSpec[]): VmSpec;
10964
+ /**
10965
+ * @deprecated Use composeSpecs instead
10966
+ * Helper method to compose multiple VmTemplates together.
10967
+ * Uses the same merging strategy as the main compose function.
10968
+ * Useful in configureTemplate and configureNestedTemplate methods.
10969
+ */
10970
+ protected composeTemplates(...templates: VmTemplate[]): VmTemplate;
10971
+ /**
10972
+ * @deprecated This method is no longer needed. Use vms.get with allowedSpecs instead.
10973
+ */
10974
+ configureReturnedVm(vm: Vm, template: VmTemplate): Promise<void>;
10919
10975
  }
10920
10976
  declare const VmService: typeof VmWith;
10921
10977
  declare const VmBuilder: typeof VmWith;
@@ -11032,16 +11088,35 @@ declare class Vm {
11032
11088
  rootfsSizeGb: number;
11033
11089
  vcpuCount: number;
11034
11090
  }): Promise<ResponsePostV1VmsIdResize200>;
11091
+ /**
11092
+ * Create a VM instance by ID without making an api call.
11093
+ */
11094
+ ref({ vmId }: {
11095
+ vmId: string;
11096
+ }): Vm;
11097
+ delete({ vmId }: {
11098
+ vmId: string;
11099
+ }): Promise<ResponseDeleteV1VmsVmId200>;
11035
11100
  }
11036
- type CreateVmOptions = Exclude<PostV1VmsRequestBody, "template"> & {
11037
- template?: VmTemplate | Exclude<PostV1VmsRequestBody, "template">["template"];
11101
+ type TemplateOptions = CreateSnapshotRequest["template"] & {
11102
+ template?: VmTemplate<Record<string, VmWith>>;
11038
11103
  };
11039
- declare class VmSnapshotsNamespace {
11040
- private apiClient;
11041
- constructor(apiClient: ApiClient);
11042
- ensure(options: Exclude<PostV1VmsSnapshotsRequestBody, "template"> & {
11043
- template?: VmTemplate;
11044
- }): Promise<ResponsePostV1VmsSnapshots200>;
11104
+ declare class VmTemplate<T extends Record<string, VmWith> = Record<string, VmWith>> {
11105
+ raw: TemplateOptions;
11106
+ readonly with: T;
11107
+ constructor(template: TemplateOptions & {
11108
+ with?: T;
11109
+ });
11110
+ }
11111
+ type VmSpecOptions = TemplateOptions & {
11112
+ snapshot?: VmSpec;
11113
+ };
11114
+ declare class VmSpec<T extends Record<string, VmWith> = Record<string, VmWith>> {
11115
+ raw: VmSpecOptions;
11116
+ readonly with: T;
11117
+ constructor(spec: VmSpecOptions & {
11118
+ with?: T;
11119
+ });
11045
11120
  }
11046
11121
  declare class VmsNamespace {
11047
11122
  private apiClient;
@@ -11054,6 +11129,8 @@ declare class VmsNamespace {
11054
11129
  */
11055
11130
  create<T extends Record<string, VmWith>>(options?: CreateVmOptions & {
11056
11131
  with?: T;
11132
+ template?: VmTemplate<T>;
11133
+ spec?: VmSpec<T>;
11057
11134
  }): Promise<ResponsePostV1Vms200 & {
11058
11135
  vmId: string;
11059
11136
  vm: Vm & {
@@ -11066,6 +11143,21 @@ declare class VmsNamespace {
11066
11143
  * @returns List of VMs with their states and metadata
11067
11144
  */
11068
11145
  list(): Promise<ResponseGetV1Vms200>;
11146
+ /**
11147
+ * Get VM by ID.
11148
+ * @param vmId The ID of the VM
11149
+ * @param allowedSpecs Array of allowed specs (currently only 1 supported, always uses the first)
11150
+ * @returns The VM instance configured with builders from the spec
11151
+ */
11152
+ get<T extends Record<string, VmWith>>({ vmId, allowedSpecs, }: {
11153
+ vmId: string;
11154
+ allowedSpecs?: [VmSpec<T>];
11155
+ }): Promise<{
11156
+ vm: Vm & {
11157
+ [K in keyof T]: ReturnType<T[K]["createInstance"]>;
11158
+ };
11159
+ spec?: VmSpec<T>;
11160
+ }>;
11069
11161
  /**
11070
11162
  * Create a VM instance by ID without making an api call.
11071
11163
  */
@@ -11076,13 +11168,16 @@ declare class VmsNamespace {
11076
11168
  vmId: string;
11077
11169
  }): Promise<ResponseDeleteV1VmsVmId200>;
11078
11170
  }
11079
- type TemplateOptions = CreateSnapshotRequest["template"] & {
11080
- template?: VmTemplate;
11081
- };
11082
- declare class VmTemplate {
11083
- raw: TemplateOptions;
11084
- constructor(template: TemplateOptions);
11171
+ declare class VmSnapshotsNamespace {
11172
+ private apiClient;
11173
+ constructor(apiClient: ApiClient);
11174
+ ensure(options: Exclude<PostV1VmsSnapshotsRequestBody, "template"> & {
11175
+ template?: VmTemplate;
11176
+ }): Promise<ResponsePostV1VmsSnapshots200>;
11085
11177
  }
11178
+ type CreateVmOptions = Omit<PostV1VmsRequestBody, "template"> & {
11179
+ template?: VmTemplate | Exclude<PostV1VmsRequestBody, "template">["template"];
11180
+ };
11086
11181
 
11087
11182
  type FreestyleOptions = ApiClientConfig;
11088
11183
  declare class Freestyle {
@@ -11105,5 +11200,5 @@ declare class Freestyle {
11105
11200
  */
11106
11201
  declare const freestyle: Freestyle;
11107
11202
 
11108
- export { Deployment, errors as Errors, FileSystem, Freestyle, GitRepo, Identity, requests as Requests, responses as Responses, Vm, VmBuilder, VmService, VmTemplate, VmWith, VmWithInstance, freestyle };
11203
+ export { Deployment, errors as Errors, FileSystem, Freestyle, GitRepo, Identity, requests as Requests, responses as Responses, Vm, VmBuilder, VmService, VmSpec, VmTemplate, VmWith, VmWithInstance, freestyle };
11109
11204
  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
  */
@@ -10897,13 +10899,50 @@ declare class VmWithInstance {
10897
10899
  declare abstract class VmWith<TInstance extends VmWithInstance = VmWithInstance> {
10898
10900
  instance: TInstance;
10899
10901
  /**
10902
+ * @deprecated Use configureSpec instead.
10900
10903
  * Transform the VM configuration by applying this component's config.
10901
10904
  * Acts as middleware - receives existing config, returns transformed config.
10902
10905
  *
10903
10906
  * @param existingConfig - The current VM configuration
10904
10907
  * @returns The transformed VM configuration with this component's changes applied
10905
10908
  */
10906
- abstract configure(existingConfig: CreateVmOptions): CreateVmOptions | Promise<CreateVmOptions>;
10909
+ configure?(existingConfig: CreateVmOptions): CreateVmOptions | Promise<CreateVmOptions>;
10910
+ /**
10911
+ * Configure the spec that this VmWith is directly attached to.
10912
+ * Override this to modify the parent spec's configuration.
10913
+ *
10914
+ * @param spec - The VmSpec this VmWith is attached to
10915
+ * @returns The transformed VmSpec with this component's changes applied
10916
+ */
10917
+ configureSpec?(spec: VmSpec): VmSpec | Promise<VmSpec>;
10918
+ /**
10919
+ * Configure the snapshot spec inside the current spec.
10920
+ * If no snapshot spec exists, one will be created.
10921
+ * Override this to modify the inner snapshot layer's configuration.
10922
+ *
10923
+ * @param spec - The snapshot VmSpec (created if it doesn't exist)
10924
+ * @returns The transformed snapshot VmSpec with this component's changes applied
10925
+ */
10926
+ configureSnapshotSpec?(spec: VmSpec): VmSpec | Promise<VmSpec>;
10927
+ /**
10928
+ * @deprecated Use configureSpec instead
10929
+ * Configure the template that this VmWith is directly attached to.
10930
+ * Override this to modify the parent template's configuration.
10931
+ *
10932
+ * @param template - The VmTemplate this VmWith is attached to
10933
+ * @returns The transformed VmTemplate with this component's changes applied
10934
+ */
10935
+ configureTemplate?(template: VmTemplate): VmTemplate | Promise<VmTemplate>;
10936
+ /**
10937
+ * @deprecated Use configureSnapshotSpec instead
10938
+ * Configure the nested template inside the current template.
10939
+ * If no nested template exists, one will be created.
10940
+ * Override this to modify the inner template layer's configuration.
10941
+ *
10942
+ * @param template - The nested VmTemplate (created if it doesn't exist)
10943
+ * @returns The transformed nested VmTemplate with this component's changes applied
10944
+ */
10945
+ configureNestedTemplate?(template: VmTemplate): VmTemplate | Promise<VmTemplate>;
10907
10946
  /**
10908
10947
  * Create an instance of this component that will be attached to the VM.
10909
10948
  * Override this to provide custom interaction methods for your component.
@@ -10916,6 +10955,23 @@ declare abstract class VmWith<TInstance extends VmWithInstance = VmWithInstance>
10916
10955
  * Uses the same merging strategy as the main compose function.
10917
10956
  */
10918
10957
  protected compose(...configs: CreateVmOptions[]): CreateVmOptions;
10958
+ /**
10959
+ * Helper method to compose multiple VmSpecs together.
10960
+ * Uses the same merging strategy as the main compose function.
10961
+ * Useful in configureSpec and configureSnapshotSpec methods.
10962
+ */
10963
+ protected composeSpecs(...specs: VmSpec[]): VmSpec;
10964
+ /**
10965
+ * @deprecated Use composeSpecs instead
10966
+ * Helper method to compose multiple VmTemplates together.
10967
+ * Uses the same merging strategy as the main compose function.
10968
+ * Useful in configureTemplate and configureNestedTemplate methods.
10969
+ */
10970
+ protected composeTemplates(...templates: VmTemplate[]): VmTemplate;
10971
+ /**
10972
+ * @deprecated This method is no longer needed. Use vms.get with allowedSpecs instead.
10973
+ */
10974
+ configureReturnedVm(vm: Vm, template: VmTemplate): Promise<void>;
10919
10975
  }
10920
10976
  declare const VmService: typeof VmWith;
10921
10977
  declare const VmBuilder: typeof VmWith;
@@ -11032,16 +11088,35 @@ declare class Vm {
11032
11088
  rootfsSizeGb: number;
11033
11089
  vcpuCount: number;
11034
11090
  }): Promise<ResponsePostV1VmsIdResize200>;
11091
+ /**
11092
+ * Create a VM instance by ID without making an api call.
11093
+ */
11094
+ ref({ vmId }: {
11095
+ vmId: string;
11096
+ }): Vm;
11097
+ delete({ vmId }: {
11098
+ vmId: string;
11099
+ }): Promise<ResponseDeleteV1VmsVmId200>;
11035
11100
  }
11036
- type CreateVmOptions = Exclude<PostV1VmsRequestBody, "template"> & {
11037
- template?: VmTemplate | Exclude<PostV1VmsRequestBody, "template">["template"];
11101
+ type TemplateOptions = CreateSnapshotRequest["template"] & {
11102
+ template?: VmTemplate<Record<string, VmWith>>;
11038
11103
  };
11039
- declare class VmSnapshotsNamespace {
11040
- private apiClient;
11041
- constructor(apiClient: ApiClient);
11042
- ensure(options: Exclude<PostV1VmsSnapshotsRequestBody, "template"> & {
11043
- template?: VmTemplate;
11044
- }): Promise<ResponsePostV1VmsSnapshots200>;
11104
+ declare class VmTemplate<T extends Record<string, VmWith> = Record<string, VmWith>> {
11105
+ raw: TemplateOptions;
11106
+ readonly with: T;
11107
+ constructor(template: TemplateOptions & {
11108
+ with?: T;
11109
+ });
11110
+ }
11111
+ type VmSpecOptions = TemplateOptions & {
11112
+ snapshot?: VmSpec;
11113
+ };
11114
+ declare class VmSpec<T extends Record<string, VmWith> = Record<string, VmWith>> {
11115
+ raw: VmSpecOptions;
11116
+ readonly with: T;
11117
+ constructor(spec: VmSpecOptions & {
11118
+ with?: T;
11119
+ });
11045
11120
  }
11046
11121
  declare class VmsNamespace {
11047
11122
  private apiClient;
@@ -11054,6 +11129,8 @@ declare class VmsNamespace {
11054
11129
  */
11055
11130
  create<T extends Record<string, VmWith>>(options?: CreateVmOptions & {
11056
11131
  with?: T;
11132
+ template?: VmTemplate<T>;
11133
+ spec?: VmSpec<T>;
11057
11134
  }): Promise<ResponsePostV1Vms200 & {
11058
11135
  vmId: string;
11059
11136
  vm: Vm & {
@@ -11066,6 +11143,21 @@ declare class VmsNamespace {
11066
11143
  * @returns List of VMs with their states and metadata
11067
11144
  */
11068
11145
  list(): Promise<ResponseGetV1Vms200>;
11146
+ /**
11147
+ * Get VM by ID.
11148
+ * @param vmId The ID of the VM
11149
+ * @param allowedSpecs Array of allowed specs (currently only 1 supported, always uses the first)
11150
+ * @returns The VM instance configured with builders from the spec
11151
+ */
11152
+ get<T extends Record<string, VmWith>>({ vmId, allowedSpecs, }: {
11153
+ vmId: string;
11154
+ allowedSpecs?: [VmSpec<T>];
11155
+ }): Promise<{
11156
+ vm: Vm & {
11157
+ [K in keyof T]: ReturnType<T[K]["createInstance"]>;
11158
+ };
11159
+ spec?: VmSpec<T>;
11160
+ }>;
11069
11161
  /**
11070
11162
  * Create a VM instance by ID without making an api call.
11071
11163
  */
@@ -11076,13 +11168,16 @@ declare class VmsNamespace {
11076
11168
  vmId: string;
11077
11169
  }): Promise<ResponseDeleteV1VmsVmId200>;
11078
11170
  }
11079
- type TemplateOptions = CreateSnapshotRequest["template"] & {
11080
- template?: VmTemplate;
11081
- };
11082
- declare class VmTemplate {
11083
- raw: TemplateOptions;
11084
- constructor(template: TemplateOptions);
11171
+ declare class VmSnapshotsNamespace {
11172
+ private apiClient;
11173
+ constructor(apiClient: ApiClient);
11174
+ ensure(options: Exclude<PostV1VmsSnapshotsRequestBody, "template"> & {
11175
+ template?: VmTemplate;
11176
+ }): Promise<ResponsePostV1VmsSnapshots200>;
11085
11177
  }
11178
+ type CreateVmOptions = Omit<PostV1VmsRequestBody, "template"> & {
11179
+ template?: VmTemplate | Exclude<PostV1VmsRequestBody, "template">["template"];
11180
+ };
11086
11181
 
11087
11182
  type FreestyleOptions = ApiClientConfig;
11088
11183
  declare class Freestyle {
@@ -11105,5 +11200,5 @@ declare class Freestyle {
11105
11200
  */
11106
11201
  declare const freestyle: Freestyle;
11107
11202
 
11108
- export { Deployment, errors as Errors, FileSystem, Freestyle, GitRepo, Identity, requests as Requests, responses as Responses, Vm, VmBuilder, VmService, VmTemplate, VmWith, VmWithInstance, freestyle };
11203
+ export { Deployment, errors as Errors, FileSystem, Freestyle, GitRepo, Identity, requests as Requests, responses as Responses, Vm, VmBuilder, VmService, VmSpec, VmTemplate, VmWith, VmWithInstance, freestyle };
11109
11204
  export type { CreateVmOptions, FreestyleOptions };
package/index.mjs CHANGED
@@ -4622,6 +4622,36 @@ 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
+ /**
4643
+ * @deprecated This method is no longer needed. Use vms.get with allowedSpecs instead.
4644
+ */
4645
+ async configureReturnedVm(vm, template) {
4646
+ if (template.raw) {
4647
+ if (this.configure) {
4648
+ await this.configure(template.raw);
4649
+ }
4650
+ const instance = this.createInstance();
4651
+ instance._init(vm);
4652
+ this.instance = instance;
4653
+ }
4654
+ }
4625
4655
  }
4626
4656
  const VmService = VmWith;
4627
4657
  const VmBuilder = VmWith;
@@ -4873,6 +4903,73 @@ function composeCreateVmOptions(arr) {
4873
4903
  }
4874
4904
  return result;
4875
4905
  }
4906
+ function composeVmTemplates(templates) {
4907
+ if (templates.length === 0) {
4908
+ return new VmTemplate({});
4909
+ }
4910
+ if (templates.length === 1) {
4911
+ return templates[0];
4912
+ }
4913
+ const rawConfigs = templates.map((t) => t.raw);
4914
+ const composedRaw = composeCreateVmOptions(
4915
+ rawConfigs
4916
+ );
4917
+ const mergedWith = {};
4918
+ for (const template of templates) {
4919
+ for (const key in template.with) {
4920
+ const builder = template.with[key];
4921
+ if (builder) {
4922
+ mergedWith[key] = builder;
4923
+ }
4924
+ }
4925
+ }
4926
+ const nestedTemplates = templates.map((t) => t.raw.template).filter((t) => t instanceof VmTemplate);
4927
+ let nestedTemplate;
4928
+ if (nestedTemplates.length > 0) {
4929
+ nestedTemplate = composeVmTemplates(nestedTemplates);
4930
+ }
4931
+ const result = new VmTemplate({
4932
+ ...composedRaw,
4933
+ template: nestedTemplate,
4934
+ with: mergedWith
4935
+ });
4936
+ return result;
4937
+ }
4938
+ function composeVmSpecs(specs) {
4939
+ if (specs.length === 0) {
4940
+ return new VmSpec({});
4941
+ }
4942
+ if (specs.length === 1) {
4943
+ return specs[0];
4944
+ }
4945
+ const rawConfigs = specs.map((s) => {
4946
+ const { snapshot, ...rest } = s.raw;
4947
+ return rest;
4948
+ });
4949
+ const composedRaw = composeCreateVmOptions(
4950
+ rawConfigs
4951
+ );
4952
+ const mergedWith = {};
4953
+ for (const spec of specs) {
4954
+ for (const key in spec.with) {
4955
+ const builder = spec.with[key];
4956
+ if (builder) {
4957
+ mergedWith[key] = builder;
4958
+ }
4959
+ }
4960
+ }
4961
+ const nestedSpecs = specs.map((s) => s.raw.snapshot).filter((s) => s instanceof VmSpec);
4962
+ let nestedSpec;
4963
+ if (nestedSpecs.length > 0) {
4964
+ nestedSpec = composeVmSpecs(nestedSpecs);
4965
+ }
4966
+ const result = new VmSpec({
4967
+ ...composedRaw,
4968
+ snapshot: nestedSpec,
4969
+ with: mergedWith
4970
+ });
4971
+ return result;
4972
+ }
4876
4973
 
4877
4974
  class VmTerminals {
4878
4975
  constructor(vmId, client) {
@@ -5052,51 +5149,121 @@ class Vm {
5052
5149
  return this.apiClient.post("/v1/vms/{id}/resize", {
5053
5150
  params: { id: this.vmId },
5054
5151
  body: { memSizeGb, rootfsSizeGb, vcpuCount }
5152
+ // Temporarily cast to any
5055
5153
  });
5056
5154
  }
5057
- }
5058
- class VmSnapshotsNamespace {
5059
- constructor(apiClient) {
5060
- this.apiClient = apiClient;
5155
+ /**
5156
+ * Create a VM instance by ID without making an api call.
5157
+ */
5158
+ ref({ vmId }) {
5159
+ return new Vm(vmId, this.apiClient);
5061
5160
  }
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
- }
5161
+ async delete({ vmId }) {
5162
+ return this.apiClient.delete("/v1/vms/{vm_id}", {
5163
+ params: { vm_id: vmId }
5072
5164
  });
5073
5165
  }
5074
5166
  }
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);
5167
+ class VmTemplate {
5168
+ raw;
5169
+ with;
5170
+ constructor(template) {
5171
+ const { with: withBuilders, ...rest } = template;
5172
+ this.raw = rest;
5173
+ this.with = withBuilders ?? {};
5081
5174
  }
5082
- return await layerTemplates(templates, snapshots);
5083
5175
  }
5084
- async function layerTemplates(templates, snapshots) {
5085
- let lastTemplate = templates.pop();
5086
- const { snapshotId } = await snapshots.ensure({
5087
- template: lastTemplate
5176
+ class VmSpec {
5177
+ raw;
5178
+ with;
5179
+ constructor(spec) {
5180
+ const { with: withBuilders, ...rest } = spec;
5181
+ this.raw = rest;
5182
+ this.with = withBuilders ?? {};
5183
+ }
5184
+ }
5185
+ async function convertSpecSnapshotsToTemplates(spec, processBuilders = true) {
5186
+ if (!spec.raw.snapshot) {
5187
+ return void 0;
5188
+ }
5189
+ let innerSpec = spec.raw.snapshot;
5190
+ if (processBuilders) {
5191
+ innerSpec = await processSpecTree(innerSpec);
5192
+ }
5193
+ const { snapshot: nestedSnapshot, ...innerRaw } = innerSpec.raw;
5194
+ let nestedTemplate;
5195
+ if (nestedSnapshot instanceof VmSpec) {
5196
+ nestedTemplate = await convertSpecSnapshotsToTemplates(innerSpec, false);
5197
+ }
5198
+ return new VmTemplate({
5199
+ ...innerRaw,
5200
+ template: nestedTemplate,
5201
+ with: innerSpec.with
5088
5202
  });
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;
5203
+ }
5204
+ async function processOuterSpecBuilders(spec) {
5205
+ for (const key in spec.with) {
5206
+ const builder = spec.with[key];
5207
+ if (builder) {
5208
+ if (builder.configureSpec) {
5209
+ spec = await builder.configureSpec(spec);
5210
+ }
5211
+ if (builder.configureSnapshotSpec) {
5212
+ let snapshotSpec;
5213
+ if (spec.raw.snapshot instanceof VmSpec) {
5214
+ snapshotSpec = spec.raw.snapshot;
5215
+ } else {
5216
+ snapshotSpec = new VmSpec({});
5217
+ spec.raw.snapshot = snapshotSpec;
5218
+ }
5219
+ spec.raw.snapshot = await builder.configureSnapshotSpec(snapshotSpec);
5220
+ }
5221
+ }
5098
5222
  }
5099
- return lastTemplate;
5223
+ return spec;
5224
+ }
5225
+ async function processSpecTree(spec) {
5226
+ for (const key in spec.with) {
5227
+ const builder = spec.with[key];
5228
+ if (builder) {
5229
+ if (builder.configureSpec) {
5230
+ spec = await builder.configureSpec(spec);
5231
+ }
5232
+ if (builder.configureSnapshotSpec) {
5233
+ let snapshotSpec;
5234
+ if (spec.raw.snapshot instanceof VmSpec) {
5235
+ snapshotSpec = spec.raw.snapshot;
5236
+ } else {
5237
+ snapshotSpec = new VmSpec({});
5238
+ spec.raw.snapshot = snapshotSpec;
5239
+ }
5240
+ spec.raw.snapshot = await builder.configureSnapshotSpec(snapshotSpec);
5241
+ }
5242
+ }
5243
+ }
5244
+ if (spec.raw.snapshot instanceof VmSpec) {
5245
+ spec.raw.snapshot = await processSpecTree(spec.raw.snapshot);
5246
+ }
5247
+ return spec;
5248
+ }
5249
+ function collectSpecBuilders(spec) {
5250
+ const builders = {};
5251
+ if (spec.with) {
5252
+ for (const [key, builder] of Object.entries(spec.with)) {
5253
+ if (builder) {
5254
+ builders[key] = builder;
5255
+ }
5256
+ }
5257
+ }
5258
+ if (spec.raw.snapshot instanceof VmSpec) {
5259
+ const nestedBuilders = collectSpecBuilders(spec.raw.snapshot);
5260
+ for (const [key, builder] of Object.entries(nestedBuilders)) {
5261
+ if (!builders[key] && builder) {
5262
+ builders[key] = builder;
5263
+ }
5264
+ }
5265
+ }
5266
+ return builders;
5100
5267
  }
5101
5268
  class VmsNamespace {
5102
5269
  constructor(apiClient) {
@@ -5110,9 +5277,32 @@ class VmsNamespace {
5110
5277
  * @returns A VM instance representing the created VM
5111
5278
  */
5112
5279
  async create(options = {}) {
5113
- const builders = options.with || {};
5280
+ if (options.spec instanceof VmSpec) {
5281
+ let spec = options.spec;
5282
+ spec = await processOuterSpecBuilders(spec);
5283
+ const { snapshot, ...outerSpecOptions } = spec.raw;
5284
+ const innerTemplate = await convertSpecSnapshotsToTemplates(spec);
5285
+ if (innerTemplate) {
5286
+ options.template = innerTemplate;
5287
+ }
5288
+ const mergedOptions = composeCreateVmOptions([
5289
+ outerSpecOptions,
5290
+ options
5291
+ ]);
5292
+ options = {
5293
+ ...options,
5294
+ ...mergedOptions
5295
+ };
5296
+ if (!options.with && spec.with) {
5297
+ options.with = spec.with;
5298
+ }
5299
+ }
5300
+ const builders = options.with || options.template?.with || {};
5114
5301
  const { with: _, ...baseConfig } = options;
5115
5302
  let config = baseConfig;
5303
+ if (config.template instanceof VmTemplate) {
5304
+ config.template = await processTemplateTree(config.template);
5305
+ }
5116
5306
  if (config.template instanceof VmTemplate) {
5117
5307
  config.template = await ensureNestedTemplates(
5118
5308
  config.template,
@@ -5122,7 +5312,7 @@ class VmsNamespace {
5122
5312
  const keys = Object.keys(builders);
5123
5313
  for (const key of keys) {
5124
5314
  const builder = builders[key];
5125
- if (builder) {
5315
+ if (builder?.configure) {
5126
5316
  config = await builder.configure(config);
5127
5317
  }
5128
5318
  }
@@ -5138,12 +5328,6 @@ class VmsNamespace {
5138
5328
  const builder = builders[key];
5139
5329
  if (builder) {
5140
5330
  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
5331
  instance._init(vm);
5148
5332
  vm[key] = instance;
5149
5333
  }
@@ -5162,6 +5346,30 @@ class VmsNamespace {
5162
5346
  async list() {
5163
5347
  return this.apiClient.get("/v1/vms", void 0);
5164
5348
  }
5349
+ /**
5350
+ * Get VM by ID.
5351
+ * @param vmId The ID of the VM
5352
+ * @param allowedSpecs Array of allowed specs (currently only 1 supported, always uses the first)
5353
+ * @returns The VM instance configured with builders from the spec
5354
+ */
5355
+ async get({
5356
+ vmId,
5357
+ allowedSpecs
5358
+ }) {
5359
+ const vm = new Vm(vmId, this.apiClient);
5360
+ const spec = allowedSpecs?.[0];
5361
+ if (spec) {
5362
+ const allBuilders = collectSpecBuilders(spec);
5363
+ for (const [key, builder] of Object.entries(allBuilders)) {
5364
+ if (builder) {
5365
+ const instance = builder.createInstance();
5366
+ instance._init(vm);
5367
+ vm[key] = instance;
5368
+ }
5369
+ }
5370
+ }
5371
+ return { vm, spec };
5372
+ }
5165
5373
  /**
5166
5374
  * Create a VM instance by ID without making an api call.
5167
5375
  */
@@ -5174,11 +5382,68 @@ class VmsNamespace {
5174
5382
  });
5175
5383
  }
5176
5384
  }
5177
- class VmTemplate {
5178
- raw;
5179
- constructor(template) {
5180
- this.raw = template;
5385
+ class VmSnapshotsNamespace {
5386
+ constructor(apiClient) {
5387
+ this.apiClient = apiClient;
5181
5388
  }
5389
+ async ensure(options) {
5390
+ return this.apiClient.post("/v1/vms/snapshots", {
5391
+ body: {
5392
+ ...options,
5393
+ template: options.template?.raw
5394
+ }
5395
+ });
5396
+ }
5397
+ }
5398
+ async function processTemplateTree(template) {
5399
+ for (const key in template.with) {
5400
+ const builder = template.with[key];
5401
+ if (builder) {
5402
+ if (builder.configureTemplate) {
5403
+ template = await builder.configureTemplate(template);
5404
+ }
5405
+ if (builder.configureNestedTemplate) {
5406
+ let nestedTemplate;
5407
+ if (template.raw.template instanceof VmTemplate) {
5408
+ nestedTemplate = template.raw.template;
5409
+ } else {
5410
+ nestedTemplate = new VmTemplate({});
5411
+ template.raw.template = nestedTemplate;
5412
+ }
5413
+ template.raw.template = await builder.configureNestedTemplate(nestedTemplate);
5414
+ }
5415
+ }
5416
+ }
5417
+ if (template.raw.template instanceof VmTemplate) {
5418
+ template.raw.template = await processTemplateTree(template.raw.template);
5419
+ }
5420
+ return template;
5421
+ }
5422
+ async function ensureNestedTemplates(template, snapshots) {
5423
+ let templates = [template];
5424
+ while (templates.at(-1)?.raw.template instanceof VmTemplate) {
5425
+ let innerTemplate = templates.at(-1).raw.template;
5426
+ templates.at(-1).raw.template = void 0;
5427
+ templates.push(innerTemplate);
5428
+ }
5429
+ return await layerTemplates(templates, snapshots);
5430
+ }
5431
+ async function layerTemplates(templates, snapshots) {
5432
+ let lastTemplate = templates.pop();
5433
+ const { snapshotId } = await snapshots.ensure({
5434
+ template: lastTemplate
5435
+ });
5436
+ let lastSnapshotId = snapshotId;
5437
+ while (templates.length > 0) {
5438
+ const template = templates.pop();
5439
+ template.raw.snapshotId = lastSnapshotId;
5440
+ const { snapshotId: snapshotId2 } = await snapshots.ensure({
5441
+ template
5442
+ });
5443
+ lastSnapshotId = snapshotId2;
5444
+ lastTemplate = template;
5445
+ }
5446
+ return lastTemplate;
5182
5447
  }
5183
5448
 
5184
5449
  class Freestyle {
@@ -5244,4 +5509,4 @@ function createLazyFreestyle(options = {}) {
5244
5509
  }
5245
5510
  const freestyle = createLazyFreestyle();
5246
5511
 
5247
- export { Deployment, errors as Errors, FileSystem, Freestyle, GitRepo, Identity, Vm, VmBuilder, VmService, VmTemplate, VmWith, VmWithInstance, freestyle };
5512
+ 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.8",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "require": {