modal 0.10.2-dev.5 → 0.10.2-dev.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -50,6 +50,7 @@ We also provide a number of examples:
50
50
  - [Create a Sandbox using a private image from AWS ECR](https://github.com/modal-labs/modal-client/blob/main/js/examples/sandbox-private-image.ts)
51
51
  - [Run a Sidecar and snapshot its filesystem](https://github.com/modal-labs/modal-client/blob/main/js/examples/sandbox-sidecar.ts)
52
52
  - [Persist a workspace across Sidecars](https://github.com/modal-labs/modal-client/blob/main/js/examples/sandbox-sidecar-directory-snapshot.ts)
53
+ - [Mount a Volume in a Sidecar](https://github.com/modal-labs/modal-client/blob/main/js/examples/sandbox-sidecar-volume.ts)
53
54
  - [Take a snapshot of the filesystem of a Sandbox](https://github.com/modal-labs/modal-client/blob/main/js/examples/sandbox-filesystem-snapshot.ts)
54
55
  - [Snapshot the filesystem of a Sandbox when it exits](https://github.com/modal-labs/modal-client/blob/main/js/examples/sandbox-exit-snapshot.ts)
55
56
  - [Snapshot a directory, and mount it in a running Sandbox](https://github.com/modal-labs/modal-client/blob/main/js/examples/sandbox-directory-snapshot.ts)
package/dist/index.cjs CHANGED
@@ -70555,8 +70555,151 @@ var SandboxFilesystem = class {
70555
70555
  };
70556
70556
 
70557
70557
  // src/sandbox_sidecar.ts
70558
- var import_nice_grpc10 = require("nice-grpc");
70558
+ var import_nice_grpc11 = require("nice-grpc");
70559
70559
  var import_uuid = require("uuid");
70560
+
70561
+ // src/volume.ts
70562
+ var import_nice_grpc10 = require("nice-grpc");
70563
+ var VolumeService = class {
70564
+ #client;
70565
+ constructor(client) {
70566
+ this.#client = client;
70567
+ }
70568
+ /**
70569
+ * Reference a {@link Volume} by its name.
70570
+ */
70571
+ async fromName(name, params) {
70572
+ try {
70573
+ const resp = await this.#client.cpClient.volumeGetOrCreate({
70574
+ deploymentName: name,
70575
+ environmentName: this.#client.environmentName(params?.environment),
70576
+ objectCreationType: params?.createIfMissing ? 1 /* OBJECT_CREATION_TYPE_CREATE_IF_MISSING */ : 0 /* OBJECT_CREATION_TYPE_UNSPECIFIED */
70577
+ });
70578
+ this.#client.logger.debug(
70579
+ "Retrieved Volume",
70580
+ "volume_id",
70581
+ resp.volumeId,
70582
+ "volume_name",
70583
+ name
70584
+ );
70585
+ return new Volume(resp.volumeId, name);
70586
+ } catch (err) {
70587
+ if (err instanceof import_nice_grpc10.ClientError && err.code === import_nice_grpc10.Status.NOT_FOUND)
70588
+ throw new NotFoundError(err.details);
70589
+ throw err;
70590
+ }
70591
+ }
70592
+ /**
70593
+ * Create a nameless, temporary {@link Volume}.
70594
+ * It persists until closeEphemeral() is called, or the process exits.
70595
+ */
70596
+ async ephemeral(params = {}) {
70597
+ const resp = await this.#client.cpClient.volumeGetOrCreate({
70598
+ objectCreationType: 5 /* OBJECT_CREATION_TYPE_EPHEMERAL */,
70599
+ environmentName: this.#client.environmentName(params.environment)
70600
+ });
70601
+ this.#client.logger.debug(
70602
+ "Created ephemeral Volume",
70603
+ "volume_id",
70604
+ resp.volumeId
70605
+ );
70606
+ const ephemeralHbManager = new EphemeralHeartbeatManager(
70607
+ () => this.#client.cpClient.volumeHeartbeat({ volumeId: resp.volumeId })
70608
+ );
70609
+ return new Volume(resp.volumeId, void 0, void 0, ephemeralHbManager);
70610
+ }
70611
+ /**
70612
+ * Delete a named {@link Volume}.
70613
+ *
70614
+ * Warning: Deletion is irreversible and will affect any Apps currently using the Volume.
70615
+ */
70616
+ async delete(name, params) {
70617
+ try {
70618
+ const volume = await this.fromName(name, {
70619
+ environment: params?.environment,
70620
+ createIfMissing: false
70621
+ });
70622
+ await this.#client.cpClient.volumeDelete({
70623
+ volumeId: volume.volumeId
70624
+ });
70625
+ this.#client.logger.debug(
70626
+ "Deleted Volume",
70627
+ "volume_name",
70628
+ name,
70629
+ "volume_id",
70630
+ volume.volumeId
70631
+ );
70632
+ } catch (err) {
70633
+ const isNotFound = err instanceof NotFoundError || err instanceof import_nice_grpc10.ClientError && err.code === import_nice_grpc10.Status.NOT_FOUND;
70634
+ if (isNotFound && params?.allowMissing) {
70635
+ return;
70636
+ }
70637
+ throw err;
70638
+ }
70639
+ }
70640
+ };
70641
+ var DEFAULT_MOUNT_OPTIONS = {
70642
+ readOnly: false,
70643
+ subPath: void 0
70644
+ };
70645
+ var Volume = class _Volume {
70646
+ volumeId;
70647
+ name;
70648
+ /**
70649
+ * @hidden
70650
+ * @internal
70651
+ */
70652
+ _mountOptions = DEFAULT_MOUNT_OPTIONS;
70653
+ #ephemeralHbManager;
70654
+ /** @ignore */
70655
+ constructor(volumeId, name, mountOptions, ephemeralHbManager) {
70656
+ this.volumeId = volumeId;
70657
+ this.name = name;
70658
+ if (mountOptions) {
70659
+ this._mountOptions = mountOptions;
70660
+ }
70661
+ this.#ephemeralHbManager = ephemeralHbManager;
70662
+ }
70663
+ /**
70664
+ * Configure options used when mounting this Volume. Fields left undefined preserve their value
70665
+ * from any previous withMountOptions call on the same Volume (stacking).
70666
+ */
70667
+ withMountOptions(params = {}) {
70668
+ let subPath = this._mountOptions.subPath;
70669
+ if (params.subPath !== void 0) {
70670
+ subPath = params.subPath === "/" ? void 0 : params.subPath;
70671
+ }
70672
+ return new _Volume(
70673
+ this.volumeId,
70674
+ this.name,
70675
+ {
70676
+ readOnly: params.readOnly ?? this._mountOptions.readOnly,
70677
+ subPath
70678
+ },
70679
+ this.#ephemeralHbManager
70680
+ );
70681
+ }
70682
+ /** Delete the ephemeral Volume. Only usable with emphemeral Volumes. */
70683
+ closeEphemeral() {
70684
+ if (this.#ephemeralHbManager) {
70685
+ this.#ephemeralHbManager.stop();
70686
+ } else {
70687
+ throw new InvalidError("Volume is not ephemeral.");
70688
+ }
70689
+ }
70690
+ };
70691
+ function volumeToMountProto(mountPath, volume) {
70692
+ const opts = volume._mountOptions ?? DEFAULT_MOUNT_OPTIONS;
70693
+ return {
70694
+ volumeId: volume.volumeId,
70695
+ mountPath,
70696
+ allowBackgroundCommits: true,
70697
+ readOnly: opts.readOnly,
70698
+ subPath: opts.subPath
70699
+ };
70700
+ }
70701
+
70702
+ // src/sandbox_sidecar.ts
70560
70703
  var MAIN_CONTAINER_NAME = "main";
