modal 0.3.5 → 0.3.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
@@ -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,9 @@
1
1
  import { BinaryWriter, BinaryReader } from '@bufbuild/protobuf/wire';
2
2
 
3
+ /** A container image, used for starting sandboxes. */
3
4
  declare class Image {
4
5
  readonly imageId: string;
6
+ /** @ignore */
5
7
  constructor(imageId: string);
6
8
  }
7
9
 
@@ -42,19 +44,33 @@ interface ModalWriteStream<R = any> extends WritableStream<R> {
42
44
  writeBytes(bytes: Uint8Array): Promise<void>;
43
45
  }
44
46
 
47
+ /**
48
+ * Stdin is always present, but this option allow you to drop stdout or stderr
49
+ * if you don't need them. The default is "pipe", matching Node.js behavior.
50
+ *
51
+ * If behavior is set to "ignore", the output streams will be empty.
52
+ */
45
53
  type StdioBehavior = "pipe" | "ignore";
54
+ /**
55
+ * Specifies the type of data that will be read from the sandbox or container
56
+ * process. "text" means the data will be read as UTF-8 text, while "binary"
57
+ * means the data will be read as raw bytes (Uint8Array).
58
+ */
46
59
  type StreamMode = "text" | "binary";
60
+ /** Options to configure a `Sandbox.exec()` operation. */
47
61
  type ExecOptions = {
48
62
  mode?: StreamMode;
49
63
  stdout?: StdioBehavior;
50
64
  stderr?: StdioBehavior;
51
65
  };
