modal 0.3.1 → 0.3.3

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,19 +1,25 @@
1
- # modal-js development
1
+ # Modal JavaScript Library
2
2
 
3
- Setup after cloning the repo with submodules:
3
+ [![Version](https://img.shields.io/npm/v/modal.svg)](https://www.npmjs.org/package/modal)
4
+ [![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
+ [![Downloads](https://img.shields.io/npm/dm/modal.svg)](https://www.npmjs.com/package/modal)
4
6
 
5
- ```bash
6
- npm install
7
- ```
7
+ The [Modal](https://modal.com/) JavaScript SDK allows you to run Modal Functions and Sandboxes from server-side JavaScript applications.
8
8
 
9
- Then run a script with:
9
+ It comes with built-in TypeScript type definitions.
10
10
 
11
- ```bash
12
- node --import tsx path/to/script.ts
13
- ```
11
+ ## Documentation
12
+
13
+ See the [documentation and examples](https://github.com/modal-labs/libmodal?tab=readme-ov-file#javascript-modal-js) on GitHub.
14
14
 
15
- ## gRPC support
15
+ ## Requirements
16
16
 
17
- We're using `nice-grpc` because the `@grpc/grpc-js` library doesn't support promises and is difficult to customize with types.
17
+ Node 22 or higher, in [ES modules](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c) mode.
18
18
 
19
- This gRPC library depends on the `protobuf-ts` package, which is not compatible with tree shaking because `ModalClientDefinition` transitively references every type. However, since `modal-js` is a server-side package, having a larger bundled library is not a huge issue.
19
+ ## Installation
20
+
21
+ Install the package with
22
+
23
+ ```bash
24
+ npm install modal
25
+ ```
package/dist/index.d.ts CHANGED
@@ -93,4 +93,25 @@ declare class App {
93
93
  imageFromRegistry(tag: string): Promise<Image>;
94
94
  }
95
95
 
96
- export { App, Image, type LookupOptions, Sandbox, type SandboxCreateOptions, type StdioBehavior, type StreamMode };
96
+ /** Function execution exceeds the allowed time limit. */
97
+ declare class TimeoutError extends Error {
98
+ constructor(message: string);
99
+ }
100
+ /** An error on the Modal server, or a Python exception. */
101
+ declare class RemoteError extends Error {
102
+ constructor(message: string);
103
+ }
104
+ /** A retryable internal error from Modal. */
105
+ declare class InternalFailure extends Error {
106
+ constructor(message: string);
107
+ }
108
+
109
+ /** Represents a deployed Modal Function, which can be invoked remotely. */
110
+ declare class Function_ {
111
+ readonly functionId: string;
112
+ constructor(functionId: string);
113
+ static lookup(appName: string, name: string, options?: LookupOptions): Promise<Function_>;
114
+ remote(args?: any[], kwargs?: Record<string, any>): Promise<any>;
115
+ }
116
+
117
+ export { App, Function_, Image, InternalFailure, type LookupOptions, RemoteError, Sandbox, type SandboxCreateOptions, type StdioBehavior, type StreamMode, TimeoutError };
package/dist/index.js CHANGED
@@ -20247,6 +20247,57 @@ var GPUConfig = {
20247
20247
  return message;
20248
20248
  }
20249
20249
  };
20250
+ function createBaseGeneratorDone() {
20251
+ return { itemsTotal: 0 };
20252
+ }
20253
+ var GeneratorDone = {
20254
+ encode(message, writer = new BinaryWriter()) {
20255
+ if (message.itemsTotal !== 0) {
20256
+ writer.uint32(8).uint64(message.itemsTotal);
20257
+ }
20258
+ return writer;
20259
+ },
20260
+ decode(input, length) {
20261
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
20262
+ let end = length === void 0 ? reader.len : reader.pos + length;
20263
+ const message = createBaseGeneratorDone();
20264
+ while (reader.pos < end) {
20265
+ const tag = reader.uint32();
20266
+ switch (tag >>> 3) {
20267
+ case 1: {
20268
+ if (tag !== 8) {
20269
+ break;
20270
+ }
20271
+ message.itemsTotal = longToNumber(reader.uint64());
20272
+ continue;
20273
+ }
20274
+ }
20275
+ if ((tag & 7) === 4 || tag === 0) {
20276
+ break;
20277
+ }
20278
+ reader.skip(tag & 7);
20279
+ }
20280
+ return message;
20281
+ },
20282
+ fromJSON(object) {
20283
+ return { itemsTotal: isSet3(object.itemsTotal) ? globalThis.Number(object.itemsTotal) : 0 };
20284
+ },
20285
+ toJSON(message) {
20286
+ const obj = {};
20287
+ if (message.itemsTotal !== 0) {
20288
+ obj.itemsTotal = Math.round(message.itemsTotal);
20289
+ }
20290
+ return obj;
20291
+ },
20292
+ create(base) {
20293
+ return GeneratorDone.fromPartial(base ?? {});
20294
+ },
20295
+ fromPartial(object) {
20296
+ const message = createBaseGeneratorDone();
20297
+ message.itemsTotal = object.itemsTotal ?? 0;
20298
+ return message;
20299
+ }
20300
+ };
20250
20301
  function createBaseGenericPayloadType() {
20251
20302
  return { baseType: 0, subTypes: [] };
20252
20303
  }
@@ -37167,8 +37218,469 @@ var App = class _App {
37167
37218
  return await fromRegistryInternal(this.appId, tag);
37168
37219
  }
37169
37220
  };
37221
+
37222
+ // src/errors.ts
37223
+ var TimeoutError = class extends Error {
37224
+ constructor(message) {
37225
+ super(message);
37226
+ this.name = "TimeoutError";
37227
+ }
37228
+ };
37229
+ var RemoteError = class extends Error {
37230
+ constructor(message) {
37231
+ super(message);
37232
+ this.name = "RemoteError";
37233
+ }
37234
+ };
37235
+ var InternalFailure = class extends Error {
37236
+ constructor(message) {
37237
+ super(message);
37238
+ this.name = "InternalFailure";
37239
+ }
37240
+ };
37241
+
37242
+ // src/function.ts
37243
+ import { createHash } from "node:crypto";
37244
+
37245
+ // src/pickle.ts
37246
+ var PickleError = class extends Error {
37247
+ constructor(message) {
37248
+ super(message);
37249
+ this.name = "PickleError";
37250
+ }
37251
+ };
37252
+ var Writer = class {
37253
+ out = [];
37254
+ byte(b) {
37255
+ this.out.push(b & 255);
37256
+ }
37257
+ bytes(arr) {
37258
+ for (const b of arr) this.byte(b);
37259
+ }
37260
+ uint32LE(x) {
37261
+ this.byte(x);
37262
+ this.byte(x >>> 8);
37263
+ this.byte(x >>> 16);
37264
+ this.byte(x >>> 24);
37265
+ }
37266
+ uint64LE(n) {
37267
+ let v = BigInt(n);
37268
+ for (let i = 0; i < 8; i++) {
37269
+ this.byte(Number(v & 0xffn));
37270
+ v >>= 8n;
37271
+ }
37272
+ }
37273
+ float64BE(v) {
37274
+ const dv = new DataView(new ArrayBuffer(8));
37275
+ dv.setFloat64(0, v, false);
37276
+ this.bytes(new Uint8Array(dv.buffer));
37277
+ }
37278
+ toUint8() {
37279
+ return new Uint8Array(this.out);
37280
+ }
37281
+ };
37282
+ var Reader = class {
37283
+ constructor(buf, pos = 0) {
37284
+ this.buf = buf;
37285
+ this.pos = pos;
37286
+ }
37287
+ eof() {
37288
+ return this.pos >= this.buf.length;
37289
+ }
37290
+ byte() {
37291
+ return this.buf[this.pos++];
37292
+ }
37293
+ take(n) {
37294
+ const s = this.buf.subarray(this.pos, this.pos + n);
37295
+ this.pos += n;
37296
+ return s;
37297
+ }
37298
+ uint32LE() {
37299
+ const b0 = this.byte(), b1 = this.byte(), b2 = this.byte(), b3 = this.byte();
37300
+ return b0 | b1 << 8 | b2 << 16 | b3 << 24;
37301
+ }
37302
+ uint64LE() {
37303
+ const lo = this.uint32LE() >>> 0;
37304
+ const hi = this.uint32LE() >>> 0;
37305
+ return hi * 2 ** 32 + lo;
37306
+ }
37307
+ float64BE() {
37308
+ const v = new DataView(
37309
+ this.buf.buffer,
37310
+ this.buf.byteOffset + this.pos,
37311
+ 8
37312
+ ).getFloat64(0, false);
37313
+ this.pos += 8;
37314
+ return v;
37315
+ }
37316
+ };
37317
+ function encodeValue(val, w, proto) {
37318
+ if (val === null || val === void 0) {
37319
+ w.byte(78 /* NONE */);
37320
+ return;
37321
+ }
37322
+ if (typeof val === "boolean") {
37323
+ w.byte(val ? 136 /* NEWTRUE */ : 137 /* NEWFALSE */);
37324
+ return;
37325
+ }
37326
+ if (typeof val === "number") {
37327
+ if (Number.isInteger(val)) {
37328
+ if (val >= 0 && val <= 255) {
37329
+ w.byte(75 /* BININT1 */);
37330
+ w.byte(val);
37331
+ } else if (val >= -32768 && val <= 32767) {
37332
+ w.byte(77 /* BININT2 */);
37333
+ w.byte(val & 255);
37334
+ w.byte(val >> 8 & 255);
37335
+ } else {
37336
+ w.byte(74 /* BININT4 */);
37337
+ w.uint32LE(val >>> 0);
37338
+ }
37339
+ } else {
37340
+ w.byte(71 /* BINFLOAT */);
37341
+ w.float64BE(val);
37342
+ }
37343
+ maybeMemoize(w, proto);
37344
+ return;
37345
+ }
37346
+ if (typeof val === "string") {
37347
+ const utf8 = new TextEncoder().encode(val);
37348
+ if (proto >= 4 && utf8.length < 256) {
37349
+ w.byte(140 /* SHORT_BINUNICODE */);
37350
+ w.byte(utf8.length);
37351
+ } else if (proto >= 4 && utf8.length > 4294967295) {
37352
+ w.byte(138 /* BINUNICODE8 */);
37353
+ w.uint64LE(utf8.length);
37354
+ } else {
37355
+ w.byte(88 /* BINUNICODE */);
37356
+ w.uint32LE(utf8.length);
37357
+ }
37358
+ w.bytes(utf8);
37359
+ maybeMemoize(w, proto);
37360
+ return;
37361
+ }
37362
+ if (val instanceof Uint8Array) {
37363
+ const len = val.length;
37364
+ if (proto >= 4 && len < 256) {
37365
+ w.byte(142 /* SHORT_BINBYTES */);
37366
+ w.byte(len);
37367
+ } else if (proto >= 4 && len > 4294967295) {
37368
+ w.byte(141 /* BINBYTES8 */);
37369
+ w.uint64LE(len);
37370
+ } else {
37371
+ w.byte(66 /* BINBYTES */);
37372
+ w.uint32LE(len);
37373
+ }
37374
+ w.bytes(val);
37375
+ maybeMemoize(w, proto);
37376
+ return;
37377
+ }
37378
+ if (Array.isArray(val)) {
37379
+ w.byte(93 /* EMPTY_LIST */);
37380
+ maybeMemoize(w, proto);
37381
+ for (const item of val) {
37382
+ encodeValue(item, w, proto);
37383
+ w.byte(97 /* APPEND */);
37384
+ }
37385
+ return;
37386
+ }
37387
+ if (typeof val === "object") {
37388
+ w.byte(125 /* EMPTY_DICT */);
37389
+ maybeMemoize(w, proto);
37390
+ for (const [k, v] of Object.entries(val)) {
37391
+ encodeValue(k, w, proto);
37392
+ encodeValue(v, w, proto);
37393
+ w.byte(115 /* SETITEM */);
37394
+ }
37395
+ return;
37396
+ }
37397
+ throw new PickleError(`Unsupported type in dumps(): ${typeof val}`);
37398
+ }
37399
+ function maybeMemoize(w, proto) {
37400
+ if (proto >= 4) {
37401
+ w.byte(148 /* MEMOIZE */);
37402
+ }
37403
+ }
37404
+ function dumps(obj, protocol = 4) {
37405
+ if (![3, 4, 5].includes(protocol))
37406
+ throw new PickleError("Protocol must be 3, 4, or 5");
37407
+ const w = new Writer();
37408
+ w.byte(128 /* PROTO */);
37409
+ w.byte(protocol);
37410
+ if (protocol === 5) {
37411
+ w.byte(149 /* FRAME */);
37412
+ w.uint64LE(0);
37413
+ }
37414
+ encodeValue(obj, w, protocol);
37415
+ w.byte(46 /* STOP */);
37416
+ return w.toUint8();
37417
+ }
37418
+ function loads(buf) {
37419
+ const r = new Reader(buf);
37420
+ const op0 = r.byte();
37421
+ if (op0 !== 128 /* PROTO */) throw new PickleError("pickle missing PROTO header");
37422
+ const proto = r.byte();
37423
+ if (!(proto === 3 || proto === 4 || proto === 5))
37424
+ throw new PickleError(`Unsupported protocol ${proto}`);
37425
+ const stack = [];
37426
+ const memo = [];
37427
+ const tdec = new TextDecoder();
37428
+ function push(v) {
37429
+ stack.push(v);
37430
+ }
37431
+ function pop() {
37432
+ return stack.pop();
37433
+ }
37434
+ if (proto === 5 && buf[r["pos"]] === 149 /* FRAME */) {
37435
+ r.byte();
37436
+ const size = r.uint64LE();
37437
+ void size;
37438
+ }
37439
+ while (!r.eof()) {
37440
+ const op = r.byte();
37441
+ switch (op) {
37442
+ case 46 /* STOP */:
37443
+ return stack.pop();
37444
+ case 78 /* NONE */:
37445
+ push(null);
37446
+ break;
37447
+ case 136 /* NEWTRUE */:
37448
+ push(true);
37449
+ break;
37450
+ case 137 /* NEWFALSE */:
37451
+ push(false);
37452
+ break;
37453
+ case 75 /* BININT1 */:
37454
+ push(r.byte());
37455
+ break;
37456
+ case 77 /* BININT2 */: {
37457
+ const lo = r.byte(), hi = r.byte();
37458
+ const n = hi << 8 | lo;
37459
+ push(n & 32768 ? n - 65536 : n);
37460
+ break;
37461
+ }
37462
+ case 74 /* BININT4 */: {
37463
+ const n = r.uint32LE();
37464
+ push(n >>> 31 ? n - 4294967296 : n);
37465
+ break;
37466
+ }
37467
+ case 71 /* BINFLOAT */:
37468
+ push(r.float64BE());
37469
+ break;
37470
+ case 140 /* SHORT_BINUNICODE */: {
37471
+ const n = r.byte();
37472
+ push(tdec.decode(r.take(n)));
37473
+ break;
37474
+ }
37475
+ case 88 /* BINUNICODE */: {
37476
+ const n = r.uint32LE();
37477
+ push(tdec.decode(r.take(n)));
37478
+ break;
37479
+ }
37480
+ case 138 /* BINUNICODE8 */: {
37481
+ const n = r.uint64LE();
37482
+ push(tdec.decode(r.take(n)));
37483
+ break;
37484
+ }
37485
+ case 142 /* SHORT_BINBYTES */: {
37486
+ const n = r.byte();
37487
+ push(r.take(n));
37488
+ break;
37489
+ }
37490
+ case 66 /* BINBYTES */: {
37491
+ const n = r.uint32LE();
37492
+ push(r.take(n));
37493
+ break;
37494
+ }
37495
+ case 141 /* BINBYTES8 */: {
37496
+ const n = r.uint64LE();
37497
+ push(r.take(n));
37498
+ break;
37499
+ }
37500
+ case 93 /* EMPTY_LIST */:
37501
+ push([]);
37502
+ break;
37503
+ case 97 /* APPEND */: {
37504
+ const v = pop();
37505
+ const lst = pop();
37506
+ lst.push(v);
37507
+ push(lst);
37508
+ break;
37509
+ }
37510
+ case 125 /* EMPTY_DICT */:
37511
+ push({});
37512
+ break;
37513
+ case 115 /* SETITEM */: {
37514
+ const v = pop(), k = pop(), d = pop();
37515
+ d[k] = v;
37516
+ push(d);
37517
+ break;
37518
+ }
37519
+ // Memo handling ----------------------------------------
37520
+ case 148 /* MEMOIZE */:
37521
+ memo.push(stack[stack.length - 1]);
37522
+ break;
37523
+ case 113 /* BINPUT */:
37524
+ memo[r.byte()] = stack[stack.length - 1];
37525
+ break;
37526
+ case 114 /* LONG_BINPUT */:
37527
+ memo[r.uint32LE()] = stack[stack.length - 1];
37528
+ break;
37529
+ case 104 /* BINGET */:
37530
+ push(memo[r.byte()]);
37531
+ break;
37532
+ case 106 /* LONG_BINGET */:
37533
+ push(memo[r.uint32LE()]);
37534
+ break;
37535
+ case 149 /* FRAME */: {
37536
+ const _size = r.uint64LE();
37537
+ break;
37538
+ }
37539
+ default:
37540
+ throw new PickleError(`Unsupported opcode 0x${op.toString(16)}`);
37541
+ }
37542
+ }
37543
+ throw new PickleError("pickle stream ended without STOP");
37544
+ }
37545
+
37546
+ // src/function.ts
37547
+ var maxObjectSizeBytes = 2 * 1024 * 1024;
37548
+ function timeNow() {
37549
+ return Date.now() / 1e3;
37550
+ }
37551
+ var Function_ = class _Function_ {
37552
+ functionId;
37553
+ constructor(functionId) {
37554
+ this.functionId = functionId;
37555
+ }
37556
+ static async lookup(appName, name, options = {}) {
37557
+ const resp = await client.functionGet({
37558
+ appName,
37559
+ objectTag: name,
37560
+ namespace: 1 /* DEPLOYMENT_NAMESPACE_WORKSPACE */,
37561
+ environmentName: environmentName(options.environment)
37562
+ });
37563
+ return new _Function_(resp.functionId);
37564
+ }
37565
+ // Execute a single input into a remote Function.
37566
+ async remote(args = [], kwargs = {}) {
37567
+ const payload = dumps([args, kwargs]);
37568
+ let argsBlobId = void 0;
37569
+ if (payload.length > maxObjectSizeBytes) {
37570
+ argsBlobId = await blobUpload(payload);
37571
+ }
37572
+ const functionMapResponse = await client.functionMap({
37573
+ functionId: this.functionId,
37574
+ functionCallType: 1 /* FUNCTION_CALL_TYPE_UNARY */,
37575
+ functionCallInvocationType: 4 /* FUNCTION_CALL_INVOCATION_TYPE_SYNC */,
37576
+ pipelinedInputs: [
37577
+ {
37578
+ idx: 0,
37579
+ input: {
37580
+ args: argsBlobId ? void 0 : payload,
37581
+ argsBlobId,
37582
+ dataFormat: 1 /* DATA_FORMAT_PICKLE */
37583
+ }
37584
+ }
37585
+ ]
37586
+ });
37587
+ while (true) {
37588
+ const response = await client.functionGetOutputs({
37589
+ functionCallId: functionMapResponse.functionCallId,
37590
+ maxValues: 1,
37591
+ timeout: 55,
37592
+ lastEntryId: "0-0",
37593
+ clearOnSuccess: true,
37594
+ requestedAt: timeNow()
37595
+ });
37596
+ const outputs = response.outputs;
37597
+ if (outputs.length > 0) {
37598
+ return await processResult(outputs[0].result, outputs[0].dataFormat);
37599
+ }
37600
+ }
37601
+ }
37602
+ };
37603
+ async function processResult(result, dataFormat) {
37604
+ if (!result) {
37605
+ throw new Error("Received null result from invocation");
37606
+ }
37607
+ let data = new Uint8Array();
37608
+ if (result.data !== void 0) {
37609
+ data = result.data;
37610
+ } else if (result.dataBlobId) {
37611
+ data = await blobDownload(result.dataBlobId);
37612
+ }
37613
+ switch (result.status) {
37614
+ case 4 /* GENERIC_STATUS_TIMEOUT */:
37615
+ throw new TimeoutError(`Timeout: ${result.exception}`);
37616
+ case 6 /* GENERIC_STATUS_INTERNAL_FAILURE */:
37617
+ throw new InternalFailure(`Internal failure: ${result.exception}`);
37618
+ case 1 /* GENERIC_STATUS_SUCCESS */:
37619
+ break;
37620
+ default:
37621
+ throw new RemoteError(`Remote error: ${result.exception}`);
37622
+ }
37623
+ return deserializeDataFormat(data, dataFormat);
37624
+ }
37625
+ async function blobUpload(data) {
37626
+ const contentMd5 = createHash("md5").update(data).digest("base64");
37627
+ const contentSha256 = createHash("sha256").update(data).digest("base64");
37628
+ const resp = await client.blobCreate({
37629
+ contentMd5,
37630
+ contentSha256Base64: contentSha256,
37631
+ contentLength: data.length
37632
+ });
37633
+ if (resp.multipart) {
37634
+ throw new Error(
37635
+ "Function input size exceeds multipart upload threshold, unsupported by this SDK version"
37636
+ );
37637
+ } else if (resp.uploadUrl) {
37638
+ const uploadResp = await fetch(resp.uploadUrl, {
37639
+ method: "PUT",
37640
+ headers: {
37641
+ "Content-Type": "application/octet-stream",
37642
+ "Content-MD5": contentMd5
37643
+ },
37644
+ body: data
37645
+ });
37646
+ if (uploadResp.status < 200 || uploadResp.status >= 300) {
37647
+ throw new Error(`Failed blob upload: ${uploadResp.statusText}`);
37648
+ }
37649
+ return resp.blobId;
37650
+ } else {
37651
+ throw new Error("Missing upload URL in BlobCreate response");
37652
+ }
37653
+ }
37654
+ async function blobDownload(blobId) {
37655
+ const resp = await client.blobGet({ blobId });
37656
+ const s3resp = await fetch(resp.downloadUrl);
37657
+ if (!s3resp.ok) {
37658
+ throw new Error(`Failed to download blob: ${s3resp.statusText}`);
37659
+ }
37660
+ const buf = await s3resp.arrayBuffer();
37661
+ return new Uint8Array(buf);
37662
+ }
37663
+ function deserializeDataFormat(data, dataFormat) {
37664
+ if (!data) {
37665
+ return null;
37666
+ }
37667
+ switch (dataFormat) {
37668
+ case 1 /* DATA_FORMAT_PICKLE */:
37669
+ return loads(data);
37670
+ case 2 /* DATA_FORMAT_ASGI */:
37671
+ throw new Error("ASGI data format is not supported in Go");
37672
+ case 3 /* DATA_FORMAT_GENERATOR_DONE */:
37673
+ return GeneratorDone.decode(data);
37674
+ default:
37675
+ throw new Error(`Unsupported data format: ${dataFormat}`);
37676
+ }
37677
+ }
37170
37678
  export {
37171
37679
  App,
37680
+ Function_,
37172
37681
  Image2 as Image,
37173
- Sandbox2 as Sandbox
37682
+ InternalFailure,
37683
+ RemoteError,
37684
+ Sandbox2 as Sandbox,
37685
+ TimeoutError
37174
37686
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modal",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Modal client library for JavaScript",
5
5
  "license": "MIT",
6
6
  "homepage": "https://modal.com/docs",