70561
70704
  var CONTAINER_WAIT_POLL_TIMEOUT_SECONDS = 10;
70562
70705
  var CONTROL_PLANE_SIDECAR_CREATE_ENV_VAR = "MODAL_USE_CONTROL_PLANE_SIDECAR_CREATE";
@@ -70573,6 +70716,29 @@ function validateSidecarName(name) {
70573
70716
  );
70574
70717
  }
70575
70718
  }
70719
+ function buildSidecarVolumeMounts(volumes) {
70720
+ const mountPathsByVolumeId = /* @__PURE__ */ new Map();
70721
+ const volumeMounts = [];
70722
+ for (const [mountPath, volume] of Object.entries(volumes ?? {})) {
70723
+ if (!volume) {
70724
+ throw new InvalidError(
70725
+ `Volume mounted at "${mountPath}" must not be null or undefined`
70726
+ );
70727
+ }
70728
+ const mountPaths = mountPathsByVolumeId.get(volume.volumeId) ?? [];
70729
+ mountPaths.push(mountPath);
70730
+ mountPathsByVolumeId.set(volume.volumeId, mountPaths);
70731
+ volumeMounts.push(volumeToMountProto(mountPath, volume));
70732
+ }
70733
+ for (const mountPaths of mountPathsByVolumeId.values()) {
70734
+ if (mountPaths.length > 1) {
70735
+ throw new InvalidError(
70736
+ "The same Volume cannot be mounted at multiple paths in a sidecar: " + mountPaths.sort().join(", ")
70737
+ );
70738
+ }
70739
+ }
70740
+ return volumeMounts;
70741
+ }
70576
70742
  function sidecarContainerFromProto(access, info) {
70577
70743
  return new SidecarContainer(
70578
70744
  access,
@@ -70610,6 +70776,7 @@ var SidecarService = class {
70610
70776
  Object.assign(envDict, params?.env ?? {});
70611
70777
  await hydrateSecrets(this.#access.client, resolvableSecrets);
70612
70778
  const secretIds = collectSecretIds(resolvableSecrets);
70779
+ const volumeMounts = buildSidecarVolumeMounts(params?.volumes);
70613
70780
  const ptyInfo = params?.pty ? defaultSandboxPTYInfo() : void 0;
70614
70781
  const networkAccess = buildOutboundNetworkAccess(
70615
70782
  false,
@@ -70629,6 +70796,7 @@ var SidecarService = class {
70629
70796
  entrypointArgs: command,
70630
70797
  workdir: params?.workdir ?? void 0,
70631
70798
  secretIds,
70799
+ volumeMounts,
70632
70800
  networkAccess,
70633
70801
  ptyInfo
70634
70802
  }),
@@ -70646,17 +70814,18 @@ var SidecarService = class {
70646
70814
  env: envDict,
70647
70815
  workdir: params?.workdir ?? "",
70648
70816
  secretIds,
70817
+ volumeMounts,
70649
70818
  networkAccess,
70650
70819
  ptyInfo
70651
70820
  })
70652
70821
  );
70653
70822
  }
70654
70823
  } catch (err) {
70655
- if (err instanceof import_nice_grpc10.ClientError) {
70656
- if (err.code === import_nice_grpc10.Status.ALREADY_EXISTS) {
70824
+ if (err instanceof import_nice_grpc11.ClientError) {
70825
+ if (err.code === import_nice_grpc11.Status.ALREADY_EXISTS) {
70657
70826
  throw new AlreadyExistsError(err.details || err.message);
70658
70827
  }
70659
- if (err.code === import_nice_grpc10.Status.INVALID_ARGUMENT) {
70828
+ if (err.code === import_nice_grpc11.Status.INVALID_ARGUMENT) {
70660
70829
  throw new InvalidError(err.details || err.message);
70661
70830
  }
70662
70831
  }
@@ -70682,7 +70851,7 @@ var SidecarService = class {
70682
70851
  })
70683
70852
  );
70684
70853
  } catch (err) {
70685
- if (err instanceof import_nice_grpc10.ClientError && err.code === import_nice_grpc10.Status.NOT_FOUND) {
70854
+ if (err instanceof import_nice_grpc11.ClientError && err.code === import_nice_grpc11.Status.NOT_FOUND) {
70686
70855
  throw new NotFoundError(`Sidecar container "${name}" not found`);
70687
70856
  }
70688
70857
  throw err;
@@ -71053,147 +71222,6 @@ var SandboxSnapshotService = class {
71053
71222
  }
71054
71223
  };
71055
71224
 
71056
- // src/volume.ts
71057
- var import_nice_grpc11 = require("nice-grpc");
71058
- var VolumeService = class {
71059
- #client;
71060
- constructor(client) {
71061
- this.#client = client;
71062
- }
71063
- /**
71064
- * Reference a {@link Volume} by its name.
71065
- */
71066
- async fromName(name, params) {
71067
- try {
71068
- const resp = await this.#client.cpClient.volumeGetOrCreate({
71069
- deploymentName: name,
71070
- environmentName: this.#client.environmentName(params?.environment),
71071
- objectCreationType: params?.createIfMissing ? 1 /* OBJECT_CREATION_TYPE_CREATE_IF_MISSING */ : 0 /* OBJECT_CREATION_TYPE_UNSPECIFIED */
71072
- });
71073
- this.#client.logger.debug(
71074
- "Retrieved Volume",
71075
- "volume_id",
71076
- resp.volumeId,
71077
- "volume_name",
71078
- name
71079
- );
71080
- return new Volume(resp.volumeId, name);
71081
- } catch (err) {
71082
- if (err instanceof import_nice_grpc11.ClientError && err.code === import_nice_grpc11.Status.NOT_FOUND)
71083
- throw new NotFoundError(err.details);
71084
- throw err;
71085
- }
71086
- }
71087
- /**
71088
- * Create a nameless, temporary {@link Volume}.
71089
- * It persists until closeEphemeral() is called, or the process exits.
71090
- */
71091
- async ephemeral(params = {}) {
71092
- const resp = await this.#client.cpClient.volumeGetOrCreate({
71093
- objectCreationType: 5 /* OBJECT_CREATION_TYPE_EPHEMERAL */,
71094
- environmentName: this.#client.environmentName(params.environment)
71095
- });
71096
- this.#client.logger.debug(
71097
- "Created ephemeral Volume",
71098
- "volume_id",
71099
- resp.volumeId
71100
- );
71101
- const ephemeralHbManager = new EphemeralHeartbeatManager(
71102
- () => this.#client.cpClient.volumeHeartbeat({ volumeId: resp.volumeId })
71103
- );
71104
- return new Volume(resp.volumeId, void 0, void 0, ephemeralHbManager);
71105
- }
71106
- /**
71107
- * Delete a named {@link Volume}.
71108
- *
71109
- * Warning: Deletion is irreversible and will affect any Apps currently using the Volume.
71110
- */
71111
- async delete(name, params) {
71112
- try {
71113
- const volume = await this.fromName(name, {
71114
- environment: params?.environment,
71115
- createIfMissing: false
71116
- });
71117
- await this.#client.cpClient.volumeDelete({
71118
- volumeId: volume.volumeId
71119
- });
71120
- this.#client.logger.debug(
71121
- "Deleted Volume",
71122
- "volume_name",
71123
- name,
71124
- "volume_id",
71125
- volume.volumeId
71126
- );
71127
- } catch (err) {
71128
- const isNotFound = err instanceof NotFoundError || err instanceof import_nice_grpc11.ClientError && err.code === import_nice_grpc11.Status.NOT_FOUND;
71129
- if (isNotFound && params?.allowMissing) {
71130
- return;
71131
- }
71132
- throw err;
71133
- }
71134
- }
71135
- };
71136
- var DEFAULT_MOUNT_OPTIONS = {
71137
- readOnly: false,
71138
- subPath: void 0
71139
- };
71140
- var Volume = class _Volume {
71141
- volumeId;
71142
- name;
71143
- /**
71144
- * @hidden
71145
- * @internal
71146
- */
71147
- _mountOptions = DEFAULT_MOUNT_OPTIONS;
71148
- #ephemeralHbManager;
71149
- /** @ignore */
71150
- constructor(volumeId, name, mountOptions, ephemeralHbManager) {
71151
- this.volumeId = volumeId;
71152
- this.name = name;
71153
- if (mountOptions) {
71154
- this._mountOptions = mountOptions;
71155
- }
71156
- this.#ephemeralHbManager = ephemeralHbManager;
71157
- }
71158
- /**
71159
- * Configure options used when mounting this Volume. Fields left undefined preserve their value
71160
- * from any previous withMountOptions call on the same Volume (stacking).
71161
- */
71162
- withMountOptions(params = {}) {
71163
- let subPath = this._mountOptions.subPath;
71164
- if (params.subPath !== void 0) {
71165
- subPath = params.subPath === "/" ? void 0 : params.subPath;
71166
- }
71167
- return new _Volume(
71168
- this.volumeId,
71169
- this.name,
71170
- {
71171
- readOnly: params.readOnly ?? this._mountOptions.readOnly,
71172
- subPath
71173
- },
71174
- this.#ephemeralHbManager
71175
- );
71176
- }
71177
- /** Delete the ephemeral Volume. Only usable with emphemeral Volumes. */
71178
- closeEphemeral() {
71179
- if (this.#ephemeralHbManager) {
71180
- this.#ephemeralHbManager.stop();
71181
- } else {
71182
- throw new InvalidError("Volume is not ephemeral.");
71183
- }
71184
- }
71185
- };
71186
- function volumeToMountProto(mountPath, volume) {
71187
- const opts = volume._mountOptions ?? DEFAULT_MOUNT_OPTIONS;
71188
- return {
71189
- volumeId: volume.volumeId,
71190
- mountPath,
71191
- allowBackgroundCommits: true,
71192
- readOnly: opts.readOnly,
71193
- subPath: opts.subPath
71194
- };
71195
- }
71196
-
71197
71225
  // src/sandbox.ts