66
+ /** Sandboxes are secure, isolated containers in Modal that boot in seconds. */
52
67
  declare class Sandbox {
53
68
  #private;
54
69
  readonly sandboxId: string;
55
70
  stdin: ModalWriteStream<string>;
56
71
  stdout: ModalReadStream<string>;
57
72
  stderr: ModalReadStream<string>;
73
+ /** @ignore */
58
74
  constructor(sandboxId: string);
59
75
  exec(command: string[], options?: ExecOptions & {
60
76
  mode?: "text";
@@ -76,18 +92,37 @@ declare class ContainerProcess<R extends string | Uint8Array = any> {
76
92
  wait(): Promise<number>;
77
93
  }
78
94
 
95
+ /** Options for functions that find deployed Modal objects. */
79
96
  type LookupOptions = {
80
97
  environment?: string;
81
98
  createIfMissing?: boolean;
82
99
  };
100
+ /** Options for deleting a named object. */
101
+ type DeleteOptions = {
102
+ environment?: string;
103
+ };
104
+ /** Options for constructors that create a temporary, nameless object. */
105
+ type EphemeralOptions = {
106
+ environment?: string;
107
+ };
108
+ /** Options for `App.createSandbox()`. */
83
109
  type SandboxCreateOptions = {
110
+ /** Reservation of physical CPU cores for the sandbox, can be fractional. */
84
111
  cpu?: number;
112
+ /** Reservation of memory in MiB. */
85
113
  memory?: number;
114
+ /** Timeout of the sandbox container, defaults to 10 minutes. */
86
115
  timeout?: number;
116
+ /**
117
+ * Sequence of program arguments for the main process.
118
+ * Default behavior is to sleep indefinitely until timeout or termination.
119
+ */
87
120
  command?: string[];
88
121
  };
122
+ /** Represents a deployed Modal App. */
89
123
  declare class App {
90
124
  readonly appId: string;
125
+ /** @ignore */
91
126
  constructor(appId: string);
92
127
  /** Lookup a deployed app by name, or create if it does not exist. */
93
128
  static lookup(name: string, options?: LookupOptions): Promise<App>;
@@ -95,14 +130,6 @@ declare class App {
95
130
  imageFromRegistry(tag: string): Promise<Image>;
96
131
  }
97
132
 
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
133
  declare enum ParameterType {
107
134
  PARAM_TYPE_UNSPECIFIED = 0,
108
135
  PARAM_TYPE_STRING = 1,
@@ -153,9 +180,11 @@ interface MessageFns<T> {
153
180
  fromPartial(object: DeepPartial<T>): T;
154
181
  }
155
182
 
183
+ /** Options for `FunctionCall.get()`. */
156
184
  type FunctionCallGetOptions = {
157
185
  timeout?: number;
158
186
  };
187
+ /** Options for `FunctionCall.cancel()`. */
159
188
  type FunctionCallCancelOptions = {
160
189
  terminateContainers?: boolean;
161
190
  };
@@ -166,6 +195,7 @@ type FunctionCallCancelOptions = {
166
195
  */
167
196
  declare class FunctionCall {
168
197
  readonly functionCallId: string;
198
+ /** @ignore */
169
199
  constructor(functionCallId: string);
170
200
  /** Create a new function call from ID. */
171
201
  fromId(functionCallId: string): FunctionCall;
@@ -177,18 +207,20 @@ declare class FunctionCall {
177
207
 
178
208
  /** Represents a deployed Modal Function, which can be invoked remotely. */
179
209
  declare class Function_ {
210
+ #private;
180
211
  readonly functionId: string;
181
212
  readonly methodName: string | undefined;
213
+ /** @ignore */
182
214
  constructor(functionId: string, methodName?: string);
183
215
  static lookup(appName: string, name: string, options?: LookupOptions): Promise<Function_>;
184
216
  remote(args?: any[], kwargs?: Record<string, any>): Promise<any>;
185
217
  spawn(args?: any[], kwargs?: Record<string, any>): Promise<FunctionCall>;
186
- execFunctionCall(args?: any[], kwargs?: Record<string, any>, invocationType?: FunctionCallInvocationType): Promise<string>;
187
218
  }
188
219
 
189
220
  /** Represents a deployed Modal Cls. */
190
221
  declare class Cls {
191
222
  #private;
223
+ /** @ignore */
192
224
  constructor(serviceFunctionId: string, schema: ClassParameterSpec[], methodNames: string[]);
193
225
  static lookup(appName: string, name: string, options?: LookupOptions): Promise<Cls>;
194
226
  /** Create a new instance of the Cls with parameters. */
@@ -217,5 +249,117 @@ declare class InternalFailure extends Error {
217
249
  declare class NotFoundError extends Error {
218
250
  constructor(message: string);
219
251
  }
252
+ /** A request or other operation was invalid. */
253
+ declare class InvalidError extends Error {
254
+ constructor(message: string);
255
+ }
256
+ /** The queue is empty. */
257
+ declare class QueueEmptyError extends Error {
258
+ constructor(message: string);
259
+ }
260
+ /** The queue is full. */
261
+ declare class QueueFullError extends Error {
262
+ constructor(message: string);
263
+ }
264
+
265
+ /** Options to configure a `Queue.clear()` operation. */
266
+ type QueueClearOptions = {
267
+ /** Partition to clear, uses default partition if not set. */
268
+ partition?: string;
269
+ /** Set to clear all queue partitions. */
270
+ all?: boolean;
271
+ };
272
+ /** Options to configure a `Queue.get()` or `Queue.getMany()` operation. */
273
+ type QueueGetOptions = {
274
+ /** How long to wait if the queue is empty (default: indefinite). */
275
+ timeout?: number;
276
+ /** Partition to fetch values from, uses default partition if not set. */
277
+ partition?: string;
278
+ };
279
+ /** Options to configure a `Queue.put()` or `Queue.putMany()` operation. */
280
+ type QueuePutOptions = {
281
+ /** How long to wait if the queue is full (default: indefinite). */
282
+ timeout?: number;
283
+ /** Partition to add items to, uses default partition if not set. */
284
+ partition?: string;
285
+ /** TTL for the partition in seconds (default: 1 day). */
286
+ partitionTtl?: number;
287
+ };
288
+ /** Options to configure a `Queue.len()` operation. */
289
+ type QueueLenOptions = {
290
+ /** Partition to compute length, uses default partition if not set. */
291
+ partition?: string;
292
+ /** Return the total length across all partitions. */
293
+ total?: boolean;
294
+ };
295
+ /** Options to configure a `Queue.iterate()` operation. */
296
+ type QueueIterateOptions = {
297
+ /** How long to wait between successive items before exiting iteration (default: 0). */
298
+ itemPollTimeout?: number;
299
+ /** Partition to iterate, uses default partition if not set. */
300
+ partition?: string;
301
+ };
302
+ /**
303
+ * Distributed, FIFO queue for data flow in Modal apps.
304
+ */
305
+ declare class Queue {
306
+ #private;
307
+ readonly queueId: string;
308
+ /** @ignore */
309
+ constructor(queueId: string, ephemeral?: boolean);
310
+ /**
311
+ * Create a nameless, temporary queue.
312
+ * You will need to call `closeEphemeral()` to delete the queue.
313
+ */
314
+ static ephemeral(options?: EphemeralOptions): Promise<Queue>;
315
+ /** Delete the ephemeral queue. Only usable with `Queue.ephemeral()`. */
316
+ closeEphemeral(): void;
317
+ /**
318
+ * Lookup a queue by name.
319
+ */
320
+ static lookup(name: string, options?: LookupOptions): Promise<Queue>;
321
+ /** Delete a queue by name. */
322
+ static delete(name: string, options?: DeleteOptions): Promise<void>;
323
+ /**
324
+ * Remove all objects from a queue partition.
325
+ */
326
+ clear(options?: QueueClearOptions): Promise<void>;
327
+ /**
328
+ * Remove and return the next object from the queue.
329
+ *
330
+ * By default, this will wait until at least one item is present in the queue.
331
+ * If `timeout` is set, raises `QueueEmptyError` if no items are available
332
+ * within that timeout in milliseconds.
333
+ */
334
+ get(options?: QueueGetOptions): Promise<any | null>;
335
+ /**
336
+ * Remove and return up to `n` objects from the queue.
337
+ *
338
+ * By default, this will wait until at least one item is present in the queue.
339
+ * If `timeout` is set, raises `QueueEmptyError` if no items are available
340
+ * within that timeout in milliseconds.
341
+ */
342
+ getMany(n: number, options?: QueueGetOptions): Promise<any[]>;
343
+ /**
344
+ * Add an item to the end of the queue.
345
+ *
346
+ * If the queue is full, this will retry with exponential backoff until the
347
+ * provided `timeout` is reached, or indefinitely if `timeout` is not set.
348
+ * Raises `QueueFullError` if the queue is still full after the timeout.
349
+ */
350
+ put(v: any, options?: QueuePutOptions): Promise<void>;
351
+ /**
352
+ * Add several items to the end of the queue.
353
+ *
354
+ * If the queue is full, this will retry with exponential backoff until the
355
+ * provided `timeout` is reached, or indefinitely if `timeout` is not set.
356
+ * Raises `QueueFullError` if the queue is still full after the timeout.
357
+ */
358
+ putMany(values: any[], options?: QueuePutOptions): Promise<void>;
359
+ /** Return the number of objects in the queue. */
360
+ len(options?: QueueLenOptions): Promise<number>;
361
+ /** Iterate through items in a queue without mutation. */
362
+ iterate(options?: QueueIterateOptions): AsyncGenerator<any, void, unknown>;
363
+ }
220
364
 
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 };
365
+ 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, type StdioBehavior, type StreamMode };
package/dist/index.js CHANGED
@@ -37831,7 +37831,7 @@ 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,
@@ -37975,6 +37975,7 @@ var client = createClientFactory().use(authMiddleware(profile)).use(retryMiddlew
37975
37975
  // src/image.ts
37976
37976
  var Image2 = class {
37977
37977
  imageId;
37978
+ /** @ignore */
37978
37979
  constructor(imageId) {
37979
37980
  this.imageId = imageId;
37980
37981
  }
@@ -38013,6 +38014,7 @@ async function fromRegistryInternal(appId, tag) {
38013
38014
  }
38014
38015
  result = resultJoined;
38015
38016
  }
38017
+ void metadata;
38016
38018
  if (result.status === 2 /* GENERIC_STATUS_FAILURE */) {
38017
38019
  throw new Error(
38018
38020
  `Image build for ${resp.imageId} failed with the exception:
@@ -38151,6 +38153,7 @@ var Sandbox2 = class {
38151
38153
  stdout;
38152
38154
  stderr;
38153
38155
  #taskId;
38156
+ /** @ignore */
38154
38157
  constructor(sandboxId) {
38155
38158
  this.sandboxId = sandboxId;
38156
38159
  this.stdin = toModalWriteStream(inputStreamSb(sandboxId));
@@ -38381,10 +38384,29 @@ var NotFoundError = class extends Error {
38381
38384
  this.name = "NotFoundError";
38382
38385
  }
38383
38386
  };
38387
+ var InvalidError = class extends Error {
38388
+ constructor(message) {
38389
+ super(message);
38390
+ this.name = "InvalidError";
38391
+ }
38392
+ };
38393
+ var QueueEmptyError = class extends Error {
38394
+ constructor(message) {
38395
+ super(message);
38396
+ this.name = "QueueEmptyError";
38397
+ }
38398
+ };
38399
+ var QueueFullError = class extends Error {
38400
+ constructor(message) {
38401
+ super(message);
38402
+ this.name = "QueueFullError";
38403
+ }
38404
+ };
38384
38405
 
38385
38406
  // src/app.ts
38386
38407
  var App = class _App {
38387
38408
  appId;
38409
+ /** @ignore */
38388
38410
  constructor(appId) {
38389
38411
  this.appId = appId;
38390
38412
  }
@@ -38442,6 +38464,7 @@ import { createHash } from "node:crypto";
38442
38464
  // src/function_call.ts
38443
38465
  var FunctionCall = class _FunctionCall {
38444
38466
  functionCallId;
38467
+ /** @ignore */
38445
38468
  constructor(functionCallId) {
38446
38469
  this.functionCallId = functionCallId;
38447
38470
  }
@@ -38570,7 +38593,7 @@ function encodeValue(val, w, proto) {
38570
38593
  w.byte(140 /* SHORT_BINUNICODE */);
38571
38594
  w.byte(utf8.length);
38572
38595
  } else if (proto >= 4 && utf8.length > 4294967295) {
38573
- w.byte(138 /* BINUNICODE8 */);
38596
+ w.byte(141 /* BINUNICODE8 */);
38574
38597
  w.uint64LE(utf8.length);
38575
38598
  } else {
38576
38599
  w.byte(88 /* BINUNICODE */);
@@ -38583,10 +38606,10 @@ function encodeValue(val, w, proto) {
38583
38606
  if (val instanceof Uint8Array) {
38584
38607
  const len = val.length;
38585
38608
  if (proto >= 4 && len < 256) {
38586
- w.byte(142 /* SHORT_BINBYTES */);
38609
+ w.byte(67 /* SHORT_BINBYTES */);
38587
38610
  w.byte(len);
38588
38611
  } else if (proto >= 4 && len > 4294967295) {
38589
- w.byte(141 /* BINBYTES8 */);
38612
+ w.byte(142 /* BINBYTES8 */);
38590
38613
  w.uint64LE(len);
38591
38614
  } else {
38592
38615
  w.byte(66 /* BINBYTES */);
@@ -38698,12 +38721,12 @@ function loads(buf) {
38698
38721
  push(tdec.decode(r.take(n)));
38699
38722
  break;
38700
38723
  }
38701
- case 138 /* BINUNICODE8 */: {
38724
+ case 141 /* BINUNICODE8 */: {
38702
38725
  const n = r.uint64LE();
38703
38726
  push(tdec.decode(r.take(n)));
38704
38727
  break;
38705
38728
  }
38706
- case 142 /* SHORT_BINBYTES */: {
38729
+ case 67 /* SHORT_BINBYTES */: {
38707
38730
  const n = r.byte();
38708
38731
  push(r.take(n));
38709
38732
  break;
@@ -38713,7 +38736,7 @@ function loads(buf) {
38713
38736
  push(r.take(n));
38714
38737
  break;
38715
38738
  }
38716
- case 141 /* BINBYTES8 */: {
38739
+ case 142 /* BINBYTES8 */: {
38717
38740
  const n = r.uint64LE();
38718
38741
  push(r.take(n));
38719
38742
  break;
@@ -38774,6 +38797,7 @@ function timeNowSeconds() {
38774
38797
  var Function_ = class _Function_ {
38775
38798
  functionId;
38776
38799
  methodName;
38800
+ /** @ignore */
38777
38801
  constructor(functionId, methodName) {
38778
38802
  this.functionId = functionId;
38779
38803
  this.methodName = methodName;
@@ -38795,7 +38819,7 @@ var Function_ = class _Function_ {
38795
38819
  }
38796
38820
  // Execute a single input into a remote Function.
38797
38821
  async remote(args = [], kwargs = {}) {
38798
- const functionCallId = await this.execFunctionCall(
38822
+ const functionCallId = await this.#execFunctionCall(
38799
38823
  args,
38800
38824
  kwargs,
38801
38825
  4 /* FUNCTION_CALL_INVOCATION_TYPE_SYNC */
@@ -38804,14 +38828,14 @@ var Function_ = class _Function_ {
38804
38828
  }
38805
38829
  // Spawn a single input into a remote function.
38806
38830
  async spawn(args = [], kwargs = {}) {
38807
- const functionCallId = await this.execFunctionCall(
38831
+ const functionCallId = await this.#execFunctionCall(
38808
38832
  args,
38809
38833
  kwargs,
38810
38834
  4 /* FUNCTION_CALL_INVOCATION_TYPE_SYNC */
38811
38835
  );
38812
38836
  return new FunctionCall(functionCallId);
38813
38837
  }
38814
- async execFunctionCall(args = [], kwargs = {}, invocationType = 4 /* FUNCTION_CALL_INVOCATION_TYPE_SYNC */) {
38838
+ async #execFunctionCall(args = [], kwargs = {}, invocationType = 4 /* FUNCTION_CALL_INVOCATION_TYPE_SYNC */) {
38815
38839
  const payload = dumps([args, kwargs]);
38816
38840
  let argsBlobId = void 0;
38817
38841
  if (payload.length > maxObjectSizeBytes) {
@@ -38952,6 +38976,7 @@ var Cls = class _Cls {
38952
38976
  #serviceFunctionId;
38953
38977
  #schema;
38954
38978
  #methodNames;
38979
+ /** @ignore */
38955
38980
  constructor(serviceFunctionId, schema, methodNames) {
38956
38981
  this.#serviceFunctionId = serviceFunctionId;
38957
38982
  this.#schema = schema;
@@ -39082,16 +39107,268 @@ var ClsInstance = class {
39082
39107
  return method;
39083
39108
  }
39084
39109
  };
39110
+
39111
+ // src/queue.ts
39112
+ import { ClientError as ClientError5, Status as Status5 } from "nice-grpc";
39113
+ var ephemeralObjectHeartbeatSleep = 3e5;
39114
+ var queueInitialPutBackoff = 100;
39115
+ var queueDefaultPartitionTtl = 24 * 3600 * 1e3;
39116
+ var Queue = class _Queue {
39117
+ queueId;
39118
+ #ephemeral;
39119
+ #abortController;
39120
+ /** @ignore */
39121
+ constructor(queueId, ephemeral = false) {
39122
+ this.queueId = queueId;
39123
+ this.#ephemeral = ephemeral;
39124
+ this.#abortController = ephemeral ? new AbortController() : void 0;
39125
+ }
39126
+ static #validatePartitionKey(partition) {
39127
+ if (partition) {
39128
+ const partitionKey = new TextEncoder().encode(partition);
39129
+ if (partitionKey.length === 0 || partitionKey.length > 64) {
39130
+ throw new InvalidError(
39131
+ "Queue partition key must be between 1 and 64 bytes."
39132
+ );
39133
+ }
39134
+ return partitionKey;
39135
+ }
39136
+ return new Uint8Array();
39137
+ }
39138
+ /**
39139
+ * Create a nameless, temporary queue.
39140
+ * You will need to call `closeEphemeral()` to delete the queue.
39141
+ */
39142
+ static async ephemeral(options = {}) {
39143
+ const resp = await client.queueGetOrCreate({
39144
+ objectCreationType: 5 /* OBJECT_CREATION_TYPE_EPHEMERAL */,
39145
+ environmentName: environmentName(options.environment)
39146
+ });
39147
+ const queue = new _Queue(resp.queueId, true);
39148
+ const signal = queue.#abortController.signal;
39149
+ (async () => {
39150
+ while (true) {
39151
+ await client.queueHeartbeat({ queueId: resp.queueId });
39152
+ await Promise.race([
39153
+ new Promise(
39154
+ (resolve) => setTimeout(resolve, ephemeralObjectHeartbeatSleep)
39155
+ ),
39156
+ new Promise((resolve) => {
39157
+ signal.addEventListener("abort", resolve, { once: true });
39158
+ })
39159
+ ]);
39160
+ }
39161
+ })();
39162
+ return queue;
39163
+ }
39164
+ /** Delete the ephemeral queue. Only usable with `Queue.ephemeral()`. */
39165
+ closeEphemeral() {
39166
+ if (this.#ephemeral) {
39167
+ this.#abortController.abort();
39168
+ } else {
39169
+ throw new InvalidError("Queue is not ephemeral.");
39170
+ }
39171
+ }
39172
+ /**
39173
+ * Lookup a queue by name.
39174
+ */
39175
+ static async lookup(name, options = {}) {
39176
+ const resp = await client.queueGetOrCreate({
39177
+ deploymentName: name,
39178
+ objectCreationType: options.createIfMissing ? 1 /* OBJECT_CREATION_TYPE_CREATE_IF_MISSING */ : void 0,
39179
+ namespace: 1 /* DEPLOYMENT_NAMESPACE_WORKSPACE */,
39180
+ environmentName: environmentName(options.environment)
39181
+ });
39182
+ return new _Queue(resp.queueId);
39183
+ }
39184
+ /** Delete a queue by name. */
39185
+ static async delete(name, options = {}) {
39186
+ const queue = await _Queue.lookup(name, options);
39187
+ await client.queueDelete({ queueId: queue.queueId });
39188
+ }
39189
+ /**
39190
+ * Remove all objects from a queue partition.
39191
+ */
39192
+ async clear(options = {}) {
39193
+ if (options.partition && options.all) {
39194
+ throw new InvalidError(
39195
+ "Partition must be null when requesting to clear all."
39196
+ );
39197
+ }
39198
+ await client.queueClear({
39199
+ queueId: this.queueId,
39200
+ partitionKey: _Queue.#validatePartitionKey(options.partition),
39201
+ allPartitions: options.all
39202
+ });
39203
+ }
39204
+ async #get(n, partition, timeout) {
39205
+ const partitionKey = _Queue.#validatePartitionKey(partition);
39206
+ const startTime = Date.now();
39207
+ let pollTimeout = 5e4;
39208
+ if (timeout !== void 0) {
39209
+ pollTimeout = Math.min(pollTimeout, timeout);
39210
+ }
39211
+ while (true) {
39212
+ const response = await client.queueGet({
39213
+ queueId: this.queueId,
39214
+ partitionKey,
39215
+ timeout: pollTimeout / 1e3,
39216
+ nValues: n
39217
+ });
39218
+ if (response.values && response.values.length > 0) {
39219
+ return response.values.map((value) => loads(value));
39220
+ }
39221
+ if (timeout !== void 0) {
39222
+ const remaining = timeout - (Date.now() - startTime);
39223
+ if (remaining <= 0) {
39224
+ const message = `Queue ${this.queueId} did not return values within ${timeout}ms.`;
39225
+ throw new QueueEmptyError(message);
39226
+ }
39227
+ pollTimeout = Math.min(pollTimeout, remaining);
39228
+ }
39229
+ }
39230
+ }
39231
+ /**
39232
+ * Remove and return the next object from the queue.
39233
+ *
39234
+ * By default, this will wait until at least one item is present in the queue.
39235
+ * If `timeout` is set, raises `QueueEmptyError` if no items are available
39236
+ * within that timeout in milliseconds.
39237
+ */
39238
+ async get(options = {}) {
39239
+ const values = await this.#get(1, options.partition, options.timeout);
39240
+ return values[0];
39241
+ }
39242
+ /**
39243
+ * Remove and return up to `n` objects from the queue.
39244
+ *
39245
+ * By default, this will wait until at least one item is present in the queue.
39246
+ * If `timeout` is set, raises `QueueEmptyError` if no items are available
39247
+ * within that timeout in milliseconds.
39248
+ */
39249
+ async getMany(n, options = {}) {
39250
+ return await this.#get(n, options.partition, options.timeout);
39251
+ }
39252
+ async #put(values, timeout, partition, partitionTtl) {
39253
+ const valuesEncoded = values.map((v) => dumps(v));
39254
+ const partitionKey = _Queue.#validatePartitionKey(partition);
39255
+ let delay = queueInitialPutBackoff;
39256
+ const deadline = timeout ? Date.now() + timeout : void 0;
39257
+ while (true) {
39258
+ try {
39259
+ await client.queuePut({
39260
+ queueId: this.queueId,
39261
+ values: valuesEncoded,
39262
+ partitionKey,
39263
+ partitionTtlSeconds: (partitionTtl || queueDefaultPartitionTtl) / 1e3
39264
+ });
39265
+ break;
39266
+ } catch (e) {
39267
+ if (e instanceof ClientError5 && e.code === Status5.RESOURCE_EXHAUSTED) {
39268
+ delay = Math.min(delay * 2, 3e4);
39269
+ if (deadline !== void 0) {
39270
+ const remaining = deadline - Date.now();
39271
+ if (remaining <= 0)
39272
+ throw new QueueFullError(`Put failed on ${this.queueId}.`);
39273
+ delay = Math.min(delay, remaining);
39274
+ }
39275
+ await new Promise((resolve) => setTimeout(resolve, delay));
39276
+ } else {
39277
+ throw e;
39278
+ }
39279
+ }
39280
+ }
39281
+ }
39282
+ /**
39283
+ * Add an item to the end of the queue.
39284
+ *
39285
+ * If the queue is full, this will retry with exponential backoff until the
39286
+ * provided `timeout` is reached, or indefinitely if `timeout` is not set.
39287
+ * Raises `QueueFullError` if the queue is still full after the timeout.
39288
+ */
39289
+ async put(v, options = {}) {
39290
+ await this.#put(
39291
+ [v],
39292
+ options.timeout,
39293
+ options.partition,
39294
+ options.partitionTtl
39295
+ );
39296
+ }
39297
+ /**
39298
+ * Add several items to the end of the queue.
39299
+ *
39300
+ * If the queue is full, this will retry with exponential backoff until the
39301
+ * provided `timeout` is reached, or indefinitely if `timeout` is not set.
39302
+ * Raises `QueueFullError` if the queue is still full after the timeout.
39303
+ */
39304
+ async putMany(values, options = {}) {
39305
+ await this.#put(
39306
+ values,
39307
+ options.timeout,
39308
+ options.partition,
39309
+ options.partitionTtl
39310
+ );
39311
+ }
39312
+ /** Return the number of objects in the queue. */
39313
+ async len(options = {}) {
39314
+ if (options.partition && options.total) {
39315
+ throw new InvalidError(
39316
+ "Partition must be null when requesting total length."
39317
+ );
39318
+ }
39319
+ const resp = await client.queueLen({
39320
+ queueId: this.queueId,
39321
+ partitionKey: _Queue.#validatePartitionKey(options.partition),
39322
+ total: options.total
39323
+ });
39324
+ return resp.len;
39325
+ }
39326
+ /** Iterate through items in a queue without mutation. */
39327
+ async *iterate(options = {}) {
39328
+ const { partition, itemPollTimeout = 0 } = options;
39329
+ let lastEntryId = void 0;
39330
+ const validatedPartitionKey = _Queue.#validatePartitionKey(partition);
39331
+ let fetchDeadline = Date.now() + itemPollTimeout;
39332
+ const maxPollDuration = 3e4;
39333
+ while (true) {
39334
+ const pollDuration = Math.max(
39335
+ 0,
39336
+ Math.min(maxPollDuration, fetchDeadline - Date.now())
39337
+ );
39338
+ const request = {
39339
+ queueId: this.queueId,
39340
+ partitionKey: validatedPartitionKey,
39341
+ itemPollTimeout: pollDuration / 1e3,
39342
+ lastEntryId: lastEntryId || ""
39343
+ };
39344
+ const response = await client.queueNextItems(request);
39345
+ if (response.items && response.items.length > 0) {
39346
+ for (const item of response.items) {
39347
+ yield loads(item.value);
39348
+ lastEntryId = item.entryId;
39349
+ }
39350
+ fetchDeadline = Date.now() + itemPollTimeout;
39351
+ } else if (Date.now() > fetchDeadline) {
39352
+ break;
39353
+ }
39354
+ }
39355
+ }
39356
+ };
39085
39357
  export {
39086
39358
  App,
39087
39359
  Cls,
39088
39360
  ClsInstance,
39361
+ ContainerProcess,
39089
39362
  FunctionCall,
39090
39363
  FunctionTimeoutError,
39091
39364
  Function_,
39092
39365
  Image2 as Image,
39093
39366
  InternalFailure,
39367
+ InvalidError,
39094
39368
  NotFoundError,
39369
+ Queue,
39370
+ QueueEmptyError,
39371
+ QueueFullError,
39095
39372
  RemoteError,
39096
39373
  Sandbox2 as Sandbox
39097
39374
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modal",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "Modal client library for JavaScript",
5
5
  "license": "MIT",
6
6
  "homepage": "https://modal.com/docs",
@@ -23,8 +23,11 @@
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
32
  "test": "vitest"
30
33
  },
@@ -36,14 +39,20 @@
36
39
  "uuid": "^11.1.0"
37
40
  },
38
41
  "devDependencies": {
42
+ "@eslint/js": "^9.28.0",
39
43
  "@types/node": "^22.15.2",
44
+ "eslint": "^9.28.0",
45
+ "globals": "^16.2.0",
40
46
  "grpc-tools": "^1.13.0",
47
+ "http-server": "^14.1.1",
41
48
  "p-queue": "^8.1.0",
42
49
  "prettier": "^3.5.3",
43
50
  "ts-proto": "^2.7.0",
44
51
  "tsup": "^8.4.0",
45
52
  "tsx": "^4.19.3",
53
+ "typedoc": "^0.28.5",
46
54
  "typescript": "~5.8.3",
55
+ "typescript-eslint": "^8.33.1",
47
56
  "vitest": "^3.1.2"
48
57
  }
49
58
  }