modal 0.3.5 → 0.3.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.
package/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # Modal JavaScript Library
2
2
 
3
+ [![Documentation](https://img.shields.io/badge/docs-reference-blue)](https://modal-labs.github.io/libmodal/)
3
4
  [![Version](https://img.shields.io/npm/v/modal.svg)](https://www.npmjs.org/package/modal)
4
5
  [![Build Status](https://github.com/modal-labs/libmodal/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/modal-labs/libmodal/actions?query=branch%3Amain)
5
6
  [![Downloads](https://img.shields.io/npm/dm/modal.svg)](https://www.npmjs.com/package/modal)
package/dist/index.d.ts CHANGED
@@ -1,7 +1,59 @@
1
1
  import { BinaryWriter, BinaryReader } from '@bufbuild/protobuf/wire';
2
2
 
3
+ declare enum ParameterType {
4
+ PARAM_TYPE_UNSPECIFIED = 0,
5
+ PARAM_TYPE_STRING = 1,
6
+ PARAM_TYPE_INT = 2,
7
+ /** PARAM_TYPE_PICKLE - currently unused */
8
+ PARAM_TYPE_PICKLE = 3,
9
+ PARAM_TYPE_BYTES = 4,
10
+ /** PARAM_TYPE_UNKNOWN - used in schemas to signify unrecognized or un-annotated types */
11
+ PARAM_TYPE_UNKNOWN = 5,
12
+ PARAM_TYPE_LIST = 6,
13
+ PARAM_TYPE_DICT = 7,
14
+ PARAM_TYPE_NONE = 8,
15
+ PARAM_TYPE_BOOL = 9,
16
+ UNRECOGNIZED = -1
17
+ }
18
+ /** TODO: rename into NamedPayloadType or similar */
19
+ interface ClassParameterSpec {
20
+ name: string;
21
+ /** TODO: deprecate - use full_type instead */
22
+ type: ParameterType;
23
+ hasDefault: boolean;
24
+ /** Default *values* are only registered for class parameters */
25
+ stringDefault?: string | undefined;
26
+ intDefault?: number | undefined;
27
+ pickleDefault?: Uint8Array | undefined;
28
+ bytesDefault?: Uint8Array | undefined;
29
+ boolDefault?: boolean | undefined;
30
+ /** supersedes `type` */
31
+ fullType: GenericPayloadType | undefined;
32
+ }
33
+ declare const ClassParameterSpec: MessageFns<ClassParameterSpec>;
34
+ interface GenericPayloadType {
35
+ baseType: ParameterType;
36
+ /** sub-type for generic types like lists */
37
+ subTypes: GenericPayloadType[];
38
+ }
39
+ declare const GenericPayloadType: MessageFns<GenericPayloadType>;
40
+ type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
41
+ type DeepPartial<T> = T extends Builtin ? T : T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>> : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>> : T extends {} ? {
42
+ [K in keyof T]?: DeepPartial<T[K]>;
43
+ } : Partial<T>;
44
+ interface MessageFns<T> {
45
+ encode(message: T, writer?: BinaryWriter): BinaryWriter;
46
+ decode(input: BinaryReader | Uint8Array, length?: number): T;
47
+ fromJSON(object: any): T;
48
+ toJSON(message: T): unknown;
49
+ create(base?: DeepPartial<T>): T;
50
+ fromPartial(object: DeepPartial<T>): T;
51
+ }
52
+
53
+ /** A container image, used for starting sandboxes. */
3
54
  declare class Image {
4
55
  readonly imageId: string;
56
+ /** @ignore */
5
57
  constructor(imageId: string);
6
58
  }
7
59
 
@@ -42,19 +94,33 @@ interface ModalWriteStream<R = any> extends WritableStream<R> {
42
94
  writeBytes(bytes: Uint8Array): Promise<void>;
43
95
  }
44
96
 
97
+ /**
98
+ * Stdin is always present, but this option allow you to drop stdout or stderr
99
+ * if you don't need them. The default is "pipe", matching Node.js behavior.
100
+ *
101
+ * If behavior is set to "ignore", the output streams will be empty.
102
+ */
45
103
  type StdioBehavior = "pipe" | "ignore";
104
+ /**
105
+ * Specifies the type of data that will be read from the sandbox or container
106
+ * process. "text" means the data will be read as UTF-8 text, while "binary"
107
+ * means the data will be read as raw bytes (Uint8Array).
108
+ */
46
109
  type StreamMode = "text" | "binary";
110
+ /** Options to configure a `Sandbox.exec()` operation. */
47
111
  type ExecOptions = {
48
112
  mode?: StreamMode;
49
113
  stdout?: StdioBehavior;
50
114
  stderr?: StdioBehavior;
51
115
  };
116
+ /** Sandboxes are secure, isolated containers in Modal that boot in seconds. */
52
117
  declare class Sandbox {
53
118
  #private;
54
119
  readonly sandboxId: string;
55
120
  stdin: ModalWriteStream<string>;
56
121
  stdout: ModalReadStream<string>;
57
122
  stderr: ModalReadStream<string>;
123
+ /** @ignore */
58
124
  constructor(sandboxId: string);
59
125
  exec(command: string[], options?: ExecOptions & {
60
126
  mode?: "text";
@@ -76,86 +142,64 @@ declare class ContainerProcess<R extends string | Uint8Array = any> {
76
142
  wait(): Promise<number>;
77
143
  }
78
144
 
145
+ /** Options for `Secret.fromName()`. */
146
+ type SecretFromNameOptions = {
147
+ environment?: string;
148
+ requiredKeys?: string[];
149
+ };
150
+ /** Secrets provide a dictionary of environment variables for images. */
151
+ declare class Secret {
152
+ readonly secretId: string;
153
+ /** @ignore */
154
+ constructor(secretId: string);
155
+ /** Reference a Secret by its name. */
156
+ static fromName(name: string, options?: SecretFromNameOptions): Promise<Secret>;
157
+ }
158
+
159
+ /** Options for functions that find deployed Modal objects. */
79
160
  type LookupOptions = {
80
161
  environment?: string;
81
162
  createIfMissing?: boolean;
82
163
  };
164
+ /** Options for deleting a named object. */
165
+ type DeleteOptions = {
166
+ environment?: string;
167
+ };
168
+ /** Options for constructors that create a temporary, nameless object. */
169
+ type EphemeralOptions = {
170
+ environment?: string;
171
+ };
172
+ /** Options for `App.createSandbox()`. */
83
173
  type SandboxCreateOptions = {
174
+ /** Reservation of physical CPU cores for the sandbox, can be fractional. */
84
175
  cpu?: number;
176
+ /** Reservation of memory in MiB. */
85
177
  memory?: number;
178
+ /** Timeout of the sandbox container, defaults to 10 minutes. */
86
179
  timeout?: number;
180
+ /**
181
+ * Sequence of program arguments for the main process.
182
+ * Default behavior is to sleep indefinitely until timeout or termination.
183
+ */
87
184
  command?: string[];
88
185
  };
186
+ /** Represents a deployed Modal App. */
89
187
  declare class App {
90
188
  readonly appId: string;
189
+ /** @ignore */
91
190
  constructor(appId: string);
92
191
  /** Lookup a deployed app by name, or create if it does not exist. */
93
192
  static lookup(name: string, options?: LookupOptions): Promise<App>;
94
193
  createSandbox(image: Image, options?: SandboxCreateOptions): Promise<Sandbox>;
95
194
  imageFromRegistry(tag: string): Promise<Image>;
195
+ imageFromAwsEcr(tag: string, secret: Secret): Promise<Image>;
96
196
  }
97
197
 
98
- declare enum FunctionCallInvocationType {
99
- FUNCTION_CALL_INVOCATION_TYPE_UNSPECIFIED = 0,
100
- FUNCTION_CALL_INVOCATION_TYPE_SYNC_LEGACY = 1,
101
- FUNCTION_CALL_INVOCATION_TYPE_ASYNC_LEGACY = 2,
102
- FUNCTION_CALL_INVOCATION_TYPE_ASYNC = 3,
103
- FUNCTION_CALL_INVOCATION_TYPE_SYNC = 4,
104
- UNRECOGNIZED = -1
105
- }
106
- declare enum ParameterType {
107
- PARAM_TYPE_UNSPECIFIED = 0,
108
- PARAM_TYPE_STRING = 1,
109
- PARAM_TYPE_INT = 2,
110
- /** PARAM_TYPE_PICKLE - currently unused */
111
- PARAM_TYPE_PICKLE = 3,
112
- PARAM_TYPE_BYTES = 4,
113
- /** PARAM_TYPE_UNKNOWN - used in schemas to signify unrecognized or un-annotated types */
114
- PARAM_TYPE_UNKNOWN = 5,
115
- PARAM_TYPE_LIST = 6,
116
- PARAM_TYPE_DICT = 7,
117
- PARAM_TYPE_NONE = 8,
118
- PARAM_TYPE_BOOL = 9,
119
- UNRECOGNIZED = -1
120
- }
121
- /** TODO: rename into NamedPayloadType or similar */
122
- interface ClassParameterSpec {
123
- name: string;
124
- /** TODO: deprecate - use full_type instead */
125
- type: ParameterType;
126
- hasDefault: boolean;
127
- /** Default *values* are only registered for class parameters */
128
- stringDefault?: string | undefined;
129
- intDefault?: number | undefined;
130
- pickleDefault?: Uint8Array | undefined;
131
- bytesDefault?: Uint8Array | undefined;
132
- boolDefault?: boolean | undefined;
133
- /** supersedes `type` */
134
- fullType: GenericPayloadType | undefined;
135
- }
136
- declare const ClassParameterSpec: MessageFns<ClassParameterSpec>;
137
- interface GenericPayloadType {
138
- baseType: ParameterType;
139
- /** sub-type for generic types like lists */
140
- subTypes: GenericPayloadType[];
141
- }
142
- declare const GenericPayloadType: MessageFns<GenericPayloadType>;
143
- type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
144
- type DeepPartial<T> = T extends Builtin ? T : T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>> : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>> : T extends {} ? {
145
- [K in keyof T]?: DeepPartial<T[K]>;
146
- } : Partial<T>;
147
- interface MessageFns<T> {
148
- encode(message: T, writer?: BinaryWriter): BinaryWriter;
149
- decode(input: BinaryReader | Uint8Array, length?: number): T;
150
- fromJSON(object: any): T;
151
- toJSON(message: T): unknown;
152
- create(base?: DeepPartial<T>): T;
153
- fromPartial(object: DeepPartial<T>): T;
154
- }
155
-
198
+ /** Options for `FunctionCall.get()`. */
156
199
  type FunctionCallGetOptions = {
157
200
  timeout?: number;
158
201
  };
202
+ /** Options for `FunctionCall.cancel()`. */
159
203
  type FunctionCallCancelOptions = {
160
204
  terminateContainers?: boolean;
161
205
  };
@@ -166,6 +210,7 @@ type FunctionCallCancelOptions = {
166
210
  */
167
211
  declare class FunctionCall {
168
212
  readonly functionCallId: string;
213
+ /** @ignore */
169
214
  constructor(functionCallId: string);
170
215
  /** Create a new function call from ID. */
171
216
  fromId(functionCallId: string): FunctionCall;
@@ -177,18 +222,20 @@ declare class FunctionCall {
177
222
 
178
223
  /** Represents a deployed Modal Function, which can be invoked remotely. */
179
224
  declare class Function_ {
225
+ #private;
180
226
  readonly functionId: string;
181
227
  readonly methodName: string | undefined;
228
+ /** @ignore */
182
229
  constructor(functionId: string, methodName?: string);
183
230
  static lookup(appName: string, name: string, options?: LookupOptions): Promise<Function_>;
184
231
  remote(args?: any[], kwargs?: Record<string, any>): Promise<any>;
185
232
  spawn(args?: any[], kwargs?: Record<string, any>): Promise<FunctionCall>;
186
- execFunctionCall(args?: any[], kwargs?: Record<string, any>, invocationType?: FunctionCallInvocationType): Promise<string>;
187
233
  }
188
234
 
189
235
  /** Represents a deployed Modal Cls. */
190
236
  declare class Cls {
191
237
  #private;
238
+ /** @ignore */
192
239
  constructor(serviceFunctionId: string, schema: ClassParameterSpec[], methodNames: string[]);
193
240
  static lookup(appName: string, name: string, options?: LookupOptions): Promise<Cls>;
194
241
  /** Create a new instance of the Cls with parameters. */
@@ -217,5 +264,117 @@ declare class InternalFailure extends Error {
217
264
  declare class NotFoundError extends Error {
218
265
  constructor(message: string);
219
266
  }
267
+ /** A request or other operation was invalid. */
268
+ declare class InvalidError extends Error {
269
+ constructor(message: string);
270
+ }
271
+ /** The queue is empty. */
272
+ declare class QueueEmptyError extends Error {
273
+ constructor(message: string);
274
+ }
275
+ /** The queue is full. */
276
+ declare class QueueFullError extends Error {
277
+ constructor(message: string);
278
+ }
279
+
280
+ /** Options to configure a `Queue.clear()` operation. */
281
+ type QueueClearOptions = {
282
+ /** Partition to clear, uses default partition if not set. */
283
+ partition?: string;
284
+ /** Set to clear all queue partitions. */
285
+ all?: boolean;
286
+ };
287
+ /** Options to configure a `Queue.get()` or `Queue.getMany()` operation. */
288
+ type QueueGetOptions = {
289
+ /** How long to wait if the queue is empty (default: indefinite). */
290
+ timeout?: number;
291
+ /** Partition to fetch values from, uses default partition if not set. */
292
+ partition?: string;
293
+ };
294
+ /** Options to configure a `Queue.put()` or `Queue.putMany()` operation. */
295
+ type QueuePutOptions = {
296
+ /** How long to wait if the queue is full (default: indefinite). */
297
+ timeout?: number;
298
+ /** Partition to add items to, uses default partition if not set. */
299
+ partition?: string;
300
+ /** TTL for the partition in seconds (default: 1 day). */
301
+ partitionTtl?: number;
302
+ };
303
+ /** Options to configure a `Queue.len()` operation. */
304
+ type QueueLenOptions = {
305
+ /** Partition to compute length, uses default partition if not set. */
306
+ partition?: string;
307
+ /** Return the total length across all partitions. */
308
+ total?: boolean;
309
+ };
310
+ /** Options to configure a `Queue.iterate()` operation. */
311
+ type QueueIterateOptions = {
312
+ /** How long to wait between successive items before exiting iteration (default: 0). */
313
+ itemPollTimeout?: number;
314
+ /** Partition to iterate, uses default partition if not set. */
315
+ partition?: string;
316
+ };
317
+ /**
318
+ * Distributed, FIFO queue for data flow in Modal apps.
319
+ */
320
+ declare class Queue {
321
+ #private;
322
+ readonly queueId: string;
323
+ /** @ignore */
324
+ constructor(queueId: string, ephemeral?: boolean);
325
+ /**
326
+ * Create a nameless, temporary queue.
327
+ * You will need to call `closeEphemeral()` to delete the queue.
328
+ */
329
+ static ephemeral(options?: EphemeralOptions): Promise<Queue>;
330
+ /** Delete the ephemeral queue. Only usable with `Queue.ephemeral()`. */
331
+ closeEphemeral(): void;
332
+ /**
333
+ * Lookup a queue by name.
334
+ */
335
+ static lookup(name: string, options?: LookupOptions): Promise<Queue>;
336
+ /** Delete a queue by name. */
337
+ static delete(name: string, options?: DeleteOptions): Promise<void>;
338
+ /**
339
+ * Remove all objects from a queue partition.
340
+ */
341
+ clear(options?: QueueClearOptions): Promise<void>;
342
+ /**
343
+ * Remove and return the next object from the queue.
344
+ *
345
+ * By default, this will wait until at least one item is present in the queue.
346
+ * If `timeout` is set, raises `QueueEmptyError` if no items are available
347
+ * within that timeout in milliseconds.
348
+ */
349
+ get(options?: QueueGetOptions): Promise<any | null>;
350
+ /**
351
+ * Remove and return up to `n` objects from the queue.
352
+ *
353
+ * By default, this will wait until at least one item is present in the queue.
354
+ * If `timeout` is set, raises `QueueEmptyError` if no items are available
355
+ * within that timeout in milliseconds.
356
+ */
357
+ getMany(n: number, options?: QueueGetOptions): Promise<any[]>;
358
+ /**
359
+ * Add an item to the end of the queue.
360
+ *
361
+ * If the queue is full, this will retry with exponential backoff until the
362
+ * provided `timeout` is reached, or indefinitely if `timeout` is not set.
363
+ * Raises `QueueFullError` if the queue is still full after the timeout.
364
+ */
365
+ put(v: any, options?: QueuePutOptions): Promise<void>;
366
+ /**
367
+ * Add several items to the end of the queue.
368
+ *
369
+ * If the queue is full, this will retry with exponential backoff until the
370
+ * provided `timeout` is reached, or indefinitely if `timeout` is not set.
371
+ * Raises `QueueFullError` if the queue is still full after the timeout.
372
+ */
373
+ putMany(values: any[], options?: QueuePutOptions): Promise<void>;
374
+ /** Return the number of objects in the queue. */
375
+ len(options?: QueueLenOptions): Promise<number>;
376
+ /** Iterate through items in a queue without mutation. */
377
+ iterate(options?: QueueIterateOptions): AsyncGenerator<any, void, unknown>;
378
+ }
220
379
 
221
- export { App, Cls, ClsInstance, FunctionCall, type FunctionCallCancelOptions, type FunctionCallGetOptions, FunctionTimeoutError, Function_, Image, InternalFailure, type LookupOptions, NotFoundError, RemoteError, Sandbox, type SandboxCreateOptions, type StdioBehavior, type StreamMode };
380
+ export { App, Cls, ClsInstance, ContainerProcess, type DeleteOptions, type EphemeralOptions, type ExecOptions, FunctionCall, type FunctionCallCancelOptions, type FunctionCallGetOptions, FunctionTimeoutError, Function_, Image, InternalFailure, InvalidError, type LookupOptions, type ModalReadStream, type ModalWriteStream, NotFoundError, Queue, type QueueClearOptions, QueueEmptyError, QueueFullError, type QueueGetOptions, type QueueIterateOptions, type QueueLenOptions, type QueuePutOptions, RemoteError, Sandbox, type SandboxCreateOptions, Secret, type SecretFromNameOptions, type StdioBehavior, type StreamMode };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/app.ts
2
- import { ClientError as ClientError2, Status as Status2 } from "nice-grpc";
2
+ import { ClientError as ClientError3, Status as Status3 } from "nice-grpc";
3
3
 
4
4
  // node_modules/@bufbuild/protobuf/dist/esm/wire/varint.js
5
5
  function varint64read() {
@@ -37831,11 +37831,12 @@ function getProfile(profileName) {
37831
37831
  );
37832
37832
  }
37833
37833
  const profileData = profileName ? config[profileName] : {};
37834
- let profile2 = {
37834
+ const profile2 = {
37835
37835
  serverUrl: process.env["MODAL_SERVER_URL"] || profileData.server_url || "https://api.modal.com:443",
37836
37836
  tokenId: process.env["MODAL_TOKEN_ID"] || profileData.token_id,
37837
37837
  tokenSecret: process.env["MODAL_TOKEN_SECRET"] || profileData.token_secret,
37838
- environment: process.env["MODAL_ENVIRONMENT"] || profileData.environment
37838
+ environment: process.env["MODAL_ENVIRONMENT"] || profileData.environment,
37839
+ imageBuilderVersion: process.env["MODAL_IMAGE_BUILDER_VERSION"] || profileData.imageBuilderVersion
37839
37840
  };
37840
37841
  if (!profile2.tokenId || !profile2.tokenSecret) {
37841
37842
  throw new Error(
@@ -37848,6 +37849,9 @@ var profile = getProfile(process.env["MODAL_PROFILE"] || void 0);
37848
37849
  function environmentName(environment) {
37849
37850
  return environment || profile.environment || "";
37850
37851
  }
37852
+ function imageBuilderVersion(version) {
37853
+ return version || profile.imageBuilderVersion || "2024.10";
37854
+ }
37851
37855
 
37852
37856
  // src/client.ts
37853
37857
  function authMiddleware(profile2) {
@@ -37975,19 +37979,20 @@ var client = createClientFactory().use(authMiddleware(profile)).use(retryMiddlew
37975
37979
  // src/image.ts
37976
37980
  var Image2 = class {
37977
37981
  imageId;
37982
+ /** @ignore */
37978
37983
  constructor(imageId) {
37979
37984
  this.imageId = imageId;
37980
37985
  }
37981
37986
  };
37982
- async function fromRegistryInternal(appId, tag) {
37987
+ async function fromRegistryInternal(appId, tag, imageRegistryConfig) {
37983
37988
  const resp = await client.imageGetOrCreate({
37984
37989
  appId,
37985
37990
  image: {
37986
- dockerfileCommands: [`FROM ${tag}`]
37991
+ dockerfileCommands: [`FROM ${tag}`],
37992
+ imageRegistryConfig
37987
37993
  },
37988
37994
  namespace: 1 /* DEPLOYMENT_NAMESPACE_WORKSPACE */,
37989
- builderVersion: "2024.10"
37990
- // TODO: make this configurable
37995
+ builderVersion: imageBuilderVersion()
37991
37996
  });
37992
37997
  let result;
37993
37998
  let metadata = void 0;
@@ -38013,6 +38018,7 @@ async function fromRegistryInternal(appId, tag) {
38013
38018
  }
38014
38019
  result = resultJoined;
38015
38020
  }
38021
+ void metadata;
38016
38022
  if (result.status === 2 /* GENERIC_STATUS_FAILURE */) {
38017
38023
  throw new Error(
38018
38024
  `Image build for ${resp.imageId} failed with the exception:
@@ -38151,6 +38157,7 @@ var Sandbox2 = class {
38151
38157
  stdout;
38152
38158
  stderr;
38153
38159
  #taskId;
38160
+ /** @ignore */
38154
38161
  constructor(sandboxId) {
38155
38162
  this.sandboxId = sandboxId;
38156
38163
  this.stdin = toModalWriteStream(inputStreamSb(sandboxId));
@@ -38381,10 +38388,57 @@ var NotFoundError = class extends Error {
38381
38388
  this.name = "NotFoundError";
38382
38389
  }
38383
38390
  };
38391
+ var InvalidError = class extends Error {
38392
+ constructor(message) {
38393
+ super(message);
38394
+ this.name = "InvalidError";
38395
+ }
38396
+ };
38397
+ var QueueEmptyError = class extends Error {
38398
+ constructor(message) {
38399
+ super(message);
38400
+ this.name = "QueueEmptyError";
38401
+ }
38402
+ };
38403
+ var QueueFullError = class extends Error {
38404
+ constructor(message) {
38405
+ super(message);
38406
+ this.name = "QueueFullError";
38407
+ }
38408
+ };
38409
+
38410
+ // src/secret.ts
38411
+ import { ClientError as ClientError2, Status as Status2 } from "nice-grpc";
38412
+ var Secret = class _Secret {
38413
+ secretId;
38414
+ /** @ignore */
38415
+ constructor(secretId) {
38416
+ this.secretId = secretId;
38417
+ }
38418
+ /** Reference a Secret by its name. */
38419
+ static async fromName(name, options) {
38420
+ try {
38421
+ const resp = await client.secretGetOrCreate({
38422
+ deploymentName: name,
38423
+ namespace: 1 /* DEPLOYMENT_NAMESPACE_WORKSPACE */,
38424
+ environmentName: environmentName(options?.environment),
38425
+ requiredKeys: options?.requiredKeys ?? []
38426
+ });
38427
+ return new _Secret(resp.secretId);
38428
+ } catch (err) {
38429
+ if (err instanceof ClientError2 && err.code === Status2.NOT_FOUND)
38430
+ throw new NotFoundError(err.details);
38431
+ if (err instanceof ClientError2 && err.code === Status2.FAILED_PRECONDITION && err.details.includes("Secret is missing key"))
38432
+ throw new NotFoundError(err.details);
38433
+ throw err;
38434
+ }
38435
+ }
38436
+ };
38384
38437
 
38385
38438
  // src/app.ts
38386
38439
  var App = class _App {
38387
38440
  appId;
38441
+ /** @ignore */
38388
38442
  constructor(appId) {
38389
38443
  this.appId = appId;
38390
38444
  }
@@ -38398,7 +38452,7 @@ var App = class _App {
38398
38452
  });
38399
38453
  return new _App(resp.appId);
38400
38454
  } catch (err) {
38401
- if (err instanceof ClientError2 && err.code === Status2.NOT_FOUND)
38455
+ if (err instanceof ClientError3 && err.code === Status3.NOT_FOUND)
38402
38456
  throw new NotFoundError(`App '${name}' not found`);
38403
38457
  throw err;
38404
38458
  }
@@ -38431,10 +38485,22 @@ var App = class _App {
38431
38485
  async imageFromRegistry(tag) {
38432
38486
  return await fromRegistryInternal(this.appId, tag);
38433
38487
  }
38488
+ async imageFromAwsEcr(tag, secret) {
38489
+ if (!(secret instanceof Secret)) {
38490
+ throw new TypeError(
38491
+ "secret must be a reference to an existing Secret, e.g. `await Secret.fromName('my_secret')`"
38492
+ );
38493
+ }
38494
+ const imageRegistryConfig = {
38495
+ registryAuthType: 1 /* REGISTRY_AUTH_TYPE_AWS */,
38496
+ secretId: secret.secretId
38497
+ };
38498
+ return await fromRegistryInternal(this.appId, tag, imageRegistryConfig);
38499
+ }
38434
38500
  };
38435
38501
 
38436
38502
  // src/cls.ts
38437
- import { ClientError as ClientError4, Status as Status4 } from "nice-grpc";
38503
+ import { ClientError as ClientError5, Status as Status5 } from "nice-grpc";
38438
38504
 
38439
38505
  // src/function.ts
38440
38506
  import { createHash } from "node:crypto";
@@ -38442,6 +38508,7 @@ import { createHash } from "node:crypto";
38442
38508
  // src/function_call.ts
38443
38509
  var FunctionCall = class _FunctionCall {
38444
38510
  functionCallId;
38511
+ /** @ignore */
38445
38512
  constructor(functionCallId) {
38446
38513
  this.functionCallId = functionCallId;
38447
38514
  }
@@ -38525,6 +38592,15 @@ var Reader = class {
38525
38592
  const hi = this.uint32LE() >>> 0;
38526
38593
  return hi * 2 ** 32 + lo;
38527
38594
  }
38595
+ int32LE() {
38596
+ const v = new DataView(
38597
+ this.buf.buffer,
38598
+ this.buf.byteOffset + this.pos,
38599
+ 4
38600
+ ).getInt32(0, true);
38601
+ this.pos += 4;
38602
+ return v;
38603
+ }
38528
38604
  float64BE() {
38529
38605
  const v = new DataView(
38530
38606
  this.buf.buffer,
@@ -38570,7 +38646,7 @@ function encodeValue(val, w, proto) {
38570
38646
  w.byte(140 /* SHORT_BINUNICODE */);
38571
38647
  w.byte(utf8.length);
38572
38648
  } else if (proto >= 4 && utf8.length > 4294967295) {
38573
- w.byte(138 /* BINUNICODE8 */);
38649
+ w.byte(141 /* BINUNICODE8 */);
38574
38650
  w.uint64LE(utf8.length);
38575
38651
  } else {
38576
38652
  w.byte(88 /* BINUNICODE */);
@@ -38583,10 +38659,10 @@ function encodeValue(val, w, proto) {
38583
38659
  if (val instanceof Uint8Array) {
38584
38660
  const len = val.length;
38585
38661
  if (proto >= 4 && len < 256) {
38586
- w.byte(142 /* SHORT_BINBYTES */);
38662
+ w.byte(67 /* SHORT_BINBYTES */);
38587
38663
  w.byte(len);
38588
38664
  } else if (proto >= 4 && len > 4294967295) {
38589
- w.byte(141 /* BINBYTES8 */);
38665
+ w.byte(142 /* BINBYTES8 */);
38590
38666
  w.uint64LE(len);
38591
38667
  } else {
38592
38668
  w.byte(66 /* BINBYTES */);
@@ -38657,6 +38733,7 @@ function loads(buf) {
38657
38733
  const size = r.uint64LE();
38658
38734
  void size;
38659
38735
  }
38736
+ const MARK = Symbol("pickle-mark");
38660
38737
  while (!r.eof()) {
38661
38738
  const op = r.byte();
38662
38739
  switch (op) {
@@ -38681,8 +38758,7 @@ function loads(buf) {
38681
38758
  break;
38682
38759
  }
38683
38760
  case 74 /* BININT4 */: {
38684
- const n = r.uint32LE();
38685
- push(n >>> 31 ? n - 4294967296 : n);
38761
+ push(r.int32LE());
38686
38762
  break;
38687
38763
  }
38688
38764
  case 71 /* BINFLOAT */:
@@ -38698,12 +38774,12 @@ function loads(buf) {
38698
38774
  push(tdec.decode(r.take(n)));
38699
38775
  break;
38700
38776
  }
38701
- case 138 /* BINUNICODE8 */: {
38777
+ case 141 /* BINUNICODE8 */: {
38702
38778
  const n = r.uint64LE();
38703
38779
  push(tdec.decode(r.take(n)));
38704
38780
  break;
38705
38781
  }
38706
- case 142 /* SHORT_BINBYTES */: {
38782
+ case 67 /* SHORT_BINBYTES */: {
38707
38783
  const n = r.byte();
38708
38784
  push(r.take(n));
38709
38785
  break;
@@ -38713,7 +38789,7 @@ function loads(buf) {
38713
38789
  push(r.take(n));
38714
38790
  break;
38715
38791
  }
38716
- case 141 /* BINBYTES8 */: {
38792
+ case 142 /* BINBYTES8 */: {
38717
38793
  const n = r.uint64LE();
38718
38794
  push(r.take(n));
38719
38795
  break;
@@ -38757,6 +38833,43 @@ function loads(buf) {
38757
38833
  const _size = r.uint64LE();
38758
38834
  break;
38759
38835
  }
38836
+ case 40 /* MARK */:
38837
+ push(MARK);
38838
+ break;
38839
+ case 101 /* APPENDS */: {
38840
+ const markIndex = stack.lastIndexOf(MARK);
38841
+ if (markIndex === -1) {
38842
+ throw new PickleError("APPENDS without MARK");
38843
+ }
38844
+ const lst = stack[markIndex - 1];
38845
+ if (!Array.isArray(lst)) {
38846
+ throw new PickleError("APPENDS expects a list below MARK");
38847
+ }
38848
+ const items = stack.slice(markIndex + 1);
38849
+ lst.push(...items);
38850
+ stack.length = markIndex - 1;
38851
+ push(lst);
38852
+ break;
38853
+ }
38854
+ case 117 /* SETITEMS */: {
38855
+ const markIndex = stack.lastIndexOf(MARK);
38856
+ if (markIndex === -1) {
38857
+ throw new PickleError("SETITEMS without MARK");
38858
+ }
38859
+ const d = stack[markIndex - 1];
38860
+ if (typeof d !== "object" || d === null || Array.isArray(d)) {
38861
+ throw new PickleError("SETITEMS expects a dict below MARK");
38862
+ }
38863
+ const items = stack.slice(markIndex + 1);
38864
+ for (let i = 0; i < items.length; i += 2) {
38865
+ if (i + 1 < items.length) {
38866
+ d[items[i]] = items[i + 1];
38867
+ }
38868
+ }
38869
+ stack.length = markIndex - 1;
38870
+ push(d);
38871
+ break;
38872
+ }
38760
38873
  default:
38761
38874
  throw new PickleError(`Unsupported opcode 0x${op.toString(16)}`);
38762
38875
  }
@@ -38765,7 +38878,7 @@ function loads(buf) {
38765
38878
  }
38766
38879
 
38767
38880
  // src/function.ts
38768
- import { ClientError as ClientError3, Status as Status3 } from "nice-grpc";
38881
+ import { ClientError as ClientError4, Status as Status4 } from "nice-grpc";
38769
38882
  var maxObjectSizeBytes = 2 * 1024 * 1024;
38770
38883
  var outputsTimeout = 55 * 1e3;
38771
38884
  function timeNowSeconds() {
@@ -38774,6 +38887,7 @@ function timeNowSeconds() {
38774
38887
  var Function_ = class _Function_ {
38775
38888
  functionId;
38776
38889
  methodName;
38890
+ /** @ignore */
38777
38891
  constructor(functionId, methodName) {
38778
38892
  this.functionId = functionId;
38779
38893
  this.methodName = methodName;
@@ -38788,14 +38902,14 @@ var Function_ = class _Function_ {
38788
38902
  });
38789
38903
  return new _Function_(resp.functionId);
38790
38904
  } catch (err) {
38791
- if (err instanceof ClientError3 && err.code === Status3.NOT_FOUND)
38905
+ if (err instanceof ClientError4 && err.code === Status4.NOT_FOUND)
38792
38906
  throw new NotFoundError(`Function '${appName}/${name}' not found`);
38793
38907
  throw err;
38794
38908
  }
38795
38909
  }
38796
38910
  // Execute a single input into a remote Function.
38797
38911
  async remote(args = [], kwargs = {}) {
38798
- const functionCallId = await this.execFunctionCall(
38912
+ const functionCallId = await this.#execFunctionCall(
38799
38913
  args,
38800
38914
  kwargs,
38801
38915
  4 /* FUNCTION_CALL_INVOCATION_TYPE_SYNC */
@@ -38804,14 +38918,14 @@ var Function_ = class _Function_ {
38804
38918
  }
38805
38919
  // Spawn a single input into a remote function.
38806
38920
  async spawn(args = [], kwargs = {}) {
38807
- const functionCallId = await this.execFunctionCall(
38921
+ const functionCallId = await this.#execFunctionCall(
38808
38922
  args,
38809
38923
  kwargs,
38810
38924
  4 /* FUNCTION_CALL_INVOCATION_TYPE_SYNC */
38811
38925
  );
38812
38926
  return new FunctionCall(functionCallId);
38813
38927
  }
38814
- async execFunctionCall(args = [], kwargs = {}, invocationType = 4 /* FUNCTION_CALL_INVOCATION_TYPE_SYNC */) {
38928
+ async #execFunctionCall(args = [], kwargs = {}, invocationType = 4 /* FUNCTION_CALL_INVOCATION_TYPE_SYNC */) {
38815
38929
  const payload = dumps([args, kwargs]);
38816
38930
  let argsBlobId = void 0;
38817
38931
  if (payload.length > maxObjectSizeBytes) {
@@ -38952,6 +39066,7 @@ var Cls = class _Cls {
38952
39066
  #serviceFunctionId;
38953
39067
  #schema;
38954
39068
  #methodNames;
39069
+ /** @ignore */
38955
39070
  constructor(serviceFunctionId, schema, methodNames) {
38956
39071
  this.#serviceFunctionId = serviceFunctionId;
38957
39072
  this.#schema = schema;
@@ -38985,7 +39100,7 @@ var Cls = class _Cls {
38985
39100
  }
38986
39101
  return new _Cls(serviceFunction.functionId, schema, methodNames);
38987
39102
  } catch (err) {
38988
- if (err instanceof ClientError4 && err.code === Status4.NOT_FOUND)
39103
+ if (err instanceof ClientError5 && err.code === Status5.NOT_FOUND)
38989
39104
  throw new NotFoundError(`Class '${appName}/${name}' not found`);
38990
39105
  throw err;
38991
39106
  }
@@ -39082,16 +39197,269 @@ var ClsInstance = class {
39082
39197
  return method;
39083
39198
  }
39084
39199
  };
39200
+
39201
+ // src/queue.ts
39202
+ import { ClientError as ClientError6, Status as Status6 } from "nice-grpc";
39203
+ var ephemeralObjectHeartbeatSleep = 3e5;
39204
+ var queueInitialPutBackoff = 100;
39205
+ var queueDefaultPartitionTtl = 24 * 3600 * 1e3;
39206
+ var Queue = class _Queue {
39207
+ queueId;
39208
+ #ephemeral;
39209
+ #abortController;
39210
+ /** @ignore */
39211
+ constructor(queueId, ephemeral = false) {
39212
+ this.queueId = queueId;
39213
+ this.#ephemeral = ephemeral;
39214
+ this.#abortController = ephemeral ? new AbortController() : void 0;
39215
+ }
39216
+ static #validatePartitionKey(partition) {
39217
+ if (partition) {
39218
+ const partitionKey = new TextEncoder().encode(partition);
39219
+ if (partitionKey.length === 0 || partitionKey.length > 64) {
39220
+ throw new InvalidError(
39221
+ "Queue partition key must be between 1 and 64 bytes."
39222
+ );
39223
+ }
39224
+ return partitionKey;
39225
+ }
39226
+ return new Uint8Array();
39227
+ }
39228
+ /**
39229
+ * Create a nameless, temporary queue.
39230
+ * You will need to call `closeEphemeral()` to delete the queue.
39231
+ */
39232
+ static async ephemeral(options = {}) {
39233
+ const resp = await client.queueGetOrCreate({
39234
+ objectCreationType: 5 /* OBJECT_CREATION_TYPE_EPHEMERAL */,
39235
+ environmentName: environmentName(options.environment)
39236
+ });
39237
+ const queue = new _Queue(resp.queueId, true);
39238
+ const signal = queue.#abortController.signal;
39239
+ (async () => {
39240
+ while (true) {
39241
+ await client.queueHeartbeat({ queueId: resp.queueId });
39242
+ await Promise.race([
39243
+ new Promise(
39244
+ (resolve) => setTimeout(resolve, ephemeralObjectHeartbeatSleep)
39245
+ ),
39246
+ new Promise((resolve) => {
39247
+ signal.addEventListener("abort", resolve, { once: true });
39248
+ })
39249
+ ]);
39250
+ }
39251
+ })();
39252
+ return queue;
39253
+ }
39254
+ /** Delete the ephemeral queue. Only usable with `Queue.ephemeral()`. */
39255
+ closeEphemeral() {
39256
+ if (this.#ephemeral) {
39257
+ this.#abortController.abort();
39258
+ } else {
39259
+ throw new InvalidError("Queue is not ephemeral.");
39260
+ }
39261
+ }
39262
+ /**
39263
+ * Lookup a queue by name.
39264
+ */
39265
+ static async lookup(name, options = {}) {
39266
+ const resp = await client.queueGetOrCreate({
39267
+ deploymentName: name,
39268
+ objectCreationType: options.createIfMissing ? 1 /* OBJECT_CREATION_TYPE_CREATE_IF_MISSING */ : void 0,
39269
+ namespace: 1 /* DEPLOYMENT_NAMESPACE_WORKSPACE */,
39270
+ environmentName: environmentName(options.environment)
39271
+ });
39272
+ return new _Queue(resp.queueId);
39273
+ }
39274
+ /** Delete a queue by name. */
39275
+ static async delete(name, options = {}) {
39276
+ const queue = await _Queue.lookup(name, options);
39277
+ await client.queueDelete({ queueId: queue.queueId });
39278
+ }
39279
+ /**
39280
+ * Remove all objects from a queue partition.
39281
+ */
39282
+ async clear(options = {}) {
39283
+ if (options.partition && options.all) {
39284
+ throw new InvalidError(
39285
+ "Partition must be null when requesting to clear all."
39286
+ );
39287
+ }
39288
+ await client.queueClear({
39289
+ queueId: this.queueId,
39290
+ partitionKey: _Queue.#validatePartitionKey(options.partition),
39291
+ allPartitions: options.all
39292
+ });
39293
+ }
39294
+ async #get(n, partition, timeout) {
39295
+ const partitionKey = _Queue.#validatePartitionKey(partition);
39296
+ const startTime = Date.now();
39297
+ let pollTimeout = 5e4;
39298
+ if (timeout !== void 0) {
39299
+ pollTimeout = Math.min(pollTimeout, timeout);
39300
+ }
39301
+ while (true) {
39302
+ const response = await client.queueGet({
39303
+ queueId: this.queueId,
39304
+ partitionKey,
39305
+ timeout: pollTimeout / 1e3,
39306
+ nValues: n
39307
+ });
39308
+ if (response.values && response.values.length > 0) {
39309
+ return response.values.map((value) => loads(value));
39310
+ }
39311
+ if (timeout !== void 0) {
39312
+ const remaining = timeout - (Date.now() - startTime);
39313
+ if (remaining <= 0) {
39314
+ const message = `Queue ${this.queueId} did not return values within ${timeout}ms.`;
39315
+ throw new QueueEmptyError(message);
39316
+ }
39317
+ pollTimeout = Math.min(pollTimeout, remaining);
39318
+ }
39319
+ }
39320
+ }
39321
+ /**
39322
+ * Remove and return the next object from the queue.
39323
+ *
39324
+ * By default, this will wait until at least one item is present in the queue.
39325
+ * If `timeout` is set, raises `QueueEmptyError` if no items are available
39326
+ * within that timeout in milliseconds.
39327
+ */
39328
+ async get(options = {}) {
39329
+ const values = await this.#get(1, options.partition, options.timeout);
39330
+ return values[0];
39331
+ }
39332
+ /**
39333
+ * Remove and return up to `n` objects from the queue.
39334
+ *
39335
+ * By default, this will wait until at least one item is present in the queue.
39336
+ * If `timeout` is set, raises `QueueEmptyError` if no items are available
39337
+ * within that timeout in milliseconds.
39338
+ */
39339
+ async getMany(n, options = {}) {
39340
+ return await this.#get(n, options.partition, options.timeout);
39341
+ }
39342
+ async #put(values, timeout, partition, partitionTtl) {
39343
+ const valuesEncoded = values.map((v) => dumps(v));
39344
+ const partitionKey = _Queue.#validatePartitionKey(partition);
39345
+ let delay = queueInitialPutBackoff;
39346
+ const deadline = timeout ? Date.now() + timeout : void 0;
39347
+ while (true) {
39348
+ try {
39349
+ await client.queuePut({
39350
+ queueId: this.queueId,
39351
+ values: valuesEncoded,
39352
+ partitionKey,
39353
+ partitionTtlSeconds: (partitionTtl || queueDefaultPartitionTtl) / 1e3
39354
+ });
39355
+ break;
39356
+ } catch (e) {
39357
+ if (e instanceof ClientError6 && e.code === Status6.RESOURCE_EXHAUSTED) {
39358
+ delay = Math.min(delay * 2, 3e4);
39359
+ if (deadline !== void 0) {
39360
+ const remaining = deadline - Date.now();
39361
+ if (remaining <= 0)
39362
+ throw new QueueFullError(`Put failed on ${this.queueId}.`);
39363
+ delay = Math.min(delay, remaining);
39364
+ }
39365
+ await new Promise((resolve) => setTimeout(resolve, delay));
39366
+ } else {
39367
+ throw e;
39368
+ }
39369
+ }
39370
+ }
39371
+ }
39372
+ /**
39373
+ * Add an item to the end of the queue.
39374
+ *
39375
+ * If the queue is full, this will retry with exponential backoff until the
39376
+ * provided `timeout` is reached, or indefinitely if `timeout` is not set.
39377
+ * Raises `QueueFullError` if the queue is still full after the timeout.
39378
+ */
39379
+ async put(v, options = {}) {
39380
+ await this.#put(
39381
+ [v],
39382
+ options.timeout,
39383
+ options.partition,
39384
+ options.partitionTtl
39385
+ );
39386
+ }
39387
+ /**
39388
+ * Add several items to the end of the queue.
39389
+ *
39390
+ * If the queue is full, this will retry with exponential backoff until the
39391
+ * provided `timeout` is reached, or indefinitely if `timeout` is not set.
39392
+ * Raises `QueueFullError` if the queue is still full after the timeout.
39393
+ */
39394
+ async putMany(values, options = {}) {
39395
+ await this.#put(
39396
+ values,
39397
+ options.timeout,
39398
+ options.partition,
39399
+ options.partitionTtl
39400
+ );
39401
+ }
39402
+ /** Return the number of objects in the queue. */
39403
+ async len(options = {}) {
39404
+ if (options.partition && options.total) {
39405
+ throw new InvalidError(
39406
+ "Partition must be null when requesting total length."
39407
+ );
39408
+ }
39409
+ const resp = await client.queueLen({
39410
+ queueId: this.queueId,
39411
+ partitionKey: _Queue.#validatePartitionKey(options.partition),
39412
+ total: options.total
39413
+ });
39414
+ return resp.len;
39415
+ }
39416
+ /** Iterate through items in a queue without mutation. */
39417
+ async *iterate(options = {}) {
39418
+ const { partition, itemPollTimeout = 0 } = options;
39419
+ let lastEntryId = void 0;
39420
+ const validatedPartitionKey = _Queue.#validatePartitionKey(partition);
39421
+ let fetchDeadline = Date.now() + itemPollTimeout;
39422
+ const maxPollDuration = 3e4;
39423
+ while (true) {
39424
+ const pollDuration = Math.max(
39425
+ 0,
39426
+ Math.min(maxPollDuration, fetchDeadline - Date.now())
39427
+ );
39428
+ const request = {
39429
+ queueId: this.queueId,
39430
+ partitionKey: validatedPartitionKey,
39431
+ itemPollTimeout: pollDuration / 1e3,
39432
+ lastEntryId: lastEntryId || ""
39433
+ };
39434
+ const response = await client.queueNextItems(request);
39435
+ if (response.items && response.items.length > 0) {
39436
+ for (const item of response.items) {
39437
+ yield loads(item.value);
39438
+ lastEntryId = item.entryId;
39439
+ }
39440
+ fetchDeadline = Date.now() + itemPollTimeout;
39441
+ } else if (Date.now() > fetchDeadline) {
39442
+ break;
39443
+ }
39444
+ }
39445
+ }
39446
+ };
39085
39447
  export {
39086
39448
  App,
39087
39449
  Cls,
39088
39450
  ClsInstance,
39451
+ ContainerProcess,
39089
39452
  FunctionCall,
39090
39453
  FunctionTimeoutError,
39091
39454
  Function_,
39092
39455
  Image2 as Image,
39093
39456
  InternalFailure,
39457
+ InvalidError,
39094
39458
  NotFoundError,
39459
+ Queue,
39460
+ QueueEmptyError,
39461
+ QueueFullError,
39095
39462
  RemoteError,
39096
- Sandbox2 as Sandbox
39463
+ Sandbox2 as Sandbox,
39464
+ Secret
39097
39465
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modal",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "Modal client library for JavaScript",
5
5
  "license": "MIT",
6
6
  "homepage": "https://modal.com/docs",
@@ -23,10 +23,16 @@
23
23
  "scripts": {
24
24
  "build": "tsup",
25
25
  "check": "tsc",
26
+ "docs": "typedoc src/index.ts --treatWarningsAsErrors",
27
+ "docs:serve": "npm run docs && http-server ./docs",
26
28
  "format": "prettier --write .",
27
29
  "format:check": "prettier --check .",
30
+ "lint": "eslint",
28
31
  "prepare": "scripts/gen-proto.sh",
29
- "test": "vitest"
32
+ "test": "vitest",
33
+ "preversion": "(git update-index --really-refresh && git diff-index --quiet HEAD) || (echo 'You must commit all changes before running npm version' && exit 1)",
34
+ "version": "npm run build && git add package.json package-lock.json && git commit -m \"modal-js/v$npm_package_version\" && git tag modal-js/v$npm_package_version",
35
+ "postversion": "git push && git push --tags && npm publish"
30
36
  },
31
37
  "dependencies": {
32
38
  "long": "^5.3.1",
@@ -36,14 +42,20 @@
36
42
  "uuid": "^11.1.0"
37
43
  },
38
44
  "devDependencies": {
45
+ "@eslint/js": "^9.28.0",
39
46
  "@types/node": "^22.15.2",
47
+ "eslint": "^9.28.0",
48
+ "globals": "^16.2.0",
40
49
  "grpc-tools": "^1.13.0",
50
+ "http-server": "^14.1.1",
41
51
  "p-queue": "^8.1.0",
42
52
  "prettier": "^3.5.3",
43
53
  "ts-proto": "^2.7.0",
44
54
  "tsup": "^8.4.0",
45
55
  "tsx": "^4.19.3",
56
+ "typedoc": "^0.28.5",
46
57
  "typescript": "~5.8.3",
58
+ "typescript-eslint": "^8.33.1",
47
59
  "vitest": "^3.1.2"
48
60
  }
49
61
  }