71198
71226
  var SB_LOGS_INITIAL_DELAY_MS = 10;
71199
71227
  var SB_LOGS_DELAY_FACTOR = 2;
@@ -73372,7 +73400,7 @@ var AuthTokenManager = class {
73372
73400
  };
73373
73401
 
73374
73402
  // src/version.ts
73375
- var SDK_VERSION = "0.10.2-dev.5";
73403
+ var SDK_VERSION = "0.10.2-dev.6";
73376
73404
  function getSDKVersion() {
73377
73405
  return SDK_VERSION;
73378
73406
  }
package/dist/index.d.cts CHANGED
@@ -8893,6 +8893,8 @@ type SidecarCreateParams = {
8893
8893
  secrets?: Secret[];
8894
8894
  /** Working directory of the sidecar container. */
8895
8895
  workdir?: string;
8896
+ /** Mount points for Modal {@link Volume}s. */
8897
+ volumes?: Record<string, Volume>;
8896
8898
  /**
8897
8899
  * List of CIDRs the sidecar is allowed to access. Independent of the main
8898
8900
  * container; if not set, all CIDRs are allowed. An empty list blocks all
package/dist/index.d.ts CHANGED
@@ -8893,6 +8893,8 @@ type SidecarCreateParams = {
8893
8893
  secrets?: Secret[];
8894
8894
  /** Working directory of the sidecar container. */
8895
8895
  workdir?: string;
8896
+ /** Mount points for Modal {@link Volume}s. */
8897
+ volumes?: Record<string, Volume>;
8896
8898
  /**
8897
8899
  * List of CIDRs the sidecar is allowed to access. Independent of the main
8898
8900
  * container; if not set, all CIDRs are allowed. An empty list blocks all
package/dist/index.js CHANGED
@@ -70475,8 +70475,151 @@ var SandboxFilesystem = class {
70475
70475
  };
70476
70476
 
70477
70477
  // src/sandbox_sidecar.ts
70478
- import { ClientError as ClientError9, Status as Status9 } from "nice-grpc";
70478
+ import { ClientError as ClientError10, Status as Status10 } from "nice-grpc";
70479
70479
  import { v4 as uuidv4 } from "uuid";
70480
+
70481
+ // src/volume.ts
70482
+ import { ClientError as ClientError9, Status as Status9 } from "nice-grpc";
70483
+ var VolumeService = class {
70484
+ #client;
70485
+ constructor(client) {
70486
+ this.#client = client;
70487
+ }
70488
+ /**
70489
+ * Reference a {@link Volume} by its name.
70490
+ */
70491
+ async fromName(name, params) {
70492
+ try {
70493
+ const resp = await this.#client.cpClient.volumeGetOrCreate({
70494
+ deploymentName: name,
70495
+ environmentName: this.#client.environmentName(params?.environment),
70496
+ objectCreationType: params?.createIfMissing ? 1 /* OBJECT_CREATION_TYPE_CREATE_IF_MISSING */ : 0 /* OBJECT_CREATION_TYPE_UNSPECIFIED */
70497
+ });
70498
+ this.#client.logger.debug(
70499
+ "Retrieved Volume",
70500
+ "volume_id",
70501
+ resp.volumeId,
70502
+ "volume_name",
70503
+ name
70504
+ );
70505
+ return new Volume(resp.volumeId, name);
70506
+ } catch (err) {
70507
+ if (err instanceof ClientError9 && err.code === Status9.NOT_FOUND)
70508
+ throw new NotFoundError(err.details);
70509
+ throw err;
70510
+ }
70511
+ }
70512
+ /**
70513
+ * Create a nameless, temporary {@link Volume}.
70514
+ * It persists until closeEphemeral() is called, or the process exits.
70515
+ */
70516
+ async ephemeral(params = {}) {
70517
+ const resp = await this.#client.cpClient.volumeGetOrCreate({
70518
+ objectCreationType: 5 /* OBJECT_CREATION_TYPE_EPHEMERAL */,
70519
+ environmentName: this.#client.environmentName(params.environment)
70520
+ });
70521
+ this.#client.logger.debug(
70522
+ "Created ephemeral Volume",
70523
+ "volume_id",
70524
+ resp.volumeId
70525
+ );
70526
+ const ephemeralHbManager = new EphemeralHeartbeatManager(
70527
+ () => this.#client.cpClient.volumeHeartbeat({ volumeId: resp.volumeId })
70528
+ );
70529
+ return new Volume(resp.volumeId, void 0, void 0, ephemeralHbManager);
70530
+ }
70531
+ /**
70532
+ * Delete a named {@link Volume}.
70533
+ *
70534
+ * Warning: Deletion is irreversible and will affect any Apps currently using the Volume.
70535
+ */
70536
+ async delete(name, params) {
70537
+ try {
70538
+ const volume = await this.fromName(name, {
70539
+ environment: params?.environment,
70540
+ createIfMissing: false
70541
+ });
70542
+ await this.#client.cpClient.volumeDelete({
70543
+ volumeId: volume.volumeId
70544
+ });
70545
+ this.#client.logger.debug(
70546
+ "Deleted Volume",
70547
+ "volume_name",
70548
+ name,
70549
+ "volume_id",
70550
+ volume.volumeId
70551
+ );
70552
+ } catch (err) {
70553
+ const isNotFound = err instanceof NotFoundError || err instanceof ClientError9 && err.code === Status9.NOT_FOUND;
70554
+ if (isNotFound && params?.allowMissing) {
70555
+ return;
70556
+ }
70557
+ throw err;
70558
+ }
70559
+ }
70560
+ };
70561
+ var DEFAULT_MOUNT_OPTIONS = {
70562
+ readOnly: false,
70563
+ subPath: void 0
70564
+ };
70565
+ var Volume = class _Volume {
70566
+ volumeId;
70567
+ name;
70568
+ /**
70569
+ * @hidden
70570
+ * @internal
70571
+ */
70572
+ _mountOptions = DEFAULT_MOUNT_OPTIONS;
70573
+ #ephemeralHbManager;
70574
+ /** @ignore */
70575
+ constructor(volumeId, name, mountOptions, ephemeralHbManager) {
70576
+ this.volumeId = volumeId;
70577
+ this.name = name;
70578
+ if (mountOptions) {
70579
+ this._mountOptions = mountOptions;
70580
+ }
70581
+ this.#ephemeralHbManager = ephemeralHbManager;
70582
+ }
70583
+ /**
70584
+ * Configure options used when mounting this Volume. Fields left undefined preserve their value
70585
+ * from any previous withMountOptions call on the same Volume (stacking).
70586
+ */
70587
+ withMountOptions(params = {}) {
70588
+ let subPath = this._mountOptions.subPath;
70589
+ if (params.subPath !== void 0) {
70590
+ subPath = params.subPath === "/" ? void 0 : params.subPath;
70591
+ }
70592
+ return new _Volume(
70593
+ this.volumeId,
70594
+ this.name,
70595
+ {
70596
+ readOnly: params.readOnly ?? this._mountOptions.readOnly,
70597
+ subPath
70598
+ },
70599
+ this.#ephemeralHbManager
70600
+ );
70601
+ }
70602
+ /** Delete the ephemeral Volume. Only usable with emphemeral Volumes. */
70603
+ closeEphemeral() {
70604
+ if (this.#ephemeralHbManager) {
70605
+ this.#ephemeralHbManager.stop();
70606
+ } else {
70607
+ throw new InvalidError("Volume is not ephemeral.");
70608
+ }
70609
+ }
70610
+ };
70611
+ function volumeToMountProto(mountPath, volume) {
70612
+ const opts = volume._mountOptions ?? DEFAULT_MOUNT_OPTIONS;
70613
+ return {
70614
+ volumeId: volume.volumeId,
70615
+ mountPath,
70616
+ allowBackgroundCommits: true,
70617
+ readOnly: opts.readOnly,
70618
+ subPath: opts.subPath
70619
+ };
70620
+ }
70621
+
70622
+ // src/sandbox_sidecar.ts
70480
70623
  var MAIN_CONTAINER_NAME = "main";
70481
70624
  var CONTAINER_WAIT_POLL_TIMEOUT_SECONDS = 10;
70482
70625
  var CONTROL_PLANE_SIDECAR_CREATE_ENV_VAR = "MODAL_USE_CONTROL_PLANE_SIDECAR_CREATE";
@@ -70493,6 +70636,29 @@ function validateSidecarName(name) {
70493
70636
  );
70494
70637
  }
70495
70638
  }
70639
+ function buildSidecarVolumeMounts(volumes) {
70640
+ const mountPathsByVolumeId = /* @__PURE__ */ new Map();
70641
+ const volumeMounts = [];
70642
+ for (const [mountPath, volume] of Object.entries(volumes ?? {})) {
70643
+ if (!volume) {
70644
+ throw new InvalidError(
70645
+ `Volume mounted at "${mountPath}" must not be null or undefined`
70646
+ );
70647
+ }
70648
+ const mountPaths = mountPathsByVolumeId.get(volume.volumeId) ?? [];
70649
+ mountPaths.push(mountPath);
70650
+ mountPathsByVolumeId.set(volume.volumeId, mountPaths);
70651
+ volumeMounts.push(volumeToMountProto(mountPath, volume));
70652
+ }
70653
+ for (const mountPaths of mountPathsByVolumeId.values()) {
70654
+ if (mountPaths.length > 1) {
70655
+ throw new InvalidError(
70656
+ "The same Volume cannot be mounted at multiple paths in a sidecar: " + mountPaths.sort().join(", ")
70657
+ );
70658
+ }
70659
+ }
70660
+ return volumeMounts;
70661
+ }
70496
70662
  function sidecarContainerFromProto(access, info) {
70497
70663
  return new SidecarContainer(
70498
70664
  access,
@@ -70530,6 +70696,7 @@ var SidecarService = class {
70530
70696
  Object.assign(envDict, params?.env ?? {});
70531
70697
  await hydrateSecrets(this.#access.client, resolvableSecrets);
70532
70698
  const secretIds = collectSecretIds(resolvableSecrets);
70699
+ const volumeMounts = buildSidecarVolumeMounts(params?.volumes);
70533
70700
  const ptyInfo = params?.pty ? defaultSandboxPTYInfo() : void 0;
70534
70701
  const networkAccess = buildOutboundNetworkAccess(
70535
70702
  false,
@@ -70549,6 +70716,7 @@ var SidecarService = class {
70549
70716
  entrypointArgs: command,
70550
70717
  workdir: params?.workdir ?? void 0,
70551
70718
  secretIds,
70719
+ volumeMounts,
70552
70720
  networkAccess,
70553
70721
  ptyInfo
70554
70722
  }),
@@ -70566,17 +70734,18 @@ var SidecarService = class {
70566
70734
  env: envDict,
70567
70735
  workdir: params?.workdir ?? "",
70568
70736
  secretIds,
70737
+ volumeMounts,
70569
70738
  networkAccess,
70570
70739
  ptyInfo
70571
70740
  })
70572
70741
  );
70573
70742
  }
70574
70743
  } catch (err) {
70575
- if (err instanceof ClientError9) {
70576
- if (err.code === Status9.ALREADY_EXISTS) {
70744
+ if (err instanceof ClientError10) {
70745
+ if (err.code === Status10.ALREADY_EXISTS) {
70577
70746
  throw new AlreadyExistsError(err.details || err.message);
70578
70747
  }
70579
- if (err.code === Status9.INVALID_ARGUMENT) {
70748
+ if (err.code === Status10.INVALID_ARGUMENT) {
70580
70749
  throw new InvalidError(err.details || err.message);
70581
70750
  }
70582
70751
  }
@@ -70602,7 +70771,7 @@ var SidecarService = class {
70602
70771
  })
70603
70772
  );
70604
70773
  } catch (err) {
70605
- if (err instanceof ClientError9 && err.code === Status9.NOT_FOUND) {
70774
+ if (err instanceof ClientError10 && err.code === Status10.NOT_FOUND) {
70606
70775
  throw new NotFoundError(`Sidecar container "${name}" not found`);
70607
70776
  }
70608
70777
  throw err;
@@ -70973,147 +71142,6 @@ var SandboxSnapshotService = class {
70973
71142
  }
70974
71143
  };
70975
71144
 
70976
- // src/volume.ts
70977
- import { ClientError as ClientError10, Status as Status10 } from "nice-grpc";
70978
- var VolumeService = class {
70979
- #client;
70980
- constructor(client) {
70981
- this.#client = client;
70982
- }
70983
- /**
70984
- * Reference a {@link Volume} by its name.
70985
- */
70986
- async fromName(name, params) {
70987
- try {
70988
- const resp = await this.#client.cpClient.volumeGetOrCreate({
70989
- deploymentName: name,
70990
- environmentName: this.#client.environmentName(params?.environment),
70991
- objectCreationType: params?.createIfMissing ? 1 /* OBJECT_CREATION_TYPE_CREATE_IF_MISSING */ : 0 /* OBJECT_CREATION_TYPE_UNSPECIFIED */
70992
- });
70993
- this.#client.logger.debug(
70994
- "Retrieved Volume",
70995
- "volume_id",
70996
- resp.volumeId,
70997
- "volume_name",
70998
- name
70999
- );
71000
- return new Volume(resp.volumeId, name);
71001
- } catch (err) {
71002
- if (err instanceof ClientError10 && err.code === Status10.NOT_FOUND)
71003
- throw new NotFoundError(err.details);
71004
- throw err;
71005
- }
71006
- }
71007
- /**
71008
- * Create a nameless, temporary {@link Volume}.
71009
- * It persists until closeEphemeral() is called, or the process exits.
71010
- */
71011
- async ephemeral(params = {}) {
71012
- const resp = await this.#client.cpClient.volumeGetOrCreate({
71013
- objectCreationType: 5 /* OBJECT_CREATION_TYPE_EPHEMERAL */,
71014
- environmentName: this.#client.environmentName(params.environment)
71015
- });
71016
- this.#client.logger.debug(
71017
- "Created ephemeral Volume",
71018
- "volume_id",
71019
- resp.volumeId
71020
- );
71021
- const ephemeralHbManager = new EphemeralHeartbeatManager(
71022
- () => this.#client.cpClient.volumeHeartbeat({ volumeId: resp.volumeId })
71023
- );
71024
- return new Volume(resp.volumeId, void 0, void 0, ephemeralHbManager);
71025
- }
71026
- /**
71027
- * Delete a named {@link Volume}.
71028
- *
71029
- * Warning: Deletion is irreversible and will affect any Apps currently using the Volume.
71030
- */
71031
- async delete(name, params) {
71032
- try {
71033
- const volume = await this.fromName(name, {
71034
- environment: params?.environment,
71035
- createIfMissing: false
71036
- });
71037
- await this.#client.cpClient.volumeDelete({
71038
- volumeId: volume.volumeId
71039
- });
71040
- this.#client.logger.debug(
71041
- "Deleted Volume",
71042
- "volume_name",
71043
- name,
71044
- "volume_id",
71045
- volume.volumeId
71046
- );
71047
- } catch (err) {
71048
- const isNotFound = err instanceof NotFoundError || err instanceof ClientError10 && err.code === Status10.NOT_FOUND;
71049
- if (isNotFound && params?.allowMissing) {
71050
- return;
71051
- }
71052
- throw err;
71053
- }
71054
- }
71055
- };
71056
- var DEFAULT_MOUNT_OPTIONS = {
71057
- readOnly: false,
71058
- subPath: void 0
71059
- };
71060
- var Volume = class _Volume {
71061
- volumeId;
71062
- name;
71063
- /**
71064
- * @hidden
71065
- * @internal
71066
- */
71067
- _mountOptions = DEFAULT_MOUNT_OPTIONS;
71068
- #ephemeralHbManager;
71069
- /** @ignore */
71070
- constructor(volumeId, name, mountOptions, ephemeralHbManager) {
71071
- this.volumeId = volumeId;
71072
- this.name = name;
71073
- if (mountOptions) {
71074
- this._mountOptions = mountOptions;
71075
- }
71076
- this.#ephemeralHbManager = ephemeralHbManager;
71077
- }
71078
- /**
71079
- * Configure options used when mounting this Volume. Fields left undefined preserve their value
71080
- * from any previous withMountOptions call on the same Volume (stacking).
71081
- */
71082
- withMountOptions(params = {}) {
71083
- let subPath = this._mountOptions.subPath;
71084
- if (params.subPath !== void 0) {
71085
- subPath = params.subPath === "/" ? void 0 : params.subPath;
71086
- }
71087
- return new _Volume(
71088
- this.volumeId,
71089
- this.name,
71090
- {
71091
- readOnly: params.readOnly ?? this._mountOptions.readOnly,
71092
- subPath
71093
- },
71094
- this.#ephemeralHbManager
71095
- );
71096
- }
71097
- /** Delete the ephemeral Volume. Only usable with emphemeral Volumes. */
71098
- closeEphemeral() {
71099
- if (this.#ephemeralHbManager) {
71100
- this.#ephemeralHbManager.stop();
71101
- } else {
71102
- throw new InvalidError("Volume is not ephemeral.");
71103
- }
71104
- }
71105
- };
71106
- function volumeToMountProto(mountPath, volume) {
71107
- const opts = volume._mountOptions ?? DEFAULT_MOUNT_OPTIONS;
71108
- return {
71109
- volumeId: volume.volumeId,
71110
- mountPath,
71111
- allowBackgroundCommits: true,
71112
- readOnly: opts.readOnly,
71113
- subPath: opts.subPath
71114
- };
71115
- }
71116
-
71117
71145
  // src/sandbox.ts
71118
71146
  var SB_LOGS_INITIAL_DELAY_MS = 10;
71119
71147
  var SB_LOGS_DELAY_FACTOR = 2;
@@ -73292,7 +73320,7 @@ var AuthTokenManager = class {
73292
73320
  };
73293
73321
 
73294
73322
  // src/version.ts
73295
- var SDK_VERSION = "0.10.2-dev.5";
73323
+ var SDK_VERSION = "0.10.2-dev.6";
73296
73324
  function getSDKVersion() {
73297
73325
  return SDK_VERSION;
73298
73326
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modal",
3
- "version": "0.10.2-dev.5",
3
+ "version": "0.10.2-dev.6",
4
4
  "description": "Modal SDK for JavaScript/TypeScript",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://modal.com/docs/guide/sdk-javascript-go",