From fdbb5d03a447f83127aa2582104b450c0e5a9815 Mon Sep 17 00:00:00 2001 From: Mattt Date: Thu, 25 May 2023 14:25:16 -0700 Subject: [PATCH 001/207] Fix JSDoc linting issues (#75) * Update jsdoc annotation for wait parameter to accept boolean * Fix eslint warning jsdoc/newline-after-description --- lib/collections.js | 1 + lib/predictions.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/collections.js b/lib/collections.js index 195bf8e8..de6f814c 100644 --- a/lib/collections.js +++ b/lib/collections.js @@ -12,6 +12,7 @@ async function getCollection(collection_slug) { /** * Fetch a list of model collections + * * @returns {Promise} - Resolves with the collections data */ async function listCollections() { diff --git a/lib/predictions.js b/lib/predictions.js index 47f2b516..3ec8546d 100644 --- a/lib/predictions.js +++ b/lib/predictions.js @@ -4,7 +4,7 @@ * @param {object} options * @param {string} options.version - Required. The model version * @param {object} options.input - Required. An object with the model inputs - * @param {object} [options.wait] - Whether to wait for the prediction to finish. Defaults to false + * @param {boolean|object} [options.wait] - Whether to wait for the prediction to finish. Defaults to false * @param {number} [options.wait.interval] - Polling interval in milliseconds. Defaults to 250 * @param {number} [options.wait.maxAttempts] - Maximum number of polling attempts. Defaults to no limit * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output From a06fa0da27426ae9354024b4ba54d5065ab93f3f Mon Sep 17 00:00:00 2001 From: Mattt Date: Fri, 9 Jun 2023 05:52:49 -0700 Subject: [PATCH 002/207] Throw error when constructing client without required auth parameter (#77) --- index.js | 4 ++++ index.test.ts | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/index.js b/index.js index 390038d1..ce97fcfd 100644 --- a/index.js +++ b/index.js @@ -33,6 +33,10 @@ class Replicate { * @param {Function} [options.fetch] - Fetch function to use. Defaults to `globalThis.fetch` */ constructor(options) { + if (!options.auth) { + throw new Error('Missing required parameter: auth'); + } + this.auth = options.auth; this.userAgent = options.userAgent || `replicate-javascript/${packageJSON.version}`; diff --git a/index.test.ts b/index.test.ts index eab4ffec..3bcfc86a 100644 --- a/index.test.ts +++ b/index.test.ts @@ -33,6 +33,22 @@ describe('Replicate client', () => { }); expect(clientWithCustomUserAgent.userAgent).toBe('my-app/1.2.3'); }); + + test('Throws error if no auth token is provided', () => { + const expected = 'Missing required parameter: auth' + + expect(() => { + new Replicate({ auth: undefined }); + }).toThrow(expected); + + expect(() => { + new Replicate({ auth: null }); + }).toThrow(expected); + + expect(() => { + new Replicate({ auth: "" }); + }).toThrow(expected); + }); }); describe('collections.list', () => { From 9f9d09f18271eb7e07a0138e3bc8fb11ec7993c9 Mon Sep 17 00:00:00 2001 From: Mattt Date: Fri, 9 Jun 2023 06:40:12 -0700 Subject: [PATCH 003/207] Sleep after creating a prediction before polling for updates (#79) --- lib/predictions.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/predictions.js b/lib/predictions.js index 3ec8546d..c5ffda94 100644 --- a/lib/predictions.js +++ b/lib/predictions.js @@ -21,6 +21,11 @@ async function createPrediction(options) { if (wait) { const { maxAttempts, interval } = wait; + + // eslint-disable-next-line no-promise-executor-return + const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + await sleep(interval || 250); + return this.wait(await prediction, { maxAttempts, interval }); } From 415b35d40c2e5478a2452decc673a0f7e5918077 Mon Sep 17 00:00:00 2001 From: Mattt Date: Fri, 9 Jun 2023 06:49:04 -0700 Subject: [PATCH 004/207] Add engines.node entry to package.json (#80) --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 5d000012..aa4332c8 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,9 @@ "bugs": "https://github.com/replicate/replicate-javascript/issues", "license": "Apache-2.0", "main": "index.js", + "engines": { + "node": ">=18" + }, "scripts": { "lint": "eslint .", "test": "jest" From 32e6c10b5dba357908c193eb231e970ad2697872 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 9 Jun 2023 06:51:31 -0700 Subject: [PATCH 005/207] Update engines.node entry with npm, git, and yarn --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index aa4332c8..7871ba6e 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,10 @@ "license": "Apache-2.0", "main": "index.js", "engines": { - "node": ">=18" + "node": ">=16.6.0", + "npm": ">=7.19.0", + "git": ">=2.11.0", + "yarn": ">=1.7.0" }, "scripts": { "lint": "eslint .", From d937b6ad92ae82cd8c383eec3dcb4a40360d7845 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 9 Jun 2023 06:54:05 -0700 Subject: [PATCH 006/207] Update package-lock.json --- package-lock.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package-lock.json b/package-lock.json index 740a9d32..8c38b095 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,12 @@ "nock": "^13.3.0", "ts-jest": "^29.1.0", "typescript": "^5.0.2" + }, + "engines": { + "git": ">=2.11.0", + "node": ">=16.6.0", + "npm": ">=7.19.0", + "yarn": ">=1.7.0" } }, "node_modules/@ampproject/remapping": { From 91addd5e837f65716ad368869cd72b6b26270ef3 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 9 Jun 2023 06:58:25 -0700 Subject: [PATCH 007/207] Update devDependencies --- package-lock.json | 68 ++++++++++++++++++++++++++++++++++++----------- package.json | 2 +- 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8c38b095..cdd92ac7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "eslint-config-airbnb-base": "^15.0.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-jest": "^27.2.1", - "eslint-plugin-jsdoc": "^40.1.0", + "eslint-plugin-jsdoc": "^46.2.6", "eslint-plugin-n": "^15.6.1", "eslint-plugin-promise": "^6.1.1", "jest": "^29.5.0", @@ -645,17 +645,17 @@ "dev": true }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.37.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.37.0.tgz", - "integrity": "sha512-hjK0wnsPCYLlF+HHB4R/RbUjOWeLW2SlarB67+Do5WsKILOkmIZvvPJFbtWSmbypxcjpoECLAMzoao0D4Bg5ZQ==", + "version": "0.39.4", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.39.4.tgz", + "integrity": "sha512-Jvw915fjqQct445+yron7Dufix9A+m9j1fCJYlCo1FWlRvTxa3pjJelxdSTdaLWcTwRU6vbL+NYjO4YuNIS5Qg==", "dev": true, "dependencies": { "comment-parser": "1.3.1", - "esquery": "^1.4.0", + "esquery": "^1.5.0", "jsdoc-type-pratt-parser": "~4.0.0" }, "engines": { - "node": "^14 || ^16 || ^17 || ^18 || ^19" + "node": ">=16" } }, "node_modules/@eslint-community/eslint-utils": { @@ -1664,6 +1664,15 @@ "node": ">= 8" } }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1933,6 +1942,18 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/builtins": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", @@ -2629,21 +2650,23 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "40.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-40.1.0.tgz", - "integrity": "sha512-ANvrhiu62VlSorARM0hup60VQsS3hNyp0Ca7cnJDj8tpJzM7tNhBVqMVYXSuLzEmqrpwx6aAh+NAN2DdAGG5fQ==", + "version": "46.2.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.2.6.tgz", + "integrity": "sha512-zIaK3zbSrKuH12bP+SPybPgcHSM6MFzh3HFeaODzmsF1N8C1l8dzJ22cW1aq4g0+nayU1VMjmNf7hg0dpShLrA==", "dev": true, "dependencies": { - "@es-joy/jsdoccomment": "~0.37.0", + "@es-joy/jsdoccomment": "~0.39.4", + "are-docs-informative": "^0.0.2", "comment-parser": "1.3.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "esquery": "^1.5.0", - "semver": "^7.3.8", + "is-builtin-module": "^3.2.1", + "semver": "^7.5.1", "spdx-expression-parse": "^3.0.1" }, "engines": { - "node": "^14 || ^16 || ^17 || ^18 || ^19" + "node": ">=16" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" @@ -3482,6 +3505,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -5267,9 +5305,9 @@ } }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" diff --git a/package.json b/package.json index 7871ba6e..c3feb5e9 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "eslint-config-airbnb-base": "^15.0.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-jest": "^27.2.1", - "eslint-plugin-jsdoc": "^40.1.0", + "eslint-plugin-jsdoc": "^46.2.6", "eslint-plugin-n": "^15.6.1", "eslint-plugin-promise": "^6.1.1", "jest": "^29.5.0", From 8c8bcd9cac3ca4b1bed306d984021c81dd04980a Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 9 Jun 2023 06:58:39 -0700 Subject: [PATCH 008/207] 0.12.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index cdd92ac7..2718e22a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.12.1", + "version": "0.12.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.12.1", + "version": "0.12.2", "license": "Apache-2.0", "devDependencies": { "@types/jest": "^29.5.0", diff --git a/package.json b/package.json index c3feb5e9..7323d8ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.12.1", + "version": "0.12.2", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From edbac161b8b89ea083e60f902cd8e89483271436 Mon Sep 17 00:00:00 2001 From: Mattt Date: Mon, 12 Jun 2023 03:02:11 -0700 Subject: [PATCH 009/207] Update usage documentation in README (#83) * Fix signature for trainings.create * Add entry for trainings.cancel * Replace {version} placeholder with id for flan-t5-xl * Add entry for predictions.cancel * Update timestamps in example responses * Reorder fields in JSON responses --- README.md | 103 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 84 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index d0593ceb..07457328 100644 --- a/README.md +++ b/README.md @@ -248,9 +248,6 @@ const response = await replicate.predictions.create(options); "get": "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", "cancel": "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel" }, - "created_at": "2022-04-26T22:13:06.224088Z", - "started_at": null, - "completed_at": null, "status": "succeeded", "input": { "text": "Alice" @@ -258,7 +255,10 @@ const response = await replicate.predictions.create(options); "output": null, "error": null, "logs": null, - "metrics": {} + "metrics": {}, + "created_at": "2022-04-26T22:13:06.224088Z", + "started_at": null, + "completed_at": null } ``` @@ -280,17 +280,49 @@ const response = await replicate.predictions.get(prediction_id); "get": "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", "cancel": "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel" }, + "status": "starting", + "input": { + "text": "Alice" + }, + "output": null, + "error": null, + "logs": null, + "metrics": {}, "created_at": "2022-04-26T22:13:06.224088Z", "started_at": null, - "completed_at": null, - "status": "starting", + "completed_at": null +} +``` + +### `replicate.predictions.cancel` + +```js +const response = await replicate.predictions.cancel(prediction_id); +``` + +| name | type | description | +| --------------- | ------ | ------------------------------- | +| `prediction_id` | number | **Required**. The prediction id | + +```jsonc +{ + "id": "ufawqhfynnddngldkgtslldrkq", + "version": "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + "urls": { + "get": "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", + "cancel": "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel" + }, + "status": "canceled", "input": { "text": "Alice" }, "output": null, "error": null, "logs": null, - "metrics": {} + "metrics": {}, + "created_at": "2022-04-26T22:13:06.224088Z", + "started_at": "2022-04-26T22:13:06.224088Z", + "completed_at": "2022-04-26T22:13:06.224088Z" } ``` @@ -314,11 +346,11 @@ const response = await replicate.predictions.list(); "get": "https://api.replicate.com/v1/predictions/jpzd7hm5gfcapbfyt4mqytarku", "cancel": "https://api.replicate.com/v1/predictions/jpzd7hm5gfcapbfyt4mqytarku/cancel" }, + "source": "web", + "status": "succeeded", "created_at": "2022-04-26T20:00:40.658234Z", "started_at": "2022-04-26T20:00:84.583803Z", - "completed_at": "2022-04-26T20:02:27.648305Z", - "source": "web", - "status": "succeeded" + "completed_at": "2022-04-26T20:02:27.648305Z" } /* ... */ ] @@ -328,12 +360,14 @@ const response = await replicate.predictions.list(); ### `replicate.trainings.create` ```js -const response = await replicate.trainings.create(options); +const response = await replicate.trainings.create(model_owner, model_name, version_id, options); ``` | name | type | description | | ------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `options.version` | string | **Required**. The model version | +| `model_owner` | string | **Required**. The name of the user or organization that owns the model. | +| `model_name` | string | **Required**. The name of the model. | +| `version` | string | **Required**. The model version | | `options.destination` | string | **Required**. The destination for the trained version in the form `{username}/{model_name}` | | `options.input` | object | **Required**. An object with the model's inputs | | `options.webhook` | string | An HTTPS URL for receiving a webhook when the training has new output | @@ -342,7 +376,7 @@ const response = await replicate.trainings.create(options); ```jsonc { "id": "zz4ibbonubfz7carwiefibzgga", - "version": "{version}", + "version": "3ae0799123a1fe11f8c89fd99632f843fc5f7a761630160521c4253149754523", "status": "starting", "input": { "text": "..." @@ -369,7 +403,7 @@ const response = await replicate.trainings.get(training_id); ```jsonc { "id": "zz4ibbonubfz7carwiefibzgga", - "version": "{version}", + "version": "3ae0799123a1fe11f8c89fd99632f843fc5f7a761630160521c4253149754523", "status": "succeeded", "input": { "data": "..." @@ -381,9 +415,40 @@ const response = await replicate.trainings.get(training_id); "error": null, "logs": null, "webhook_completed": null, - "started_at": null, + "started_at": "2023-03-28T21:48:02.402755Z", "created_at": "2023-03-28T21:47:58.566434Z", - "completed_at": null + "completed_at": "2023-03-28T02:49:48.492023Z" +} +``` + +### `replicate.trainings.cancel` + +```js +const response = await replicate.trainings.cancel(training_id); +``` + +| name | type | description | +| ------------- | ------ | ----------------------------- | +| `training_id` | number | **Required**. The training id | + +```jsonc +{ + "id": "zz4ibbonubfz7carwiefibzgga", + "version": "3ae0799123a1fe11f8c89fd99632f843fc5f7a761630160521c4253149754523", + "status": "canceled", + "input": { + "data": "..." + "param1": "..." + }, + "output": { + "version": "..." + }, + "error": null, + "logs": null, + "webhook_completed": null, + "started_at": "2023-03-28T21:48:02.402755Z", + "created_at": "2023-03-28T21:47:58.566434Z", + "completed_at": "2023-03-28T02:49:48.492023Z" } ``` @@ -407,11 +472,11 @@ const response = await replicate.trainings.list(); "get": "https://api.replicate.com/v1/trainings/jpzd7hm5gfcapbfyt4mqytarku", "cancel": "https://api.replicate.com/v1/trainings/jpzd7hm5gfcapbfyt4mqytarku/cancel" }, + "source": "web", + "status": "succeeded", "created_at": "2022-04-26T20:00:40.658234Z", "started_at": "2022-04-26T20:00:84.583803Z", - "completed_at": "2022-04-26T20:02:27.648305Z", - "source": "web", - "status": "succeeded" + "completed_at": "2022-04-26T20:02:27.648305Z" } /* ... */ ] From 7ed6bb1047938af7de6004185e971fb208e19a05 Mon Sep 17 00:00:00 2001 From: Mattt Date: Mon, 12 Jun 2023 03:14:37 -0700 Subject: [PATCH 010/207] Throw an error if webhook URL is invalid (#84) --- index.test.ts | 41 +++++++++++++++++++++++++++++++++++++++++ lib/predictions.js | 9 +++++++++ lib/trainings.js | 9 +++++++++ 3 files changed, 59 insertions(+) diff --git a/index.test.ts b/index.test.ts index 3bcfc86a..d948a531 100644 --- a/index.test.ts +++ b/index.test.ts @@ -151,6 +151,18 @@ describe('Replicate client', () => { }); expect(prediction.id).toBe('ufawqhfynnddngldkgtslldrkq'); }); + + test('Throws an error if webhook URL is invalid', async () => { + await expect(async () => { + await client.predictions.create({ + version: '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + input: { + text: 'Alice', + }, + webhook: 'invalid-url', + }); + }).rejects.toThrow('Invalid webhook URL'); + }); // Add more tests for error handling, edge cases, etc. }); @@ -321,6 +333,23 @@ describe('Replicate client', () => { expect(training.id).toBe('zz4ibbonubfz7carwiefibzgga'); }); + test('Throws an error if webhook is not a valid URL', async () => { + await expect( + client.trainings.create( + 'owner', + 'model', + '632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532', + { + destination: 'new_owner/new_model', + input: { + text: '...', + }, + webhook: 'invalid-url', + } + ) + ).rejects.toThrow('Invalid webhook URL'); + }); + // Add more tests for error handling, edge cases, etc. }); @@ -499,6 +528,18 @@ describe('Replicate client', () => { // @ts-expect-error await expect(client.run(':abc123', options)).rejects.toThrow(); }); + + test('Throws an error if webhook URL is invalid', async () => { + await expect(async () => { + await client.run( + 'owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', { + input: { + text: 'Alice', + }, + webhook: 'invalid-url', + }); + }).rejects.toThrow('Invalid webhook URL'); + }); }); // Continue with tests for other methods diff --git a/lib/predictions.js b/lib/predictions.js index c5ffda94..ba46ef8f 100644 --- a/lib/predictions.js +++ b/lib/predictions.js @@ -14,6 +14,15 @@ async function createPrediction(options) { const { wait, ...data } = options; + if (data.webhook) { + try { + // eslint-disable-next-line no-new + new URL(data.webhook); + } catch (err) { + throw new Error('Invalid webhook URL'); + } + } + const prediction = this.request('/predictions', { method: 'POST', data, diff --git a/lib/trainings.js b/lib/trainings.js index 8f8c5d1b..688e9199 100644 --- a/lib/trainings.js +++ b/lib/trainings.js @@ -14,6 +14,15 @@ async function createTraining(model_owner, model_name, version_id, options) { const { ...data } = options; + if (data.webhook) { + try { + // eslint-disable-next-line no-new + new URL(data.webhook); + } catch (err) { + throw new Error('Invalid webhook URL'); + } + } + const training = this.request(`/models/${model_owner}/${model_name}/versions/${version_id}/trainings`, { method: 'POST', data, From 119b8e6a65372f915fb6bd990b6a653aa9a2d577 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Mon, 12 Jun 2023 03:15:21 -0700 Subject: [PATCH 011/207] 0.12.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2718e22a..45cd240c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.12.2", + "version": "0.12.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.12.2", + "version": "0.12.3", "license": "Apache-2.0", "devDependencies": { "@types/jest": "^29.5.0", diff --git a/package.json b/package.json index 7323d8ad..146785ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.12.2", + "version": "0.12.3", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 91861b70ebd33a8e9f50c38f41a208e28cc2764d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B1=B3=E7=A9=BA=E6=A0=BC?= Date: Tue, 27 Jun 2023 19:48:28 +0800 Subject: [PATCH 012/207] Update README.md (#86) Here, the file needs to be read as a raw buffer, and there is no need to specify the encoding parameter. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 07457328..b1413370 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ convert its data into a base64-encoded data URI: import { promises as fs } from "fs"; // Read the file into a buffer -const data = await fs.readFile("path/to/image.png", "utf-8"); +const data = await fs.readFile("path/to/image.png"); // Convert the buffer into a base64-encoded string const base64 = data.toString("base64"); // Set MIME type for PNG image From 3cea681791bd7795a2f85597328219c45cf38264 Mon Sep 17 00:00:00 2001 From: Mattt Date: Mon, 10 Jul 2023 03:44:24 -0700 Subject: [PATCH 013/207] Document how to customize client requests by setting fetch property (#96) --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index b1413370..497f486a 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,20 @@ const replicate = new Replicate({ }); ``` +You can override the `fetch` property to add custom behavior to client requests, +such as injecting headers or adding log statements. + +```js +client.fetch = (url, options) => { + const headers = new Headers(options && options.headers); + headers.append("X-Custom-Header", "some value"); + + console.log("fetch", { url, ...options, headers }); + + return fetch(url, { ...options, headers }); +}; +``` + ### `replicate.models.get` ```js From 84ce0d3c98435da7ec9a45ab56d20f263f6d2600 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Jul 2023 04:22:26 -0700 Subject: [PATCH 014/207] Bump word-wrap from 1.2.3 to 1.2.4 (#101) Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4. - [Release notes](https://github.com/jonschlinkert/word-wrap/releases) - [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4) --- updated-dependencies: - dependency-name: word-wrap dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 45cd240c..2d91ecc9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5956,9 +5956,9 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", "dev": true, "engines": { "node": ">=0.10.0" From 725755aa8c2bb84a2fdb24f277a57f0f631cef87 Mon Sep 17 00:00:00 2001 From: Mattt Date: Mon, 24 Jul 2023 09:51:35 -0700 Subject: [PATCH 015/207] Add information about training API to README (#102) --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 497f486a..06b7436f 100644 --- a/README.md +++ b/README.md @@ -373,6 +373,14 @@ const response = await replicate.predictions.list(); ### `replicate.trainings.create` +Use the training API to fine-tune language models +to make them better at a particular task. +To see what **language models** currently support fine-tuning, +check out Replicate's [collection of trainable language models](https://replicate.com/collections/trainable-language-models). + +If you're looking to fine-tune **image models**, +check out Replicate's [guide to fine-tuning image models](https://replicate.com/docs/guides/fine-tune-an-image-model). + ```js const response = await replicate.trainings.create(model_owner, model_name, version_id, options); ``` @@ -404,6 +412,10 @@ const response = await replicate.trainings.create(model_owner, model_name, versi } ``` +> **Warning** +> If you try to fine-tune a model that doesn't support training, +> you'll get a `400 Bad Request` response from the server. + ### `replicate.trainings.get` ```js From 6343c52a0b911c3538096f41ab53453e4b833011 Mon Sep 17 00:00:00 2001 From: Mattt Date: Tue, 25 Jul 2023 04:45:08 -0700 Subject: [PATCH 016/207] Provide more information in API errors (#103) --- index.d.ts | 13 +++++++++---- index.js | 21 ++++++++++++++++----- index.test.ts | 23 +++++++++++++++++++++++ lib/error.js | 17 +++++++++++++++++ 4 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 lib/error.js diff --git a/index.d.ts b/index.d.ts index df4f434b..03efee09 100644 --- a/index.d.ts +++ b/index.d.ts @@ -2,10 +2,9 @@ declare module 'replicate' { type Status = 'starting' | 'processing' | 'succeeded' | 'failed' | 'canceled'; type WebhookEventType = 'start' | 'output' | 'logs' | 'completed'; - interface Page { - previous?: string; - next?: string; - results: T[]; + export interface ApiError extends Error { + request: Request; + response: Response; } export interface Collection { @@ -58,6 +57,12 @@ declare module 'replicate' { export type Training = Prediction; + interface Page { + previous?: string; + next?: string; + results: T[]; + } + export default class Replicate { constructor(options: { auth: string; diff --git a/index.js b/index.js index ce97fcfd..de754313 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,10 @@ +const ApiError = require('./lib/error'); + const collections = require('./lib/collections'); const models = require('./lib/models'); const predictions = require('./lib/predictions'); const trainings = require('./lib/trainings'); + const packageJSON = require('./package.json'); /** @@ -127,7 +130,7 @@ class Replicate { * @param {object} [parameters.params] - Query parameters * @param {object} [parameters.data] - Body parameters * @returns {Promise} - Resolves with the API response data - * @throws {Error} If the request failed + * @throws {ApiError} If the request failed */ async request(route, parameters) { const { auth, baseUrl, userAgent } = this; @@ -149,14 +152,22 @@ class Replicate { 'User-Agent': userAgent, }; - const response = await this.fetch(url, { + const options = { method, headers, body: data ? JSON.stringify(data) : undefined, - }); + }; + + const response = await this.fetch(url, options); if (!response.ok) { - throw new Error(`API request failed: ${response.statusText}`); + const request = new Request(url, options); + const responseText = await response.text(); + throw new ApiError( + `Request to ${url} failed with status ${response.status} ${response.statusText}: ${responseText}.`, + request, + response, + ); } return response.json(); @@ -173,7 +184,7 @@ class Replicate { * @param {Function} endpoint - Function that returns a promise for the next page of results * @yields {object[]} Each page of results */ - async *paginate(endpoint) { + async * paginate(endpoint) { const response = await endpoint(); yield response.results; if (response.next) { diff --git a/index.test.ts b/index.test.ts index d948a531..0538afd8 100644 --- a/index.test.ts +++ b/index.test.ts @@ -163,6 +163,29 @@ describe('Replicate client', () => { }); }).rejects.toThrow('Invalid webhook URL'); }); + + test('Throws an error with details failing response is JSON', async () => { + nock(BASE_URL) + .post('/predictions') + .reply(400, { + status: 400, + detail: "Invalid input", + }, { "Content-Type": "application/json" }) + + try { + expect.assertions(2); + + await client.predictions.create({ + version: '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + input: { + text: null, + }, + }); + } catch (error) { + expect(error.response.status).toBe(400); + expect(error.message).toContain("Invalid input") + } + }) // Add more tests for error handling, edge cases, etc. }); diff --git a/lib/error.js b/lib/error.js new file mode 100644 index 00000000..50c0e2fe --- /dev/null +++ b/lib/error.js @@ -0,0 +1,17 @@ +class ApiError extends Error { + /** + * Creates a representation of an API error. + * @param {string} message - Error message + * @param {Request} request - HTTP request + * @param {Response} response - HTTP response + * @returns {ApiError} + */ + constructor(message, request, response) { + super(message); + this.name = 'ApiError'; + this.request = request; + this.response = response; + } +} + +module.exports = ApiError; From 398cca5fabe390c12942641de35db06272caaeca Mon Sep 17 00:00:00 2001 From: Mattt Date: Tue, 25 Jul 2023 05:18:41 -0700 Subject: [PATCH 017/207] Fix `jsdoc/tag-lines` linter rule setting (#105) * Fix jsdoc/tag-lines linter rule setting * Fix linting errors in ApiError --- .eslintrc.js | 1 + lib/error.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 57a44ddb..e33833fb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -23,5 +23,6 @@ module.exports = { 'max-len': 'off', 'operator-linebreak': 'off', 'jsdoc/require-param-description': 'off', + 'jsdoc/tag-lines': ['error', 'any', { startLines: 1 }], }, }; diff --git a/lib/error.js b/lib/error.js index 50c0e2fe..fc3b1840 100644 --- a/lib/error.js +++ b/lib/error.js @@ -1,10 +1,11 @@ class ApiError extends Error { /** * Creates a representation of an API error. + * * @param {string} message - Error message * @param {Request} request - HTTP request * @param {Response} response - HTTP response - * @returns {ApiError} + * @returns {ApiError} - An instance of ApiError */ constructor(message, request, response) { super(message); From c71672211fd615cbec1f21726716db93d9f385d6 Mon Sep 17 00:00:00 2001 From: Mattt Date: Tue, 25 Jul 2023 06:09:31 -0700 Subject: [PATCH 018/207] Add `stream` option to `predictions.create` operation (#99) * Update request method to return response instead of JSON * Add stream parameter to predictions.create * Remove wait parameter from predictions.create * Rename wait parameter maxAttempts to max_attempts * Add documentation for ApiError class * Specify withCredentials options in EventSource constructor --- README.md | 53 +++++++++++++++++++++++++++++---- index.d.ts | 20 +++++++++++-- index.js | 74 +++++++++++++++++++++++++++------------------- index.test.ts | 20 ++++++++++++- lib/collections.js | 8 +++-- lib/error.js | 3 ++ lib/models.js | 12 ++++++-- lib/predictions.js | 36 ++++++++++------------ lib/trainings.js | 16 ++++++---- 9 files changed, 172 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 06b7436f..0e175576 100644 --- a/README.md +++ b/README.md @@ -251,6 +251,7 @@ const response = await replicate.predictions.create(options); | ------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- | | `options.version` | string | **Required**. The model version | | `options.input` | object | **Required**. An object with the model's inputs | +| `options.stream` | boolean | Requests a URL for streaming output output | | `options.webhook` | string | An HTTPS URL for receiving a webhook when the prediction has new output | | `options.webhook_events_filter` | string[] | You can change which events trigger webhook requests by specifying webhook events (`start` \| `output` \| `logs` \| `completed`) | @@ -258,10 +259,6 @@ const response = await replicate.predictions.create(options); { "id": "ufawqhfynnddngldkgtslldrkq", "version": "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", - "urls": { - "get": "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", - "cancel": "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel" - }, "status": "succeeded", "input": { "text": "Alice" @@ -272,10 +269,56 @@ const response = await replicate.predictions.create(options); "metrics": {}, "created_at": "2022-04-26T22:13:06.224088Z", "started_at": null, - "completed_at": null + "completed_at": null, + "urls": { + "get": "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", + "cancel": "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel", + "stream": "https://streaming.api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq" // Present only if `options.stream` is `true` + } } ``` +#### Streaming + +Specify the `stream` option when creating a prediction +to request a URL to receive streaming output using +[server-sent events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events). + +If the requested model version supports streaming, +then the returned prediction will have a `stream` entry in its `urls` property +with a URL that you can use to construct an +[`EventSource`](https://developer.mozilla.org/en-US/docs/Web/API/EventSource). + +```js +if (prediction && prediction.urls && prediction.urls.stream) { + const source = new EventSource(prediction.urls.stream, { withCredentials: true }); + + source.addEventListener("output", (e) => { + console.log("output", e.data); + }); + + source.addEventListener("error"), (e) => { + console.error("error", JSON.parse(e.data)); + }); + + source.addEventListener("done"), (e) => { + source.close(); + console.log("done", JSON.parse(e.data)); + }); +} +``` + +A prediction's event stream consists of the following event types: + +| event | format | description | +| -------- | ---------- | ---------------------------------------------- | +| `output` | plain text | Emitted when the prediction returns new output | +| `error` | JSON | Emitted when the prediction returns an error | +| `done` | JSON | Emitted when the prediction finishes | + +A `done` event is emitted when a prediction finishes successfully, +is cancelled, or produces an error. + ### `replicate.predictions.get` ```js diff --git a/index.d.ts b/index.d.ts index 03efee09..4f6e49ff 100644 --- a/index.d.ts +++ b/index.d.ts @@ -53,6 +53,11 @@ declare module 'replicate' { created_at: string; updated_at: string; completed_at?: string; + urls: { + get: string; + cancel: string; + stream?: string; + }; } export type Training = Prediction; @@ -80,18 +85,26 @@ declare module 'replicate' { identifier: `${string}/${string}:${string}`, options: { input: object; - wait?: boolean | { interval?: number; maxAttempts?: number }; + wait?: { interval?: number; max_attempts?: number }; webhook?: string; webhook_events_filter?: WebhookEventType[]; } ): Promise; - request(route: string, parameters: any): Promise; + + request(route: string | URL, options: { + method?: string; + headers?: object | Headers; + params?: object; + data?: object; + }): Promise; + paginate(endpoint: () => Promise>): AsyncGenerator<[ T ]>; + wait( prediction: Prediction, options: { interval?: number; - maxAttempts?: number; + max_attempts?: number; } ): Promise; @@ -116,6 +129,7 @@ declare module 'replicate' { create(options: { version: string; input: object; + stream?: boolean; webhook?: string; webhook_events_filter?: WebhookEventType[]; }): Promise; diff --git a/index.js b/index.js index de754313..e6d50d35 100644 --- a/index.js +++ b/index.js @@ -80,15 +80,17 @@ class Replicate { * @param {string} identifier - Required. The model version identifier in the format "{owner}/{name}:{version}" * @param {object} options * @param {object} options.input - Required. An object with the model inputs - * @param {object} [options.wait] - Whether to wait for the prediction to finish. Defaults to false + * @param {object} [options.wait] - Options for waiting for the prediction to finish * @param {number} [options.wait.interval] - Polling interval in milliseconds. Defaults to 250 - * @param {number} [options.wait.maxAttempts] - Maximum number of polling attempts. Defaults to no limit + * @param {number} [options.wait.max_attempts] - Maximum number of polling attempts. Defaults to no limit * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) * @throws {Error} If the prediction failed * @returns {Promise} - Resolves with the output of running the model */ async run(identifier, options) { + const { wait, ...data } = options; + // Define a pattern for owner and model names that allows // letters, digits, and certain special characters. // Example: "user123", "abc__123", "user.name" @@ -108,12 +110,14 @@ class Replicate { } const { version } = match.groups; - const prediction = await this.predictions.create({ - wait: true, - ...options, + + let prediction = await this.predictions.create({ + ...data, version, }); + prediction = await this.wait(prediction, wait || {}); + if (prediction.status === 'failed') { throw new Error(`Prediction failed: ${prediction.error}`); } @@ -125,43 +129,53 @@ class Replicate { * Make a request to the Replicate API. * * @param {string} route - REST API endpoint path - * @param {object} parameters - Request parameters - * @param {string} [parameters.method] - HTTP method. Defaults to GET - * @param {object} [parameters.params] - Query parameters - * @param {object} [parameters.data] - Body parameters - * @returns {Promise} - Resolves with the API response data + * @param {object} options - Request parameters + * @param {string} [options.method] - HTTP method. Defaults to GET + * @param {object} [options.params] - Query parameters + * @param {object|Headers} [options.headers] - HTTP headers + * @param {object} [options.data] - Body parameters + * @returns {Promise} - Resolves with the response object * @throws {ApiError} If the request failed */ - async request(route, parameters) { + async request(route, options) { const { auth, baseUrl, userAgent } = this; - const url = new URL( - route.startsWith('/') ? route.slice(1) : route, - baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/` - ); + let url; + if (route instanceof URL) { + url = route; + } else { + url = new URL( + route.startsWith('/') ? route.slice(1) : route, + baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/` + ); + } - const { method = 'GET', params = {}, data } = parameters; + const { method = 'GET', params = {}, data } = options; Object.entries(params).forEach(([key, value]) => { url.searchParams.append(key, value); }); - const headers = { - Authorization: `Token ${auth}`, - 'Content-Type': 'application/json', - 'User-Agent': userAgent, - }; + const headers = new Headers(); + headers.append('Authorization', `Token ${auth}`); + headers.append('Content-Type', 'application/json'); + headers.append('User-Agent', userAgent); + if (options.headers) { + options.headers.forEach((value, key) => { + headers.append(key, value); + }); + } - const options = { + const init = { method, headers, body: data ? JSON.stringify(data) : undefined, }; - const response = await this.fetch(url, options); + const response = await this.fetch(url, init); if (!response.ok) { - const request = new Request(url, options); + const request = new Request(url, init); const responseText = await response.text(); throw new ApiError( `Request to ${url} failed with status ${response.status} ${response.statusText}: ${responseText}.`, @@ -170,7 +184,7 @@ class Replicate { ); } - return response.json(); + return response; } /** @@ -188,7 +202,7 @@ class Replicate { const response = await endpoint(); yield response.results; if (response.next) { - const nextPage = () => this.request(response.next, { method: 'GET' }); + const nextPage = () => this.request(response.next, { method: 'GET' }).then((r) => r.json()); yield* this.paginate(nextPage); } } @@ -204,7 +218,7 @@ class Replicate { * @param {object} prediction - Prediction object * @param {object} options - Options * @param {number} [options.interval] - Polling interval in milliseconds. Defaults to 250 - * @param {number} [options.maxAttempts] - Maximum number of polling attempts. Defaults to no limit + * @param {number} [options.max_attempts] - Maximum number of polling attempts. Defaults to no limit * @throws {Error} If the prediction doesn't complete within the maximum number of attempts * @throws {Error} If the prediction failed * @returns {Promise} Resolves with the completed prediction object @@ -230,7 +244,7 @@ class Replicate { let attempts = 0; const interval = options.interval || 250; - const maxAttempts = options.maxAttempts || null; + const max_attempts = options.max_attempts || null; while ( updatedPrediction.status !== 'succeeded' && @@ -238,9 +252,9 @@ class Replicate { updatedPrediction.status !== 'canceled' ) { attempts += 1; - if (maxAttempts && attempts > maxAttempts) { + if (max_attempts && attempts > max_attempts) { throw new Error( - `Prediction ${id} did not finish after ${maxAttempts} attempts` + `Prediction ${id} did not finish after ${max_attempts} attempts` ); } diff --git a/index.test.ts b/index.test.ts index 0538afd8..b7261378 100644 --- a/index.test.ts +++ b/index.test.ts @@ -152,6 +152,24 @@ describe('Replicate client', () => { expect(prediction.id).toBe('ufawqhfynnddngldkgtslldrkq'); }); + test('Passes stream parameter to API endpoint', async () => { + nock(BASE_URL) + .post('/predictions') + .reply(201, (_uri, body) => { + expect(body[ 'stream' ]).toBe(true); + return body + }) + + await client.predictions.create({ + version: + '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + input: { + prompt: 'Tell me a story', + }, + stream: true + }); + }); + test('Throws an error if webhook URL is invalid', async () => { await expect(async () => { await client.predictions.create({ @@ -506,7 +524,7 @@ describe('Replicate client', () => { status: 'processing', }) .get('/predictions/ufawqhfynnddngldkgtslldrkq') - .reply(200, { + .reply(201, { id: 'ufawqhfynnddngldkgtslldrkq', status: 'succeeded', output: 'foobar', diff --git a/lib/collections.js b/lib/collections.js index de6f814c..b40d0913 100644 --- a/lib/collections.js +++ b/lib/collections.js @@ -5,9 +5,11 @@ * @returns {Promise} - Resolves with the collection data */ async function getCollection(collection_slug) { - return this.request(`/collections/${collection_slug}`, { + const response = await this.request(`/collections/${collection_slug}`, { method: 'GET', }); + + return response.json(); } /** @@ -16,9 +18,11 @@ async function getCollection(collection_slug) { * @returns {Promise} - Resolves with the collections data */ async function listCollections() { - return this.request('/collections', { + const response = await this.request('/collections', { method: 'GET', }); + + return response.json(); } module.exports = { get: getCollection, list: listCollections }; diff --git a/lib/error.js b/lib/error.js index fc3b1840..81dbe473 100644 --- a/lib/error.js +++ b/lib/error.js @@ -1,3 +1,6 @@ +/** + * A representation of an API error. + */ class ApiError extends Error { /** * Creates a representation of an API error. diff --git a/lib/models.js b/lib/models.js index 55c9b43b..373ed230 100644 --- a/lib/models.js +++ b/lib/models.js @@ -6,9 +6,11 @@ * @returns {Promise} Resolves with the model data */ async function getModel(model_owner, model_name) { - return this.request(`/models/${model_owner}/${model_name}`, { + const response = await this.request(`/models/${model_owner}/${model_name}`, { method: 'GET', }); + + return response.json(); } /** @@ -19,9 +21,11 @@ async function getModel(model_owner, model_name) { * @returns {Promise} Resolves with the list of model versions */ async function listModelVersions(model_owner, model_name) { - return this.request(`/models/${model_owner}/${model_name}/versions`, { + const response = await this.request(`/models/${model_owner}/${model_name}/versions`, { method: 'GET', }); + + return response.json(); } /** @@ -33,12 +37,14 @@ async function listModelVersions(model_owner, model_name) { * @returns {Promise} Resolves with the model version data */ async function getModelVersion(model_owner, model_name, version_id) { - return this.request( + const response = await this.request( `/models/${model_owner}/${model_name}/versions/${version_id}`, { method: 'GET', } ); + + return response.json(); } module.exports = { diff --git a/lib/predictions.js b/lib/predictions.js index ba46ef8f..32b18ade 100644 --- a/lib/predictions.js +++ b/lib/predictions.js @@ -4,15 +4,13 @@ * @param {object} options * @param {string} options.version - Required. The model version * @param {object} options.input - Required. An object with the model inputs - * @param {boolean|object} [options.wait] - Whether to wait for the prediction to finish. Defaults to false - * @param {number} [options.wait.interval] - Polling interval in milliseconds. Defaults to 250 - * @param {number} [options.wait.maxAttempts] - Maximum number of polling attempts. Defaults to no limit * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) - * @returns {Promise} Resolves with the created prediction data + * @param {boolean} [options.stream] - Whether to stream the prediction output. Defaults to false + * @returns {Promise} Resolves with the created prediction */ async function createPrediction(options) { - const { wait, ...data } = options; + const { stream, ...data } = options; if (data.webhook) { try { @@ -23,22 +21,12 @@ async function createPrediction(options) { } } - const prediction = this.request('/predictions', { + const response = await this.request('/predictions', { method: 'POST', - data, + data: { ...data, stream }, }); - if (wait) { - const { maxAttempts, interval } = wait; - - // eslint-disable-next-line no-promise-executor-return - const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); - await sleep(interval || 250); - - return this.wait(await prediction, { maxAttempts, interval }); - } - - return prediction; + return response.json(); } /** @@ -48,9 +36,11 @@ async function createPrediction(options) { * @returns {Promise} Resolves with the prediction data */ async function getPrediction(prediction_id) { - return this.request(`/predictions/${prediction_id}`, { + const response = await this.request(`/predictions/${prediction_id}`, { method: 'GET', }); + + return response.json(); } /** @@ -60,9 +50,11 @@ async function getPrediction(prediction_id) { * @returns {Promise} Resolves with the data for the training */ async function cancelPrediction(prediction_id) { - return this.request(`/predictions/${prediction_id}/cancel`, { + const response = await this.request(`/predictions/${prediction_id}/cancel`, { method: 'POST', }); + + return response.json(); } /** @@ -71,9 +63,11 @@ async function cancelPrediction(prediction_id) { * @returns {Promise} - Resolves with a page of predictions */ async function listPredictions() { - return this.request('/predictions', { + const response = await this.request('/predictions', { method: 'GET', }); + + return response.json(); } module.exports = { diff --git a/lib/trainings.js b/lib/trainings.js index 688e9199..edb037c4 100644 --- a/lib/trainings.js +++ b/lib/trainings.js @@ -23,12 +23,12 @@ async function createTraining(model_owner, model_name, version_id, options) { } } - const training = this.request(`/models/${model_owner}/${model_name}/versions/${version_id}/trainings`, { + const response = await this.request(`/models/${model_owner}/${model_name}/versions/${version_id}/trainings`, { method: 'POST', data, }); - return training; + return response.json(); } /** @@ -38,9 +38,11 @@ async function createTraining(model_owner, model_name, version_id, options) { * @returns {Promise} Resolves with the data for the training */ async function getTraining(training_id) { - return this.request(`/trainings/${training_id}`, { + const response = await this.request(`/trainings/${training_id}`, { method: 'GET', }); + + return response.json(); } /** @@ -50,9 +52,11 @@ async function getTraining(training_id) { * @returns {Promise} Resolves with the data for the training */ async function cancelTraining(training_id) { - return this.request(`/trainings/${training_id}/cancel`, { + const response = await this.request(`/trainings/${training_id}/cancel`, { method: 'POST', }); + + return response.json(); } /** @@ -61,9 +65,11 @@ async function cancelTraining(training_id) { * @returns {Promise} - Resolves with a page of trainings */ async function listTrainings() { - return this.request('/trainings', { + const response = await this.request('/trainings', { method: 'GET', }); + + return response.json(); } module.exports = { From 7e7ceee284eb30e686d3b6edb2dc0edb65575e49 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 25 Jul 2023 06:11:19 -0700 Subject: [PATCH 019/207] 0.13.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2d91ecc9..f25a1c7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.12.3", + "version": "0.13.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.12.3", + "version": "0.13.0", "license": "Apache-2.0", "devDependencies": { "@types/jest": "^29.5.0", diff --git a/package.json b/package.json index 146785ec..0b6c6915 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.12.3", + "version": "0.13.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From fbc3bf26b77b7356a56a74725e0bae974fb56c79 Mon Sep 17 00:00:00 2001 From: Mattt Date: Fri, 28 Jul 2023 05:52:58 -0700 Subject: [PATCH 020/207] Add abort signal option to `run` method and stop callback to `wait` method (#106) * Add stop parameter to wait * Add abort signal parameter to run method --- index.d.ts | 4 +++- index.js | 24 +++++++++++++++++++++--- index.test.ts | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/index.d.ts b/index.d.ts index 4f6e49ff..6b56bebc 100644 --- a/index.d.ts +++ b/index.d.ts @@ -88,6 +88,7 @@ declare module 'replicate' { wait?: { interval?: number; max_attempts?: number }; webhook?: string; webhook_events_filter?: WebhookEventType[]; + signal?: AbortSignal; } ): Promise; @@ -105,7 +106,8 @@ declare module 'replicate' { options: { interval?: number; max_attempts?: number; - } + }, + stop?: (Prediction) => Promise ): Promise; collections: { diff --git a/index.js b/index.js index e6d50d35..b1803a11 100644 --- a/index.js +++ b/index.js @@ -85,6 +85,7 @@ class Replicate { * @param {number} [options.wait.max_attempts] - Maximum number of polling attempts. Defaults to no limit * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) + * @param {AbortSignal} [options.signal] - AbortSignal to cancel the prediction * @throws {Error} If the prediction failed * @returns {Promise} - Resolves with the output of running the model */ @@ -116,7 +117,16 @@ class Replicate { version, }); - prediction = await this.wait(prediction, wait || {}); + const { signal } = options; + + prediction = await this.wait(prediction, wait || {}, async ({ id }) => { + if (signal && signal.aborted) { + await this.predictions.cancel(id); + return true; // stop polling + } + + return false; // continue polling + }); if (prediction.status === 'failed') { throw new Error(`Prediction failed: ${prediction.error}`); @@ -150,7 +160,11 @@ class Replicate { ); } - const { method = 'GET', params = {}, data } = options; + const { + method = 'GET', + params = {}, + data, + } = options; Object.entries(params).forEach(([key, value]) => { url.searchParams.append(key, value); @@ -219,11 +233,12 @@ class Replicate { * @param {object} options - Options * @param {number} [options.interval] - Polling interval in milliseconds. Defaults to 250 * @param {number} [options.max_attempts] - Maximum number of polling attempts. Defaults to no limit + * @param {Function} [stop] - Async callback function that is called after each polling attempt. Receives the prediction object as an argument. Return false to cancel polling. * @throws {Error} If the prediction doesn't complete within the maximum number of attempts * @throws {Error} If the prediction failed * @returns {Promise} Resolves with the completed prediction object */ - async wait(prediction, options) { + async wait(prediction, options, stop) { const { id } = prediction; if (!id) { throw new Error('Invalid prediction'); @@ -261,6 +276,9 @@ class Replicate { /* eslint-disable no-await-in-loop */ await sleep(interval); updatedPrediction = await this.predictions.get(prediction.id); + if (stop && await stop(updatedPrediction) === true) { + break; + } /* eslint-enable no-await-in-loop */ } diff --git a/index.test.ts b/index.test.ts index b7261378..9147b816 100644 --- a/index.test.ts +++ b/index.test.ts @@ -581,6 +581,44 @@ describe('Replicate client', () => { }); }).rejects.toThrow('Invalid webhook URL'); }); + + test('Aborts the operation when abort signal is invoked', async () => { + const controller = new AbortController(); + const { signal } = controller; + + const scope = nock(BASE_URL) + .post('/predictions', (body) => { + controller.abort(); + return body; + }) + .reply(201, { + id: 'ufawqhfynnddngldkgtslldrkq', + status: 'processing', + }) + .persist() + .get('/predictions/ufawqhfynnddngldkgtslldrkq') + .reply(200, { + id: 'ufawqhfynnddngldkgtslldrkq', + status: 'processing', + }) + .post('/predictions/ufawqhfynnddngldkgtslldrkq/cancel') + .reply(200, { + id: 'ufawqhfynnddngldkgtslldrkq', + status: 'canceled', + });; + + await client.run( + 'owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + { + input: { text: 'Hello, world!' }, + signal, + } + ) + + expect(signal.aborted).toBe(true); + + scope.done(); + }); }); // Continue with tests for other methods From 68da42d68fbcff327119a71a4075f0e4b6e2a505 Mon Sep 17 00:00:00 2001 From: Mattt Date: Fri, 28 Jul 2023 06:37:16 -0700 Subject: [PATCH 021/207] Add progress callback parameter to `run` method (#107) * Fix bug in implementation of wait, where stop isn't called after initial update * Add progress parameter to run method --- index.d.ts | 3 ++- index.js | 35 ++++++++++++++++++++++++++--------- index.test.ts | 44 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 68 insertions(+), 14 deletions(-) diff --git a/index.d.ts b/index.d.ts index 6b56bebc..0b59dcb4 100644 --- a/index.d.ts +++ b/index.d.ts @@ -89,7 +89,8 @@ declare module 'replicate' { webhook?: string; webhook_events_filter?: WebhookEventType[]; signal?: AbortSignal; - } + }, + progress?: (Prediction) => void ): Promise; request(route: string | URL, options: { diff --git a/index.js b/index.js index b1803a11..b147ea5e 100644 --- a/index.js +++ b/index.js @@ -86,10 +86,11 @@ class Replicate { * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) * @param {AbortSignal} [options.signal] - AbortSignal to cancel the prediction + * @param {Function} [progress] - Callback function that receives the prediction object as it's updated. The function is called when the prediction is created, each time its updated while polling for completion, and when it's completed. * @throws {Error} If the prediction failed * @returns {Promise} - Resolves with the output of running the model */ - async run(identifier, options) { + async run(identifier, options, progress) { const { wait, ...data } = options; // Define a pattern for owner and model names that allows @@ -117,17 +118,32 @@ class Replicate { version, }); + // Call progress callback with the initial prediction object + if (progress) { + progress(prediction); + } + const { signal } = options; - prediction = await this.wait(prediction, wait || {}, async ({ id }) => { + prediction = await this.wait(prediction, wait || {}, async (updatedPrediction) => { + // Call progress callback with the updated prediction object + if (progress) { + progress(updatedPrediction); + } + if (signal && signal.aborted) { - await this.predictions.cancel(id); + await this.predictions.cancel(updatedPrediction.id); return true; // stop polling } return false; // continue polling }); + // Call progress callback with the completed prediction object + if (progress) { + progress(prediction); + } + if (prediction.status === 'failed') { throw new Error(`Prediction failed: ${prediction.error}`); } @@ -252,8 +268,6 @@ class Replicate { return prediction; } - let updatedPrediction = await this.predictions.get(id); - // eslint-disable-next-line no-promise-executor-return const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); @@ -261,11 +275,18 @@ class Replicate { const interval = options.interval || 250; const max_attempts = options.max_attempts || null; + let updatedPrediction = await this.predictions.get(id); + while ( updatedPrediction.status !== 'succeeded' && updatedPrediction.status !== 'failed' && updatedPrediction.status !== 'canceled' ) { + /* eslint-disable no-await-in-loop */ + if (stop && await stop(updatedPrediction) === true) { + break; + } + attempts += 1; if (max_attempts && attempts > max_attempts) { throw new Error( @@ -273,12 +294,8 @@ class Replicate { ); } - /* eslint-disable no-await-in-loop */ await sleep(interval); updatedPrediction = await this.predictions.get(prediction.id); - if (stop && await stop(updatedPrediction) === true) { - break; - } /* eslint-enable no-await-in-loop */ } diff --git a/index.test.ts b/index.test.ts index 9147b816..52e7c252 100644 --- a/index.test.ts +++ b/index.test.ts @@ -517,26 +517,62 @@ describe('Replicate client', () => { describe('run', () => { test('Calls the correct API routes', async () => { + let firstPollingRequest = true; + nock(BASE_URL) .post('/predictions') + .reply(201, { + id: 'ufawqhfynnddngldkgtslldrkq', + status: 'starting', + }) + .get('/predictions/ufawqhfynnddngldkgtslldrkq') + .twice() .reply(200, { id: 'ufawqhfynnddngldkgtslldrkq', status: 'processing', }) .get('/predictions/ufawqhfynnddngldkgtslldrkq') - .reply(201, { + .reply(200, { id: 'ufawqhfynnddngldkgtslldrkq', status: 'succeeded', - output: 'foobar', + output: 'Goodbye!', }); + const progress = jest.fn(); + const output = await client.run( 'owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', { input: { text: 'Hello, world!' }, - } + wait: { interval: 1 } + }, + progress ); - expect(output).toBe('foobar'); + + expect(output).toBe('Goodbye!'); + + expect(progress).toHaveBeenNthCalledWith(1, { + id: 'ufawqhfynnddngldkgtslldrkq', + status: 'starting', + }); + + expect(progress).toHaveBeenNthCalledWith(2, { + id: 'ufawqhfynnddngldkgtslldrkq', + status: 'processing', + }); + + expect(progress).toHaveBeenNthCalledWith(3, { + id: 'ufawqhfynnddngldkgtslldrkq', + status: 'processing', + }); + + expect(progress).toHaveBeenNthCalledWith(4, { + id: 'ufawqhfynnddngldkgtslldrkq', + status: 'succeeded', + output: 'Goodbye!', + }); + + expect(progress).toHaveBeenCalledTimes(4); }); test('Does not throw an error for identifier containing hyphen and full stop', async () => { From b5bf95b0f97f2262a75d6d60ea34651c93d93b80 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 28 Jul 2023 06:37:57 -0700 Subject: [PATCH 022/207] 0.14.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f25a1c7d..90f21588 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.13.0", + "version": "0.14.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.13.0", + "version": "0.14.0", "license": "Apache-2.0", "devDependencies": { "@types/jest": "^29.5.0", diff --git a/package.json b/package.json index 0b6c6915..1d72d264 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.13.0", + "version": "0.14.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 98b5c83fd811b92210ad3bf50da359fbc0d83c9d Mon Sep 17 00:00:00 2001 From: Nathan Stitt Date: Fri, 28 Jul 2023 17:07:16 -0500 Subject: [PATCH 023/207] Remove spurious ) in README.md I copy & pasted and Typescript pointed out some typos --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0e175576..20e11ba0 100644 --- a/README.md +++ b/README.md @@ -297,11 +297,11 @@ if (prediction && prediction.urls && prediction.urls.stream) { console.log("output", e.data); }); - source.addEventListener("error"), (e) => { + source.addEventListener("error", (e) => { console.error("error", JSON.parse(e.data)); }); - source.addEventListener("done"), (e) => { + source.addEventListener("done", (e) => { source.close(); console.log("done", JSON.parse(e.data)); }); From 53948d8daaba998a39474217c5242357563bdde8 Mon Sep 17 00:00:00 2001 From: Mattt Date: Mon, 31 Jul 2023 07:11:06 -0700 Subject: [PATCH 024/207] Add missing label to callback type definitions (#110) --- index.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.d.ts b/index.d.ts index 0b59dcb4..7a7ab378 100644 --- a/index.d.ts +++ b/index.d.ts @@ -90,7 +90,7 @@ declare module 'replicate' { webhook_events_filter?: WebhookEventType[]; signal?: AbortSignal; }, - progress?: (Prediction) => void + progress?: (prediction: Prediction) => void ): Promise; request(route: string | URL, options: { @@ -108,7 +108,7 @@ declare module 'replicate' { interval?: number; max_attempts?: number; }, - stop?: (Prediction) => Promise + stop?: (prediction: Prediction) => Promise ): Promise; collections: { From 096cea9684707254892b2b48befcc8e8e7ced803 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Mon, 31 Jul 2023 07:11:32 -0700 Subject: [PATCH 025/207] 0.14.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 90f21588..773f9b62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.14.0", + "version": "0.14.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.14.0", + "version": "0.14.1", "license": "Apache-2.0", "devDependencies": { "@types/jest": "^29.5.0", diff --git a/package.json b/package.json index 1d72d264..00233601 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.14.0", + "version": "0.14.1", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 64c22822c106d91972e0d76db6d9a49e1f6bd010 Mon Sep 17 00:00:00 2001 From: Mattt Date: Mon, 31 Jul 2023 11:21:47 -0700 Subject: [PATCH 026/207] Perform type check with TypeScript in CI (#113) * Add check script Update tsconfig.json * Run check script in CI * Upgrade jest and @types/jest * Fix type checking issues in tests --- .github/workflows/ci.yml | 23 +- index.test.ts | 16 +- package-lock.json | 693 +++++++++++++++++++-------------------- package.json | 5 +- tsconfig.json | 7 +- 5 files changed, 365 insertions(+), 379 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf16aff1..e41e8aef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [ "main" ] + branches: ["main"] pull_request: - branches: [ "main" ] + branches: ["main"] jobs: test: @@ -16,12 +16,13 @@ jobs: # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - cache: 'npm' - - run: npm ci - - run: npm run test - - run: npm run lint + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" + - run: npm ci + - run: npm run test + - run: npm run check + - run: npm run lint diff --git a/index.test.ts b/index.test.ts index 52e7c252..8a139b88 100644 --- a/index.test.ts +++ b/index.test.ts @@ -1,5 +1,5 @@ import { expect, jest, test } from '@jest/globals'; -import Replicate, { Prediction } from 'replicate'; +import Replicate, { ApiError, Prediction } from 'replicate'; import nock from 'nock'; import fetch from 'cross-fetch'; @@ -37,14 +37,6 @@ describe('Replicate client', () => { test('Throws error if no auth token is provided', () => { const expected = 'Missing required parameter: auth' - expect(() => { - new Replicate({ auth: undefined }); - }).toThrow(expected); - - expect(() => { - new Replicate({ auth: null }); - }).toThrow(expected); - expect(() => { new Replicate({ auth: "" }); }).toThrow(expected); @@ -156,7 +148,7 @@ describe('Replicate client', () => { nock(BASE_URL) .post('/predictions') .reply(201, (_uri, body) => { - expect(body[ 'stream' ]).toBe(true); + expect((body as any).stream).toBe(true); return body }) @@ -200,8 +192,8 @@ describe('Replicate client', () => { }, }); } catch (error) { - expect(error.response.status).toBe(400); - expect(error.message).toContain("Invalid input") + expect((error as ApiError).response.status).toBe(400); + expect((error as ApiError).message).toContain("Invalid input") } }) // Add more tests for error handling, edge cases, etc. diff --git a/package-lock.json b/package-lock.json index 773f9b62..be0ef21b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.14.1", "license": "Apache-2.0", "devDependencies": { - "@types/jest": "^29.5.0", + "@types/jest": "^29.5.3", "@typescript-eslint/eslint-plugin": "^5.56.0", "cross-fetch": "^3.1.5", "eslint": "^8.36.0", @@ -19,7 +19,7 @@ "eslint-plugin-jsdoc": "^46.2.6", "eslint-plugin-n": "^15.6.1", "eslint-plugin-promise": "^6.1.1", - "jest": "^29.5.0", + "jest": "^29.6.2", "nock": "^13.3.0", "ts-jest": "^29.1.0", "typescript": "^5.0.2" @@ -233,9 +233,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", - "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "dev": true, "engines": { "node": ">=6.9.0" @@ -464,12 +464,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -566,12 +566,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", - "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -856,16 +856,16 @@ } }, "node_modules/@jest/console": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", - "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.2.tgz", + "integrity": "sha512-0N0yZof5hi44HAR2pPS+ikJ3nzKNoZdVu8FffRf3wy47I7Dm7etk/3KetMdRUqzVd16V4O2m2ISpNTbnIuqy1w==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", + "jest-message-util": "^29.6.2", + "jest-util": "^29.6.2", "slash": "^3.0.0" }, "engines": { @@ -873,16 +873,16 @@ } }, "node_modules/@jest/core": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", - "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.2.tgz", + "integrity": "sha512-Oj+5B+sDMiMWLhPFF+4/DvHOf+U10rgvCLGPHP8Xlsy/7QxS51aU/eBngudHlJXnaWD5EohAgJ4js+T6pa+zOg==", "dev": true, "dependencies": { - "@jest/console": "^29.5.0", - "@jest/reporters": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/console": "^29.6.2", + "@jest/reporters": "^29.6.2", + "@jest/test-result": "^29.6.2", + "@jest/transform": "^29.6.2", + "@jest/types": "^29.6.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", @@ -890,20 +890,20 @@ "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-changed-files": "^29.5.0", - "jest-config": "^29.5.0", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", + "jest-config": "^29.6.2", + "jest-haste-map": "^29.6.2", + "jest-message-util": "^29.6.2", "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-resolve-dependencies": "^29.5.0", - "jest-runner": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "jest-watcher": "^29.5.0", + "jest-resolve": "^29.6.2", + "jest-resolve-dependencies": "^29.6.2", + "jest-runner": "^29.6.2", + "jest-runtime": "^29.6.2", + "jest-snapshot": "^29.6.2", + "jest-util": "^29.6.2", + "jest-validate": "^29.6.2", + "jest-watcher": "^29.6.2", "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", + "pretty-format": "^29.6.2", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, @@ -920,37 +920,37 @@ } }, "node_modules/@jest/environment": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", - "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.2.tgz", + "integrity": "sha512-AEcW43C7huGd/vogTddNNTDRpO6vQ2zaQNrttvWV18ArBx9Z56h7BIsXkNFJVOO4/kblWEQz30ckw0+L3izc+Q==", "dev": true, "dependencies": { - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/fake-timers": "^29.6.2", + "@jest/types": "^29.6.1", "@types/node": "*", - "jest-mock": "^29.5.0" + "jest-mock": "^29.6.2" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", - "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.2.tgz", + "integrity": "sha512-m6DrEJxVKjkELTVAztTLyS/7C92Y2b0VYqmDROYKLLALHn8T/04yPs70NADUYPrV3ruI+H3J0iUIuhkjp7vkfg==", "dev": true, "dependencies": { - "expect": "^29.5.0", - "jest-snapshot": "^29.5.0" + "expect": "^29.6.2", + "jest-snapshot": "^29.6.2" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", - "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.2.tgz", + "integrity": "sha512-6zIhM8go3RV2IG4aIZaZbxwpOzz3ZiM23oxAlkquOIole+G6TrbeXnykxWYlqF7kz2HlBjdKtca20x9atkEQYg==", "dev": true, "dependencies": { "jest-get-type": "^29.4.3" @@ -960,49 +960,49 @@ } }, "node_modules/@jest/fake-timers": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", - "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.2.tgz", + "integrity": "sha512-euZDmIlWjm1Z0lJ1D0f7a0/y5Kh/koLFMUBE5SUYWrmy8oNhJpbTBDAP6CxKnadcMLDoDf4waRYCe35cH6G6PA==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.1", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.0" + "jest-message-util": "^29.6.2", + "jest-mock": "^29.6.2", + "jest-util": "^29.6.2" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/globals": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", - "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.2.tgz", + "integrity": "sha512-cjuJmNDjs6aMijCmSa1g2TNG4Lby/AeU7/02VtpW+SLcZXzOLK2GpN2nLqcFjmhy3B3AoPeQVx7BnyOf681bAw==", "dev": true, "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/expect": "^29.5.0", - "@jest/types": "^29.5.0", - "jest-mock": "^29.5.0" + "@jest/environment": "^29.6.2", + "@jest/expect": "^29.6.2", + "@jest/types": "^29.6.1", + "jest-mock": "^29.6.2" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/reporters": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", - "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.2.tgz", + "integrity": "sha512-sWtijrvIav8LgfJZlrGCdN0nP2EWbakglJY49J1Y5QihcQLfy7ovyxxjJBRXMNltgt4uPtEcFmIMbVshEDfFWw==", "dev": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@jridgewell/trace-mapping": "^0.3.15", + "@jest/console": "^29.6.2", + "@jest/test-result": "^29.6.2", + "@jest/transform": "^29.6.2", + "@jest/types": "^29.6.1", + "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", @@ -1014,9 +1014,9 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", + "jest-message-util": "^29.6.2", + "jest-util": "^29.6.2", + "jest-worker": "^29.6.2", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", @@ -1035,24 +1035,24 @@ } }, "node_modules/@jest/schemas": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", - "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz", + "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.25.16" + "@sinclair/typebox": "^0.27.8" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/source-map": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", - "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.0.tgz", + "integrity": "sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.15", + "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" }, @@ -1061,13 +1061,13 @@ } }, "node_modules/@jest/test-result": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", - "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.2.tgz", + "integrity": "sha512-3VKFXzcV42EYhMCsJQURptSqnyjqCGbtLuX5Xxb6Pm6gUf1wIRIl+mandIRGJyWKgNKYF9cnstti6Ls5ekduqw==", "dev": true, "dependencies": { - "@jest/console": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/console": "^29.6.2", + "@jest/types": "^29.6.1", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, @@ -1076,14 +1076,14 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", - "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.2.tgz", + "integrity": "sha512-GVYi6PfPwVejO7slw6IDO0qKVum5jtrJ3KoLGbgBWyr2qr4GaxFV6su+ZAjdTX75Sr1DkMFRk09r2ZVa+wtCGw==", "dev": true, "dependencies": { - "@jest/test-result": "^29.5.0", + "@jest/test-result": "^29.6.2", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", + "jest-haste-map": "^29.6.2", "slash": "^3.0.0" }, "engines": { @@ -1091,22 +1091,22 @@ } }, "node_modules/@jest/transform": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", - "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.2.tgz", + "integrity": "sha512-ZqCqEISr58Ce3U+buNFJYUktLJZOggfyvR+bZMaiV1e8B1SIvJbwZMrYz3gx/KAPn9EXmOmN+uB08yLCjWkQQg==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/types": "^29.5.0", - "@jridgewell/trace-mapping": "^0.3.15", + "@jest/types": "^29.6.1", + "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", + "jest-haste-map": "^29.6.2", "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", + "jest-util": "^29.6.2", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", @@ -1117,12 +1117,12 @@ } }, "node_modules/@jest/types": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", - "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", + "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", "dev": true, "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.0", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -1171,9 +1171,9 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "3.1.0", @@ -1216,27 +1216,27 @@ } }, "node_modules/@sinclair/typebox": { - "version": "0.25.24", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, "node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", "dev": true, "dependencies": { "type-detect": "4.0.8" } }, "node_modules/@sinonjs/fake-timers": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", - "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, "dependencies": { - "@sinonjs/commons": "^2.0.0" + "@sinonjs/commons": "^3.0.0" } }, "node_modules/@types/babel__core": { @@ -1314,9 +1314,9 @@ } }, "node_modules/@types/jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.0.tgz", - "integrity": "sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==", + "version": "29.5.3", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.3.tgz", + "integrity": "sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -1341,12 +1341,6 @@ "integrity": "sha512-Ark2WDjjZO7GmvsyFFf81MXuGTA/d6oP38anyxWOL6EREyBKAxKoFHwBhaZxCfLRLpO8JgVXwqOwSwa7jRcjew==", "dev": true }, - "node_modules/@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", - "dev": true - }, "node_modules/@types/semver": { "version": "7.3.13", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", @@ -1769,12 +1763,12 @@ } }, "node_modules/babel-jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", - "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.2.tgz", + "integrity": "sha512-BYCzImLos6J3BH/+HvUCHG1dTf2MzmAB4jaVxHV+29RZLjR29XuYTmsf2sdDwkrb+FczkGo3kOhE7ga6sI0P4A==", "dev": true, "dependencies": { - "@jest/transform": "^29.5.0", + "@jest/transform": "^29.6.2", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.5.0", @@ -2051,9 +2045,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, "node_modules/cliui": { @@ -2081,9 +2075,9 @@ } }, "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true }, "node_modules/color-convert": { @@ -2172,10 +2166,18 @@ } }, "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } }, "node_modules/deep-is": { "version": "0.1.4", @@ -2903,16 +2905,17 @@ } }, "node_modules/expect": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", - "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.2.tgz", + "integrity": "sha512-iAErsLxJ8C+S02QbLAwgSGSezLQK+XXRDt8IuFXFpwCNw2ECmzZSmjKcCaFVp5VRMk+WAvz6h6jokzEzBFZEuA==", "dev": true, "dependencies": { - "@jest/expect-utils": "^29.5.0", + "@jest/expect-utils": "^29.6.2", + "@types/node": "*", "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0" + "jest-matcher-utils": "^29.6.2", + "jest-message-util": "^29.6.2", + "jest-util": "^29.6.2" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -3785,17 +3788,17 @@ } }, "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "dependencies": { "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", + "make-dir": "^4.0.0", "supports-color": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" } }, "node_modules/istanbul-lib-source-maps": { @@ -3813,9 +3816,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", "dev": true, "dependencies": { "html-escaper": "^2.0.0", @@ -3826,15 +3829,15 @@ } }, "node_modules/jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", - "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.2.tgz", + "integrity": "sha512-8eQg2mqFbaP7CwfsTpCxQ+sHzw1WuNWL5UUvjnWP4hx2riGz9fPSzYOaU5q8/GqWn1TfgZIVTqYJygbGbWAANg==", "dev": true, "dependencies": { - "@jest/core": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/core": "^29.6.2", + "@jest/types": "^29.6.1", "import-local": "^3.0.2", - "jest-cli": "^29.5.0" + "jest-cli": "^29.6.2" }, "bin": { "jest": "bin/jest.js" @@ -3865,28 +3868,28 @@ } }, "node_modules/jest-circus": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", - "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.2.tgz", + "integrity": "sha512-G9mN+KOYIUe2sB9kpJkO9Bk18J4dTDArNFPwoZ7WKHKel55eKIS/u2bLthxgojwlf9NLCVQfgzM/WsOVvoC6Fw==", "dev": true, "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/expect": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/environment": "^29.6.2", + "@jest/expect": "^29.6.2", + "@jest/test-result": "^29.6.2", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", - "dedent": "^0.7.0", + "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", - "jest-each": "^29.5.0", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", + "jest-each": "^29.6.2", + "jest-matcher-utils": "^29.6.2", + "jest-message-util": "^29.6.2", + "jest-runtime": "^29.6.2", + "jest-snapshot": "^29.6.2", + "jest-util": "^29.6.2", "p-limit": "^3.1.0", - "pretty-format": "^29.5.0", + "pretty-format": "^29.6.2", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" @@ -3896,21 +3899,21 @@ } }, "node_modules/jest-cli": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", - "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.2.tgz", + "integrity": "sha512-TT6O247v6dCEX2UGHGyflMpxhnrL0DNqP2fRTKYm3nJJpCTfXX3GCMQPGFjXDoj0i5/Blp3jriKXFgdfmbYB6Q==", "dev": true, "dependencies": { - "@jest/core": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/core": "^29.6.2", + "@jest/test-result": "^29.6.2", + "@jest/types": "^29.6.1", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", + "jest-config": "^29.6.2", + "jest-util": "^29.6.2", + "jest-validate": "^29.6.2", "prompts": "^2.0.1", "yargs": "^17.3.1" }, @@ -3930,31 +3933,31 @@ } }, "node_modules/jest-config": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", - "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.2.tgz", + "integrity": "sha512-VxwFOC8gkiJbuodG9CPtMRjBUNZEHxwfQXmIudSTzFWxaci3Qub1ddTRbFNQlD/zUeaifLndh/eDccFX4wCMQw==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.5.0", - "@jest/types": "^29.5.0", - "babel-jest": "^29.5.0", + "@jest/test-sequencer": "^29.6.2", + "@jest/types": "^29.6.1", + "babel-jest": "^29.6.2", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^29.5.0", - "jest-environment-node": "^29.5.0", + "jest-circus": "^29.6.2", + "jest-environment-node": "^29.6.2", "jest-get-type": "^29.4.3", "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-runner": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", + "jest-resolve": "^29.6.2", + "jest-runner": "^29.6.2", + "jest-util": "^29.6.2", + "jest-validate": "^29.6.2", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^29.5.0", + "pretty-format": "^29.6.2", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -3975,15 +3978,15 @@ } }, "node_modules/jest-diff": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", - "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.2.tgz", + "integrity": "sha512-t+ST7CB9GX5F2xKwhwCf0TAR17uNDiaPTZnVymP9lw0lssa9vG+AFyDZoeIHStU3WowFFwT+ky+er0WVl2yGhA==", "dev": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.4.3", "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "pretty-format": "^29.6.2" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -4002,33 +4005,33 @@ } }, "node_modules/jest-each": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", - "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.2.tgz", + "integrity": "sha512-MsrsqA0Ia99cIpABBc3izS1ZYoYfhIy0NNWqPSE0YXbQjwchyt6B1HD2khzyPe1WiJA7hbxXy77ZoUQxn8UlSw==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.1", "chalk": "^4.0.0", "jest-get-type": "^29.4.3", - "jest-util": "^29.5.0", - "pretty-format": "^29.5.0" + "jest-util": "^29.6.2", + "pretty-format": "^29.6.2" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-environment-node": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", - "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.2.tgz", + "integrity": "sha512-YGdFeZ3T9a+/612c5mTQIllvWkddPbYcN2v95ZH24oWMbGA4GGS2XdIF92QMhUhvrjjuQWYgUGW2zawOyH63MQ==", "dev": true, "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/environment": "^29.6.2", + "@jest/fake-timers": "^29.6.2", + "@jest/types": "^29.6.1", "@types/node": "*", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.0" + "jest-mock": "^29.6.2", + "jest-util": "^29.6.2" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -4044,20 +4047,20 @@ } }, "node_modules/jest-haste-map": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", - "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.2.tgz", + "integrity": "sha512-+51XleTDAAysvU8rT6AnS1ZJ+WHVNqhj1k6nTvN2PYP+HjU3kqlaKQ1Lnw3NYW3bm2r8vq82X0Z1nDDHZMzHVA==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.1", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", + "jest-util": "^29.6.2", + "jest-worker": "^29.6.2", "micromatch": "^4.0.4", "walker": "^1.0.8" }, @@ -4069,46 +4072,46 @@ } }, "node_modules/jest-leak-detector": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", - "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.2.tgz", + "integrity": "sha512-aNqYhfp5uYEO3tdWMb2bfWv6f0b4I0LOxVRpnRLAeque2uqOVVMLh6khnTcE2qJ5wAKop0HcreM1btoysD6bPQ==", "dev": true, "dependencies": { "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "pretty-format": "^29.6.2" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", - "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.2.tgz", + "integrity": "sha512-4LiAk3hSSobtomeIAzFTe+N8kL6z0JtF3n6I4fg29iIW7tt99R7ZcIFW34QkX+DuVrf+CUe6wuVOpm7ZKFJzZQ==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^29.5.0", + "jest-diff": "^29.6.2", "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "pretty-format": "^29.6.2" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", - "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.2.tgz", + "integrity": "sha512-vnIGYEjoPSuRqV8W9t+Wow95SDp6KPX2Uf7EoeG9G99J2OVh7OSwpS4B6J0NfpEIpfkBNHlBZpA2rblEuEFhZQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.1", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", + "pretty-format": "^29.6.2", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -4117,14 +4120,14 @@ } }, "node_modules/jest-mock": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", - "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.2.tgz", + "integrity": "sha512-hoSv3lb3byzdKfwqCuT6uTscan471GUECqgNYykg6ob0yiAw3zYc7OrPnI9Qv8Wwoa4lC7AZ9hyS4AiIx5U2zg==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.1", "@types/node": "*", - "jest-util": "^29.5.0" + "jest-util": "^29.6.2" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -4157,17 +4160,17 @@ } }, "node_modules/jest-resolve": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", - "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.2.tgz", + "integrity": "sha512-G/iQUvZWI5e3SMFssc4ug4dH0aZiZpsDq9o1PtXTV1210Ztyb2+w+ZgQkB3iOiC5SmAEzJBOHWz6Hvrd+QnNPw==", "dev": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", + "jest-haste-map": "^29.6.2", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", + "jest-util": "^29.6.2", + "jest-validate": "^29.6.2", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" @@ -4177,43 +4180,43 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", - "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.2.tgz", + "integrity": "sha512-LGqjDWxg2fuQQm7ypDxduLu/m4+4Lb4gczc13v51VMZbVP5tSBILqVx8qfWcsdP8f0G7aIqByIALDB0R93yL+w==", "dev": true, "dependencies": { "jest-regex-util": "^29.4.3", - "jest-snapshot": "^29.5.0" + "jest-snapshot": "^29.6.2" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-runner": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", - "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.2.tgz", + "integrity": "sha512-wXOT/a0EspYgfMiYHxwGLPCZfC0c38MivAlb2lMEAlwHINKemrttu1uSbcGbfDV31sFaPWnWJPmb2qXM8pqZ4w==", "dev": true, "dependencies": { - "@jest/console": "^29.5.0", - "@jest/environment": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/console": "^29.6.2", + "@jest/environment": "^29.6.2", + "@jest/test-result": "^29.6.2", + "@jest/transform": "^29.6.2", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", "jest-docblock": "^29.4.3", - "jest-environment-node": "^29.5.0", - "jest-haste-map": "^29.5.0", - "jest-leak-detector": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-resolve": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-util": "^29.5.0", - "jest-watcher": "^29.5.0", - "jest-worker": "^29.5.0", + "jest-environment-node": "^29.6.2", + "jest-haste-map": "^29.6.2", + "jest-leak-detector": "^29.6.2", + "jest-message-util": "^29.6.2", + "jest-resolve": "^29.6.2", + "jest-runtime": "^29.6.2", + "jest-util": "^29.6.2", + "jest-watcher": "^29.6.2", + "jest-worker": "^29.6.2", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -4222,31 +4225,31 @@ } }, "node_modules/jest-runtime": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", - "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/globals": "^29.5.0", - "@jest/source-map": "^29.4.3", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.2.tgz", + "integrity": "sha512-2X9dqK768KufGJyIeLmIzToDmsN0m7Iek8QNxRSI/2+iPFYHF0jTwlO3ftn7gdKd98G/VQw9XJCk77rbTGZnJg==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.6.2", + "@jest/fake-timers": "^29.6.2", + "@jest/globals": "^29.6.2", + "@jest/source-map": "^29.6.0", + "@jest/test-result": "^29.6.2", + "@jest/transform": "^29.6.2", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", + "jest-haste-map": "^29.6.2", + "jest-message-util": "^29.6.2", + "jest-mock": "^29.6.2", "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", + "jest-resolve": "^29.6.2", + "jest-snapshot": "^29.6.2", + "jest-util": "^29.6.2", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -4255,46 +4258,43 @@ } }, "node_modules/jest-snapshot": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", - "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.2.tgz", + "integrity": "sha512-1OdjqvqmRdGNvWXr/YZHuyhh5DeaLp1p/F8Tht/MrMw4Kr1Uu/j4lRG+iKl1DAqUJDWxtQBMk41Lnf/JETYBRA==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", + "@jest/expect-utils": "^29.6.2", + "@jest/transform": "^29.6.2", + "@jest/types": "^29.6.1", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^29.5.0", + "expect": "^29.6.2", "graceful-fs": "^4.2.9", - "jest-diff": "^29.5.0", + "jest-diff": "^29.6.2", "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", + "jest-matcher-utils": "^29.6.2", + "jest-message-util": "^29.6.2", + "jest-util": "^29.6.2", "natural-compare": "^1.4.0", - "pretty-format": "^29.5.0", - "semver": "^7.3.5" + "pretty-format": "^29.6.2", + "semver": "^7.5.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-util": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", - "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.2.tgz", + "integrity": "sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -4306,17 +4306,17 @@ } }, "node_modules/jest-validate": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", - "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.2.tgz", + "integrity": "sha512-vGz0yMN5fUFRRbpJDPwxMpgSXW1LDKROHfBopAvDcmD6s+B/s8WJrwi+4bfH4SdInBA5C3P3BI19dBtKzx1Arg==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.1", "camelcase": "^6.2.0", "chalk": "^4.0.0", "jest-get-type": "^29.4.3", "leven": "^3.1.0", - "pretty-format": "^29.5.0" + "pretty-format": "^29.6.2" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -4335,18 +4335,18 @@ } }, "node_modules/jest-watcher": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", - "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.2.tgz", + "integrity": "sha512-GZitlqkMkhkefjfN/p3SJjrDaxPflqxEAv3/ik10OirZqJGYH5rPiIsgVcfof0Tdqg3shQGdEIxDBx+B4tuLzA==", "dev": true, "dependencies": { - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/test-result": "^29.6.2", + "@jest/types": "^29.6.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", - "jest-util": "^29.5.0", + "jest-util": "^29.6.2", "string-length": "^4.0.1" }, "engines": { @@ -4354,13 +4354,13 @@ } }, "node_modules/jest-worker": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", - "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.2.tgz", + "integrity": "sha512-l3ccBOabTdkng8I/ORCkADz4eSMKejTYv1vB/Z83UiubqhC1oQ5Li6dWCyqOIvSifGjUBxuvxvlm6KGK2DtuAQ==", "dev": true, "dependencies": { "@types/node": "*", - "jest-util": "^29.5.0", + "jest-util": "^29.6.2", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -4548,29 +4548,20 @@ } }, "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "dependencies": { - "semver": "^6.0.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -5050,12 +5041,12 @@ } }, "node_modules/pretty-format": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", - "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.2.tgz", + "integrity": "sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==", "dev": true, "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.0", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -5107,9 +5098,9 @@ } }, "node_modules/pure-rand": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.1.tgz", - "integrity": "sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", + "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", "dev": true, "funding": [ { @@ -5305,9 +5296,9 @@ } }, "node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -6016,9 +6007,9 @@ "dev": true }, "node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "dependencies": { "cliui": "^8.0.1", diff --git a/package.json b/package.json index 00233601..afaa7a2b 100644 --- a/package.json +++ b/package.json @@ -14,11 +14,12 @@ "yarn": ">=1.7.0" }, "scripts": { + "check": "tsc", "lint": "eslint .", "test": "jest" }, "devDependencies": { - "@types/jest": "^29.5.0", + "@types/jest": "^29.5.3", "@typescript-eslint/eslint-plugin": "^5.56.0", "cross-fetch": "^3.1.5", "eslint": "^8.36.0", @@ -28,7 +29,7 @@ "eslint-plugin-jsdoc": "^46.2.6", "eslint-plugin-n": "^15.6.1", "eslint-plugin-promise": "^6.1.1", - "jest": "^29.5.0", + "jest": "^29.6.2", "nock": "^13.3.0", "ts-jest": "^29.1.0", "typescript": "^5.0.2" diff --git a/tsconfig.json b/tsconfig.json index 073eb382..7a564ee1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,10 @@ { "compilerOptions": { - "esModuleInterop": true + "esModuleInterop": true, + "noEmit": true, + "strict": true }, "exclude": [ - "node_modules", - "**/node_modules/*" + "**/node_modules" ] } From edeed24849dd5aafd68b2d95e674ef6a83a6f7c7 Mon Sep 17 00:00:00 2001 From: Mattt Date: Fri, 4 Aug 2023 05:58:55 -0700 Subject: [PATCH 027/207] Change polling behavior (#114) * Remove max_attempts parameter from wait method * Set default polling interval to 500ms --- index.d.ts | 3 +-- index.js | 17 +++-------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/index.d.ts b/index.d.ts index 7a7ab378..470680de 100644 --- a/index.d.ts +++ b/index.d.ts @@ -85,7 +85,7 @@ declare module 'replicate' { identifier: `${string}/${string}:${string}`, options: { input: object; - wait?: { interval?: number; max_attempts?: number }; + wait?: { interval?: number }; webhook?: string; webhook_events_filter?: WebhookEventType[]; signal?: AbortSignal; @@ -106,7 +106,6 @@ declare module 'replicate' { prediction: Prediction, options: { interval?: number; - max_attempts?: number; }, stop?: (prediction: Prediction) => Promise ): Promise; diff --git a/index.js b/index.js index b147ea5e..84806a04 100644 --- a/index.js +++ b/index.js @@ -81,8 +81,7 @@ class Replicate { * @param {object} options * @param {object} options.input - Required. An object with the model inputs * @param {object} [options.wait] - Options for waiting for the prediction to finish - * @param {number} [options.wait.interval] - Polling interval in milliseconds. Defaults to 250 - * @param {number} [options.wait.max_attempts] - Maximum number of polling attempts. Defaults to no limit + * @param {number} [options.wait.interval] - Polling interval in milliseconds. Defaults to 500 * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) * @param {AbortSignal} [options.signal] - AbortSignal to cancel the prediction @@ -247,8 +246,7 @@ class Replicate { * @async * @param {object} prediction - Prediction object * @param {object} options - Options - * @param {number} [options.interval] - Polling interval in milliseconds. Defaults to 250 - * @param {number} [options.max_attempts] - Maximum number of polling attempts. Defaults to no limit + * @param {number} [options.interval] - Polling interval in milliseconds. Defaults to 500 * @param {Function} [stop] - Async callback function that is called after each polling attempt. Receives the prediction object as an argument. Return false to cancel polling. * @throws {Error} If the prediction doesn't complete within the maximum number of attempts * @throws {Error} If the prediction failed @@ -271,9 +269,7 @@ class Replicate { // eslint-disable-next-line no-promise-executor-return const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); - let attempts = 0; - const interval = options.interval || 250; - const max_attempts = options.max_attempts || null; + const interval = options.interval || 500; let updatedPrediction = await this.predictions.get(id); @@ -287,13 +283,6 @@ class Replicate { break; } - attempts += 1; - if (max_attempts && attempts > max_attempts) { - throw new Error( - `Prediction ${id} did not finish after ${max_attempts} attempts` - ); - } - await sleep(interval); updatedPrediction = await this.predictions.get(prediction.id); /* eslint-enable no-await-in-loop */ From 994509556cab94870de7383a3d9dd45e2d49388e Mon Sep 17 00:00:00 2001 From: Mattt Date: Fri, 4 Aug 2023 14:45:44 -0700 Subject: [PATCH 028/207] Specify minimum engine requirement of Node 18 (#117) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index afaa7a2b..b9daf8e5 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "license": "Apache-2.0", "main": "index.js", "engines": { - "node": ">=16.6.0", + "node": ">=18.0.0", "npm": ">=7.19.0", "git": ">=2.11.0", "yarn": ">=1.7.0" From 533b26de40e471d8c1751a7af13eefa1fd9e1077 Mon Sep 17 00:00:00 2001 From: Mattt Date: Mon, 7 Aug 2023 06:08:44 -0700 Subject: [PATCH 029/207] Add automatic retry policy (#115) --- index.js | 6 ++++- index.test.ts | 74 +++++++++++++++++++++++++++++++++++++++++++++++++-- lib/util.js | 69 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 lib/util.js diff --git a/index.js b/index.js index 84806a04..6a23effa 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,5 @@ const ApiError = require('./lib/error'); +const { withAutomaticRetries } = require('./lib/util'); const collections = require('./lib/collections'); const models = require('./lib/models'); @@ -201,7 +202,10 @@ class Replicate { body: data ? JSON.stringify(data) : undefined, }; - const response = await this.fetch(url, init); + const shouldRetry = method === 'GET' ? + (response) => (response.status === 429 || response.status >= 500) : + (response) => (response.status === 429); + const response = await withAutomaticRetries(async () => this.fetch(url, init), { shouldRetry }); if (!response.ok) { const request = new Request(url, init); diff --git a/index.test.ts b/index.test.ts index 8a139b88..818f8744 100644 --- a/index.test.ts +++ b/index.test.ts @@ -196,7 +196,44 @@ describe('Replicate client', () => { expect((error as ApiError).message).toContain("Invalid input") } }) - // Add more tests for error handling, edge cases, etc. + + test('Automatically retries on 429', async () => { + nock(BASE_URL) + .post('/predictions') + .reply(429, { + detail: "Too many requests", + }, { "Content-Type": "application/json", "Retry-After": "1" }) + .post('/predictions') + .reply(201, { + id: 'ufawqhfynnddngldkgtslldrkq', + }); + const prediction = await client.predictions.create({ + version: + '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + input: { + text: 'Alice', + }, + }); + expect(prediction.id).toBe('ufawqhfynnddngldkgtslldrkq'); + }); + + test('Does not automatically retry on 500', async () => { + nock(BASE_URL) + .post('/predictions') + .reply(500, { + detail: "Internal server error", + }, { "Content-Type": "application/json" }); + + await expect( + client.predictions.create({ + version: + '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + input: { + text: 'Alice', + }, + }) + ).rejects.toThrow(`Request to https://api.replicate.com/v1/predictions failed with status 500 Internal Server Error: {"detail":"Internal server error"}.`) + }); }); describe('predictions.get', () => { @@ -234,7 +271,40 @@ describe('Replicate client', () => { ); expect(prediction.id).toBe('rrr4z55ocneqzikepnug6xezpe'); }); - // Add more tests for error handling, edge cases, etc. + + test('Automatically retries on 429', async () => { + nock(BASE_URL) + .get('/predictions/rrr4z55ocneqzikepnug6xezpe') + .reply(429, { + detail: "Too many requests", + }, { "Content-Type": "application/json", "Retry-After": "1" }) + .get('/predictions/rrr4z55ocneqzikepnug6xezpe') + .reply(200, { + id: 'rrr4z55ocneqzikepnug6xezpe', + }); + + const prediction = await client.predictions.get( + 'rrr4z55ocneqzikepnug6xezpe' + ); + expect(prediction.id).toBe('rrr4z55ocneqzikepnug6xezpe'); + }); + + test('Automatically retries on 500', async () => { + nock(BASE_URL) + .get('/predictions/rrr4z55ocneqzikepnug6xezpe') + .reply(500, { + detail: "Internal server error", + }, { "Content-Type": "application/json" }) + .get('/predictions/rrr4z55ocneqzikepnug6xezpe') + .reply(200, { + id: 'rrr4z55ocneqzikepnug6xezpe', + }); + + const prediction = await client.predictions.get( + 'rrr4z55ocneqzikepnug6xezpe' + ); + expect(prediction.id).toBe('rrr4z55ocneqzikepnug6xezpe'); + }); }); describe('predictions.cancel', () => { diff --git a/lib/util.js b/lib/util.js new file mode 100644 index 00000000..1b9cf9d2 --- /dev/null +++ b/lib/util.js @@ -0,0 +1,69 @@ +const ApiError = require('./error'); + +/** + * Automatically retry a request if it fails with an appropriate status code. + * + * A GET request is retried if it fails with a 429 or 5xx status code. + * A non-GET request is retried only if it fails with a 429 status code. + * + * If the response sets a Retry-After header, + * the request is retried after the number of seconds specified in the header. + * Otherwise, the request is retried after the specified interval, + * with exponential backoff and jitter. + * + * @param {Function} request - A function that returns a Promise that resolves with a Response object + * @param {object} options + * @param {Function} [options.shouldRetry] - A function that returns true if the request should be retried + * @param {number} [options.maxRetries] - Maximum number of retries. Defaults to 5 + * @param {number} [options.interval] - Interval between retries in milliseconds. Defaults to 500 + * @returns {Promise} - Resolves with the response object + * @throws {ApiError} If the request failed + */ +async function withAutomaticRetries(request, options = {}) { + const shouldRetry = options.shouldRetry || (() => (false)); + const maxRetries = options.maxRetries || 5; + const interval = options.interval || 500; + const jitter = options.jitter || 100; + + // eslint-disable-next-line no-promise-executor-return + const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + + let attempts = 0; + do { + let delay = (interval * (2 ** attempts)) + (Math.random() * jitter); + + /* eslint-disable no-await-in-loop */ + try { + const response = await request(); + if (response.ok || !shouldRetry(response)) { + return response; + } + } catch (error) { + if (error instanceof ApiError) { + const retryAfter = error.response.headers.get('Retry-After'); + if (retryAfter) { + if (!Number.isInteger(retryAfter)) { // Retry-After is a date + const date = new Date(retryAfter); + if (!Number.isNaN(date.getTime())) { + delay = date.getTime() - new Date().getTime(); + } + } else { // Retry-After is a number of seconds + delay = retryAfter * 1000; + } + } + } + } + + if (Number.isInteger(maxRetries) && maxRetries > 0) { + if (Number.isInteger(delay) && delay > 0) { + await sleep(interval * 2 ** (options.maxRetries - maxRetries)); + } + attempts += 1; + } + /* eslint-enable no-await-in-loop */ + } while (attempts < maxRetries); + + return request(); +} + +module.exports = { withAutomaticRetries }; From df0ac01c7e099e817163871ab0bbd72fff0132ba Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Mon, 7 Aug 2023 06:09:18 -0700 Subject: [PATCH 030/207] 0.15.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index be0ef21b..0fd5c22d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.14.1", + "version": "0.15.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.14.1", + "version": "0.15.0", "license": "Apache-2.0", "devDependencies": { "@types/jest": "^29.5.3", diff --git a/package.json b/package.json index b9daf8e5..fb757397 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.14.1", + "version": "0.15.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 49f248696ee285e4b0f2769b1847e4e171de9a51 Mon Sep 17 00:00:00 2001 From: Mattt Date: Tue, 8 Aug 2023 09:00:00 -0700 Subject: [PATCH 031/207] Don't throw error if auth isn't provided in client constructor (#120) --- index.js | 10 ++++------ index.test.ts | 11 ++++++++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 6a23effa..d8ee42e5 100644 --- a/index.js +++ b/index.js @@ -36,11 +36,7 @@ class Replicate { * @param {string} [options.baseUrl] - Defaults to https://api.replicate.com/v1 * @param {Function} [options.fetch] - Fetch function to use. Defaults to `globalThis.fetch` */ - constructor(options) { - if (!options.auth) { - throw new Error('Missing required parameter: auth'); - } - + constructor(options = {}) { this.auth = options.auth; this.userAgent = options.userAgent || `replicate-javascript/${packageJSON.version}`; @@ -187,7 +183,9 @@ class Replicate { }); const headers = new Headers(); - headers.append('Authorization', `Token ${auth}`); + if (auth) { + headers.append('Authorization', `Token ${auth}`); + } headers.append('Content-Type', 'application/json'); headers.append('User-Agent', userAgent); if (options.headers) { diff --git a/index.test.ts b/index.test.ts index 818f8744..442e36ee 100644 --- a/index.test.ts +++ b/index.test.ts @@ -34,12 +34,17 @@ describe('Replicate client', () => { expect(clientWithCustomUserAgent.userAgent).toBe('my-app/1.2.3'); }); - test('Throws error if no auth token is provided', () => { - const expected = 'Missing required parameter: auth' + test('Does not throw error if auth token is not provided', () => { + expect(() => { + // @ts-expect-error + new Replicate(); + }).not.toThrow(); + }); + test('Does not throw error if blank auth token is provided', () => { expect(() => { new Replicate({ auth: "" }); - }).toThrow(expected); + }).not.toThrow(); }); }); From f59a8f6e9f96b25dc5ba798dc48007cc1317cbd2 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 8 Aug 2023 09:04:28 -0700 Subject: [PATCH 032/207] 0.15.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0fd5c22d..a1ba6ddd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.15.0", + "version": "0.15.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.15.0", + "version": "0.15.1", "license": "Apache-2.0", "devDependencies": { "@types/jest": "^29.5.3", diff --git a/package.json b/package.json index fb757397..66514430 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.15.0", + "version": "0.15.1", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 07dbb5bc3d7215a6ee194904ba71d196b9cf6f66 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Tue, 8 Aug 2023 09:06:11 -0700 Subject: [PATCH 033/207] 0.16.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a1ba6ddd..1a2395c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.15.1", + "version": "0.16.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.15.1", + "version": "0.16.0", "license": "Apache-2.0", "devDependencies": { "@types/jest": "^29.5.3", diff --git a/package.json b/package.json index 66514430..b19b7783 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.15.1", + "version": "0.16.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 6c6fb5d23abb2d0e8dcf45ed4ef92aaf30dcdd98 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Tue, 15 Aug 2023 10:46:53 -0700 Subject: [PATCH 034/207] options are optional in replicate.wait() (#122) * options are optional in replicate.wait() * Use && instead of ?. --------- Co-authored-by: Mattt --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index d8ee42e5..293f447c 100644 --- a/index.js +++ b/index.js @@ -271,7 +271,7 @@ class Replicate { // eslint-disable-next-line no-promise-executor-return const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); - const interval = options.interval || 500; + const interval = (options && options.interval) || 500; let updatedPrediction = await this.predictions.get(id); From 5a4683ac51862c0d6245332a7373a404e420e78a Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 15 Aug 2023 10:47:08 -0700 Subject: [PATCH 035/207] 0.16.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1a2395c1..85a3acf5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.16.0", + "version": "0.16.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.16.0", + "version": "0.16.1", "license": "Apache-2.0", "devDependencies": { "@types/jest": "^29.5.3", diff --git a/package.json b/package.json index b19b7783..dc28462f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.16.0", + "version": "0.16.1", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 580cf7122311d4c452de2cb8282109f62ea18c13 Mon Sep 17 00:00:00 2001 From: Mattt Date: Tue, 5 Sep 2023 05:30:00 -0700 Subject: [PATCH 036/207] Add started_at attribute to Prediction type definition (#129) --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 470680de..5f94ce9c 100644 --- a/index.d.ts +++ b/index.d.ts @@ -51,7 +51,7 @@ declare module 'replicate' { webhook?: string; webhook_events_filter?: WebhookEventType[]; created_at: string; - updated_at: string; + started_at?: string; completed_at?: string; urls: { get: string; From c1e838a3bfeccec58c072131a07d693d98e078b7 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 5 Sep 2023 05:30:50 -0700 Subject: [PATCH 037/207] 0.17.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 85a3acf5..49816ead 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.16.1", + "version": "0.17.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.16.1", + "version": "0.17.0", "license": "Apache-2.0", "devDependencies": { "@types/jest": "^29.5.3", diff --git a/package.json b/package.json index dc28462f..7c8f4a31 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.16.1", + "version": "0.17.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 97d7aaae9f387e92501d9bf1041300f341fd1be4 Mon Sep 17 00:00:00 2001 From: Mattt Date: Mon, 11 Sep 2023 11:01:38 -0700 Subject: [PATCH 038/207] Add deployment endpoints (#131) Document replicate.deployments.predictions.create in README --- README.md | 17 +++++++++++++++++ index.d.ts | 17 ++++++++++++++++- index.js | 7 +++++++ index.test.ts | 37 +++++++++++++++++++++++++++++++++++++ lib/deployments.js | 37 +++++++++++++++++++++++++++++++++++++ 5 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 lib/deployments.js diff --git a/README.md b/README.md index 20e11ba0..13968cae 100644 --- a/README.md +++ b/README.md @@ -552,6 +552,23 @@ const response = await replicate.trainings.list(); } ``` +### `replicate.deployments.predictions.create` + +```js +const response = await replicate.deployments.predictions.create(deployment_owner, deployment_name, options); +``` + +| name | type | description | +| ------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `deployment_owner` | string | **Required**. The name of the user or organization that owns the deployment | +| `deployment_name` | string | **Required**. The name of the deployment | +| `options.input` | object | **Required**. An object with the model's inputs | +| `options.webhook` | string | An HTTPS URL for receiving a webhook when the prediction has new output | +| `options.webhook_events_filter` | string[] | You can change which events trigger webhook requests by specifying webhook events (`start` \| `output` \| `logs` \| `completed`) | + +Use `replicate.wait` to wait for a prediction to finish, +or `replicate.predictions.cancel` to cancel a prediction before it finishes. + ### `replicate.paginate` Pass another method as an argument to iterate over results diff --git a/index.d.ts b/index.d.ts index 5f94ce9c..7f4461cf 100644 --- a/index.d.ts +++ b/index.d.ts @@ -47,7 +47,7 @@ declare module 'replicate' { logs?: string; metrics?: { predict_time?: number; - } + }; webhook?: string; webhook_events_filter?: WebhookEventType[]; created_at: string; @@ -156,5 +156,20 @@ declare module 'replicate' { cancel(training_id: string): Promise; list(): Promise>; }; + + deployments: { + predictions: { + create( + deployment_name: string, + deployment_owner: string, + options: { + input: object; + stream?: boolean; + webhook?: string; + webhook_events_filter?: WebhookEventType[]; + } + ): Promise; + }; + }; } } diff --git a/index.js b/index.js index 293f447c..9662235f 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ const ApiError = require('./lib/error'); const { withAutomaticRetries } = require('./lib/util'); const collections = require('./lib/collections'); +const deployments = require('./lib/deployments'); const models = require('./lib/models'); const predictions = require('./lib/predictions'); const trainings = require('./lib/trainings'); @@ -69,6 +70,12 @@ class Replicate { cancel: trainings.cancel.bind(this), list: trainings.list.bind(this), }; + + this.deployments = { + predictions: { + create: deployments.predictions.create.bind(this), + } + }; } /** diff --git a/index.test.ts b/index.test.ts index 442e36ee..fb65f29d 100644 --- a/index.test.ts +++ b/index.test.ts @@ -582,6 +582,43 @@ describe('Replicate client', () => { }); }); + describe('deployments.predictions.create', () => { + test('Calls the correct API route with the correct payload', async () => { + nock(BASE_URL) + .post('/deployments/replicate/greeter/predictions') + .reply(200, { + id: 'mfrgcyzzme2wkmbwgzrgmntcg', + version: + '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + urls: { + get: 'https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq', + cancel: + 'https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel', + }, + created_at: '2022-09-10T09:44:22.165836Z', + started_at: null, + completed_at: null, + status: 'starting', + input: { + text: 'Alice', + }, + output: null, + error: null, + logs: null, + metrics: {}, + }); + const prediction = await client.deployments.predictions.create("replicate", "greeter", { + input: { + text: 'Alice', + }, + webhook: 'http://test.host/webhook', + webhook_events_filter: [ 'output', 'completed' ], + }); + expect(prediction.id).toBe('mfrgcyzzme2wkmbwgzrgmntcg'); + }); + // Add more tests for error handling, edge cases, etc. + }); + describe('run', () => { test('Calls the correct API routes', async () => { let firstPollingRequest = true; diff --git a/lib/deployments.js b/lib/deployments.js new file mode 100644 index 00000000..4682c9bd --- /dev/null +++ b/lib/deployments.js @@ -0,0 +1,37 @@ +/** + * Create a new prediction with a deployment + * + * @param {string} deployment_owner - Required. The username of the user or organization who owns the deployment + * @param {string} deployment_name - Required. The name of the deployment + * @param {object} options + * @param {object} options.input - Required. An object with the model inputs + * @param {boolean} [options.stream] - Whether to stream the prediction output. Defaults to false + * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output + * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) + * @returns {Promise} Resolves with the created prediction data + */ +async function createPrediction(deployment_owner, deployment_name, options) { + const { stream, ...data } = options; + + if (data.webhook) { + try { + // eslint-disable-next-line no-new + new URL(data.webhook); + } catch (err) { + throw new Error('Invalid webhook URL'); + } + } + + const response = await this.request(`/deployments/${deployment_owner}/${deployment_name}/predictions`, { + method: 'POST', + data: { ...data, stream }, + }); + + return response.json(); +} + +module.exports = { + predictions: { + create: createPrediction, + } +}; From 8a352c39b9aef79772076170ca713fef379af10d Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Mon, 11 Sep 2023 11:02:39 -0700 Subject: [PATCH 039/207] 0.18.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 49816ead..1e52f8b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.17.0", + "version": "0.18.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.17.0", + "version": "0.18.0", "license": "Apache-2.0", "devDependencies": { "@types/jest": "^29.5.3", diff --git a/package.json b/package.json index 7c8f4a31..1dd4c07b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.17.0", + "version": "0.18.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 46fbca2c22c0068d16775a57fa10347cf01277b9 Mon Sep 17 00:00:00 2001 From: Mattt Date: Thu, 21 Sep 2023 07:13:50 -0700 Subject: [PATCH 040/207] Update tests to ensure all requests are mocked (#137) * Change expectation to hasAssertions for exception handling * Disable network access and warn on unmatched mocks --- index.test.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/index.test.ts b/index.test.ts index fb65f29d..a560da18 100644 --- a/index.test.ts +++ b/index.test.ts @@ -3,14 +3,30 @@ import Replicate, { ApiError, Prediction } from 'replicate'; import nock from 'nock'; import fetch from 'cross-fetch'; -describe('Replicate client', () => { - let client: Replicate; +let client: Replicate; +const BASE_URL = 'https://api.replicate.com/v1'; + +nock.disableNetConnect(); - const BASE_URL = 'https://api.replicate.com/v1'; +describe('Replicate client', () => { + let unmatched: Object[] = []; + const handleNoMatch = (req: unknown, options: any, body: string) => + unmatched.push({ req, options, body }); beforeEach(() => { client = new Replicate({ auth: 'test-token' }); client.fetch = fetch; + + unmatched = []; + nock.emitter.on("no match", handleNoMatch); + }); + + afterEach(() => { + nock.emitter.off("no match", handleNoMatch); + expect(unmatched).toStrictEqual([]); + + nock.abortPendingRequests(); + nock.cleanAll(); }); describe('constructor', () => { @@ -188,7 +204,7 @@ describe('Replicate client', () => { }, { "Content-Type": "application/json" }) try { - expect.assertions(2); + expect.hasAssertions(); await client.predictions.create({ version: '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', From fdf5f76741f3dd78288dfb933c86a00d28d7f8ee Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Fri, 22 Sep 2023 11:56:09 -0700 Subject: [PATCH 041/207] document the replicate.run() method (#138) --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index 13968cae..a0beff1a 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,33 @@ client.fetch = (url, options) => { }; ``` +### `replicate.run` + +Run a model and await the result. Unlike [`replicate.prediction.create`](#replicatepredictionscreate), this method returns only the prediction output rather than the entire prediction object. + +```js +const model = "stability-ai/sdxl:8beff3369e81422112d93b89ca01426147de542cd4684c244b673b105188fe5f"; +const input = { + prompt: "a 19th century portrait of a raccoon gentleman wearing a suit", +}; +const output = await replicate.run(identifier, options, progress); +``` + +| name | type | description | +| ------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `identifier` | string | **Required**. The model version identifier in the format `{owner}/{name}:{version}`, for example `stability-ai/sdxl:8beff3369e81422112d93b89ca01426147de542cd4684c244b673b105188fe5f` | +| `options.input` | object | **Required**. An object with the model inputs. | +| `options.wait` | object | Options for waiting for the prediction to finish | +| `options.wait.interval` | number | Polling interval in milliseconds. Defaults to 500 | +| `options.webhook` | string | An HTTPS URL for receiving a webhook when the prediction has new output | +| `options.webhook_events_filter` | string[] | An array of events which should trigger [webhooks](https://replicate.com/docs/webhooks). Allowable values are `start`, `output`, `logs`, and `completed` | +| `options.signal` | object | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the prediction | +| `progress` | function | Callback function that receives the prediction object as it's updated. The function is called when the prediction is created, each time its updated while polling for completion, and when it's completed. | + +Throws `Error` if the prediction failed. + +Returns `Promise` which resolves with the output of running the model. + ### `replicate.models.get` ```js From 11f0e6e11d1c38c0e1b9584f6451199b348158e1 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Fri, 22 Sep 2023 12:03:09 -0700 Subject: [PATCH 042/207] fix run usage docs (#139) --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a0beff1a..28423586 100644 --- a/README.md +++ b/README.md @@ -150,10 +150,6 @@ client.fetch = (url, options) => { Run a model and await the result. Unlike [`replicate.prediction.create`](#replicatepredictionscreate), this method returns only the prediction output rather than the entire prediction object. ```js -const model = "stability-ai/sdxl:8beff3369e81422112d93b89ca01426147de542cd4684c244b673b105188fe5f"; -const input = { - prompt: "a 19th century portrait of a raccoon gentleman wearing a suit", -}; const output = await replicate.run(identifier, options, progress); ``` @@ -172,6 +168,14 @@ Throws `Error` if the prediction failed. Returns `Promise` which resolves with the output of running the model. +Example: + +```js +const model = "stability-ai/sdxl:8beff3369e81422112d93b89ca01426147de542cd4684c244b673b105188fe5f"; +const input = { prompt: "a 19th century portrait of a raccoon gentleman wearing a suit" }; +const output = await replicate.run(model, { input }); +``` + ### `replicate.models.get` ```js From 97c9effb80afb373b7ddc1c705c88b36afff966b Mon Sep 17 00:00:00 2001 From: akiva Date: Sun, 24 Sep 2023 09:03:25 -0700 Subject: [PATCH 043/207] Update index.d.ts (#140) The API call is actually in the opposite order --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 7f4461cf..50ebd1cd 100644 --- a/index.d.ts +++ b/index.d.ts @@ -160,8 +160,8 @@ declare module 'replicate' { deployments: { predictions: { create( - deployment_name: string, deployment_owner: string, + deployment_name: string, options: { input: object; stream?: boolean; From 4b0d9cb0e226fab3d3d31de5b32261485acf5626 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Sun, 24 Sep 2023 09:03:50 -0700 Subject: [PATCH 044/207] 0.18.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1e52f8b9..9983fbc9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.18.0", + "version": "0.18.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.18.0", + "version": "0.18.1", "license": "Apache-2.0", "devDependencies": { "@types/jest": "^29.5.3", diff --git a/package.json b/package.json index 1dd4c07b..1a01fa3a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.18.0", + "version": "0.18.1", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 3c04fbd66f1093b2557a55727e1639342faaca0f Mon Sep 17 00:00:00 2001 From: Mattt Date: Mon, 2 Oct 2023 03:36:02 -0700 Subject: [PATCH 045/207] Support implicit auth using environment variable (#127) * Default client auth to REPLICATE_API_TOKEN environment variable * Update tests * Update README * Apply suggestions from code review --- README.md | 10 +++------- index.d.ts | 4 ++-- index.js | 4 ++-- index.test.ts | 7 +++++-- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 28423586..d6f02572 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ import Replicate from "replicate"; const replicate = new Replicate({ // get your token from https://replicate.com/account - auth: process.env.REPLICATE_API_TOKEN, + auth: "my api token", // defaults to process.env.REPLICATE_API_TOKEN }); ``` @@ -124,18 +124,14 @@ and pass it to the `fetch` option in the constructor. import Replicate from "replicate"; import fetch from "cross-fetch"; -const replicate = new Replicate({ - // get your token from https://replicate.com/account - auth: process.env.REPLICATE_API_TOKEN, - fetch: fetch, -}); +const replicate = new Replicate({ fetch }); ``` You can override the `fetch` property to add custom behavior to client requests, such as injecting headers or adding log statements. ```js -client.fetch = (url, options) => { +replicate.fetch = (url, options) => { const headers = new Headers(options && options.headers); headers.append("X-Custom-Header", "some value"); diff --git a/index.d.ts b/index.d.ts index 50ebd1cd..32f279a7 100644 --- a/index.d.ts +++ b/index.d.ts @@ -69,8 +69,8 @@ declare module 'replicate' { } export default class Replicate { - constructor(options: { - auth: string; + constructor(options?: { + auth?: string; userAgent?: string; baseUrl?: string; fetch?: Function; diff --git a/index.js b/index.js index 9662235f..c6a2cc20 100644 --- a/index.js +++ b/index.js @@ -32,13 +32,13 @@ class Replicate { * Create a new Replicate API client instance. * * @param {object} options - Configuration options for the client - * @param {string} options.auth - Required. API access token + * @param {string} options.auth - API access token. Defaults to the `REPLICATE_API_TOKEN` environment variable. * @param {string} options.userAgent - Identifier of your app * @param {string} [options.baseUrl] - Defaults to https://api.replicate.com/v1 * @param {Function} [options.fetch] - Fetch function to use. Defaults to `globalThis.fetch` */ constructor(options = {}) { - this.auth = options.auth; + this.auth = options.auth || process.env.REPLICATE_API_TOKEN; this.userAgent = options.userAgent || `replicate-javascript/${packageJSON.version}`; this.baseUrl = options.baseUrl || 'https://api.replicate.com/v1'; diff --git a/index.test.ts b/index.test.ts index a560da18..c35b41f9 100644 --- a/index.test.ts +++ b/index.test.ts @@ -51,9 +51,12 @@ describe('Replicate client', () => { }); test('Does not throw error if auth token is not provided', () => { + process.env.REPLICATE_API_TOKEN = 'test-token'; + expect(() => { - // @ts-expect-error - new Replicate(); + const clientWithImplicitAuth = new Replicate(); + + expect(clientWithImplicitAuth.auth).toBe('test-token'); }).not.toThrow(); }); From 502b22dde8f5ead3df1513eb7df8a72fea295f59 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 3 Oct 2023 10:38:52 -0700 Subject: [PATCH 046/207] 0.19.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9983fbc9..989f02fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.18.1", + "version": "0.19.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.18.1", + "version": "0.19.0", "license": "Apache-2.0", "devDependencies": { "@types/jest": "^29.5.3", diff --git a/package.json b/package.json index 1a01fa3a..131663d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.18.1", + "version": "0.19.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From f8288390eb88451c746f3a4126f3c795eaa3fe5a Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Tue, 3 Oct 2023 15:25:39 -0700 Subject: [PATCH 047/207] add descriptions to methods in README (#143) * add descriptions to methods in README * Update README.md Co-authored-by: Mattt * Update README.md Co-authored-by: Mattt --------- Co-authored-by: Mattt --- README.md | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d6f02572..55e443c3 100644 --- a/README.md +++ b/README.md @@ -67,8 +67,8 @@ console.log(prediction.output); // ['https://replicate.delivery/pbxt/RoaxeXqhL0xaYyLm6w3bpGwF5RaNBjADukfFnMbhOyeoWBdhA/out-0.png'] ``` -To run a model that takes a file input, -convert its data into a base64-encoded data URI: +To run a model that takes a file input, pass a URL to a publicly accessible file. Or, for smaller files (<10MB), you can convert file data into a base64-encoded data URI and pass that directly: + ```js import { promises as fs } from "fs"; @@ -174,6 +174,8 @@ const output = await replicate.run(model, { input }); ### `replicate.models.get` +Get metadata for a public model or a private model that you own. + ```js const response = await replicate.models.get(model_owner, model_name); ``` @@ -201,6 +203,8 @@ const response = await replicate.models.get(model_owner, model_name); ### `replicate.models.versions.list` +Get a list of all published versions of a model, including input and output schemas for each version. + ```js const response = await replicate.models.versions.list(model_owner, model_name); ``` @@ -237,6 +241,8 @@ const response = await replicate.models.versions.list(model_owner, model_name); ### `replicate.models.versions.get` +Get metatadata for a specific version of a model. + ```js const response = await replicate.models.versions.get(model_owner, model_name, version_id); ``` @@ -260,6 +266,8 @@ const response = await replicate.models.versions.get(model_owner, model_name, ve ### `replicate.collections.get` +Get a list of curated model collections. See [replicate.com/collections](https://replicate.com/collections). + ```js const response = await replicate.collections.get(collection_slug); ``` @@ -270,6 +278,8 @@ const response = await replicate.collections.get(collection_slug); ### `replicate.predictions.create` +Run a model with inputs you provide. + ```js const response = await replicate.predictions.create(options); ``` @@ -380,6 +390,8 @@ const response = await replicate.predictions.get(prediction_id); ### `replicate.predictions.cancel` +Stop a running prediction before it finishes. + ```js const response = await replicate.predictions.cancel(prediction_id); ``` @@ -412,6 +424,8 @@ const response = await replicate.predictions.cancel(prediction_id); ### `replicate.predictions.list` +Get a paginated list of all the predictions you've created. + ```js const response = await replicate.predictions.list(); ``` @@ -443,7 +457,7 @@ const response = await replicate.predictions.list(); ### `replicate.trainings.create` -Use the training API to fine-tune language models +Use the [training API](https://replicate.com/docs/fine-tuning) to fine-tune language models to make them better at a particular task. To see what **language models** currently support fine-tuning, check out Replicate's [collection of trainable language models](https://replicate.com/collections/trainable-language-models). @@ -488,6 +502,8 @@ const response = await replicate.trainings.create(model_owner, model_name, versi ### `replicate.trainings.get` +Get metadata and status of a training. + ```js const response = await replicate.trainings.get(training_id); ``` @@ -519,6 +535,8 @@ const response = await replicate.trainings.get(training_id); ### `replicate.trainings.cancel` +Stop a running training job before it finishes. + ```js const response = await replicate.trainings.cancel(training_id); ``` @@ -550,6 +568,8 @@ const response = await replicate.trainings.cancel(training_id); ### `replicate.trainings.list` +Get a paginated list of all the trainings you've run. + ```js const response = await replicate.trainings.list(); ``` @@ -581,6 +601,10 @@ const response = await replicate.trainings.list(); ### `replicate.deployments.predictions.create` +Run a model using your own custom deployment. + +Deployments allow you to run a model with a private, fixed API endpoint. You can configure the version of the model, the hardware it runs on, and how it scales. See the [deployments guide](https://replicate.com/docs/deployments) to learn more and get started. + ```js const response = await replicate.deployments.predictions.create(deployment_owner, deployment_name, options); ``` @@ -620,6 +644,8 @@ const page2 = await paginator.next(); ### `replicate.request` +Low-level method used by the Replicate client to interact with API endpoints. + ```js const response = await replicate.request(route, parameters); ``` From 70c54fe035ee08087e39a04b70a2461a5accb9ab Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Wed, 4 Oct 2023 02:11:33 -0700 Subject: [PATCH 048/207] Add replicate.models.list method (#142) --- README.md | 35 +++++++++++++++++++++++++++++++++++ index.d.ts | 1 + index.js | 1 + index.test.ts | 26 +++++++++++++++++++++++++- lib/models.js | 23 +++++++++++++++++------ 5 files changed, 79 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 55e443c3..36fbcb0f 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,41 @@ const response = await replicate.models.get(model_owner, model_name); } ``` +### `replicate.models.list` + +Get a paginated list of all public models. + +```js +const response = await replicate.models.list(); +``` + +```jsonc +{ + "next": null, + "previous": null, + "results": [ + { + "url": "https://replicate.com/replicate/hello-world", + "owner": "replicate", + "name": "hello-world", + "description": "A tiny model that says hello", + "visibility": "public", + "github_url": "https://github.com/replicate/cog-examples", + "paper_url": null, + "license_url": null, + "run_count": 5681081, + "cover_image_url": "...", + "default_example": { + /* ... */ + }, + "latest_version": { + /* ... */ + } + } + ] +} +``` + ### `replicate.models.versions.list` Get a list of all published versions of a model, including input and output schemas for each version. diff --git a/index.d.ts b/index.d.ts index 32f279a7..601e15b8 100644 --- a/index.d.ts +++ b/index.d.ts @@ -117,6 +117,7 @@ declare module 'replicate' { models: { get(model_owner: string, model_name: string): Promise; + list(): Promise>; versions: { list(model_owner: string, model_name: string): Promise; get( diff --git a/index.js b/index.js index c6a2cc20..4f74985f 100644 --- a/index.js +++ b/index.js @@ -51,6 +51,7 @@ class Replicate { this.models = { get: models.get.bind(this), + list: models.list.bind(this), versions: { list: models.versions.list.bind(this), get: models.versions.get.bind(this), diff --git a/index.test.ts b/index.test.ts index c35b41f9..ab4e9d66 100644 --- a/index.test.ts +++ b/index.test.ts @@ -1,5 +1,5 @@ import { expect, jest, test } from '@jest/globals'; -import Replicate, { ApiError, Prediction } from 'replicate'; +import Replicate, { ApiError, Model, Prediction } from 'replicate'; import nock from 'nock'; import fetch from 'cross-fetch'; @@ -131,6 +131,30 @@ describe('Replicate client', () => { // Add more tests for error handling, edge cases, etc. }); + describe('models.list', () => { + test('Paginates results', async () => { + nock(BASE_URL) + .get('/models') + .reply(200, { + results: [{ url: 'https://replicate.com/some-user/model-1' }], + next: 'https://api.replicate.com/v1/models?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw', + }) + .get('/models?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw') + .reply(200, { + results: [{ url: 'https://replicate.com/some-user/model-2' }], + next: null, + }); + + const results: Model[] = []; + for await (const batch of client.paginate(client.models.list)) { + results.push(...batch); + } + expect(results).toEqual([{ url: 'https://replicate.com/some-user/model-1' }, { url: 'https://replicate.com/some-user/model-2' }]); + + // Add more tests for error handling, edge cases, etc. + }); + }); + describe('predictions.create', () => { test('Calls the correct API route with the correct payload', async () => { nock(BASE_URL) diff --git a/lib/models.js b/lib/models.js index 373ed230..be057503 100644 --- a/lib/models.js +++ b/lib/models.js @@ -37,17 +37,28 @@ async function listModelVersions(model_owner, model_name) { * @returns {Promise} Resolves with the model version data */ async function getModelVersion(model_owner, model_name, version_id) { - const response = await this.request( - `/models/${model_owner}/${model_name}/versions/${version_id}`, - { - method: 'GET', - } - ); + const response = await this.request(`/models/${model_owner}/${model_name}/versions/${version_id}`, { + method: 'GET', + }); + + return response.json(); +} + +/** + * List all public models + * + * @returns {Promise} Resolves with the model version data + */ +async function listModels() { + const response = await this.request('/models', { + method: 'GET', + }); return response.json(); } module.exports = { get: getModel, + list: listModels, versions: { list: listModelVersions, get: getModelVersion }, }; From 8f4c8e6a6d44801de3bf5ec54fab295569b4aa40 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Wed, 4 Oct 2023 02:12:20 -0700 Subject: [PATCH 049/207] 0.20.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 989f02fc..145bcb1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.19.0", + "version": "0.20.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.19.0", + "version": "0.20.0", "license": "Apache-2.0", "devDependencies": { "@types/jest": "^29.5.3", diff --git a/package.json b/package.json index 131663d7..c032728a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.19.0", + "version": "0.20.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From e2304969670f7b98bf3a54e3027d2078ce381bd0 Mon Sep 17 00:00:00 2001 From: Robert Long Date: Mon, 9 Oct 2023 15:59:30 -0700 Subject: [PATCH 050/207] Fix fetch scoping for cloudflare workers (#145) * Fix fetch scoping for cloudflare workers * Apply suggestions from code review * Fix linting error --------- Co-authored-by: Mattt Zmuda --- index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 4f74985f..902ba572 100644 --- a/index.js +++ b/index.js @@ -211,7 +211,11 @@ class Replicate { const shouldRetry = method === 'GET' ? (response) => (response.status === 429 || response.status >= 500) : (response) => (response.status === 429); - const response = await withAutomaticRetries(async () => this.fetch(url, init), { shouldRetry }); + + // Workaround to fix `TypeError: Illegal invocation` error in Cloudflare Workers + // https://github.com/replicate/replicate-javascript/issues/134 + const _fetch = this.fetch; // eslint-disable-line no-underscore-dangle + const response = await withAutomaticRetries(async () => _fetch(url, init), { shouldRetry }); if (!response.ok) { const request = new Request(url, init); From 0e08e3dff59357aefc7c72a3926316330df9d036 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Mon, 9 Oct 2023 15:59:59 -0700 Subject: [PATCH 051/207] 0.20.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 145bcb1a..ca169705 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.20.0", + "version": "0.20.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.20.0", + "version": "0.20.1", "license": "Apache-2.0", "devDependencies": { "@types/jest": "^29.5.3", diff --git a/package.json b/package.json index c032728a..2f537435 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.20.0", + "version": "0.20.1", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From b14eeaaf5e3a921ca372487103efbf5c52069c4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Oct 2023 02:08:02 -0700 Subject: [PATCH 052/207] Bump @babel/traverse from 7.21.3 to 7.23.2 (#148) Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.21.3 to 7.23.2. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse) --- updated-dependencies: - dependency-name: "@babel/traverse" dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 198 +++++++++++++++++++++++++++++++--------------- 1 file changed, 135 insertions(+), 63 deletions(-) diff --git a/package-lock.json b/package-lock.json index ca169705..8ff3fde5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ }, "engines": { "git": ">=2.11.0", - "node": ">=16.6.0", + "node": ">=18.0.0", "npm": ">=7.19.0", "yarn": ">=1.7.0" } @@ -45,17 +45,89 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/compat-data": { "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz", @@ -111,12 +183,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", - "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.21.3", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -168,34 +240,34 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -254,30 +326,30 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -307,13 +379,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -392,9 +464,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", - "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -581,33 +653,33 @@ } }, "node_modules/@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz", - "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.3", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.3", - "@babel/types": "^7.21.3", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -625,13 +697,13 @@ } }, "node_modules/@babel/types": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", - "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { From 47f8b01ef1a531028699d4064f968dc12d8b2643 Mon Sep 17 00:00:00 2001 From: Mattt Date: Mon, 6 Nov 2023 03:57:09 -0800 Subject: [PATCH 053/207] Add support for `models.create` and `hardware.list` endpoints (#153) * Add support for hardware.list endpoint * Add support for models.create endpoint * Update README * Update test expectation --- README.md | 37 +++++++++++++++++++++++++++++++++ index.d.ts | 52 +++++++++++++++++++++++++++++++++-------------- index.js | 18 +++++++++++------ index.test.ts | 54 ++++++++++++++++++++++++++++++++++++++++++++++--- lib/hardware.js | 16 +++++++++++++++ lib/models.js | 27 +++++++++++++++++++++++++ 6 files changed, 180 insertions(+), 24 deletions(-) create mode 100644 lib/hardware.js diff --git a/README.md b/README.md index 36fbcb0f..1eb7a2b5 100644 --- a/README.md +++ b/README.md @@ -236,6 +236,43 @@ const response = await replicate.models.list(); } ``` +### `replicate.models.create` + +Create a new public or private model. + +```js +const response = await replicate.models.create(model_owner, model_name, options); +``` + +| name | type | description | +| ------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `model_owner` | string | **Required**. The name of the user or organization that will own the model. This must be the same as the user or organization that is making the API request. In other words, the API token used in the request must belong to this user or organization. | +| `model_name` | string | **Required**. The name of the model. This must be unique among all models owned by the user or organization. | +| `options.visibility` | string | **Required**. Whether the model should be public or private. A public model can be viewed and run by anyone, whereas a private model can be viewed and run only by the user or organization members that own the model. | +| `options.hardware` | string | **Required**. The SKU for the hardware used to run the model. Possible values can be found by calling [`replicate.hardware.list()](#replicatehardwarelist)`. | +| `options.description` | string | A description of the model. | +| `options.github_url` | string | A URL for the model's source code on GitHub. | +| `options.paper_url` | string | A URL for the model's paper. | +| `options.license_url` | string | A URL for the model's license. | +| `options.cover_image_url` | string | A URL for the model's cover image. This should be an image file. | + +### `replicate.hardware.list` + +List available hardware for running models on Replicate. + +```js +const response = await replicate.hardware.list() +``` + +```jsonc +[ + {"name": "CPU", "sku": "cpu" }, + {"name": "Nvidia T4 GPU", "sku": "gpu-t4" }, + {"name": "Nvidia A40 GPU", "sku": "gpu-a40-small" }, + {"name": "Nvidia A40 (Large) GPU", "sku": "gpu-a40-large" }, +] +``` + ### `replicate.models.versions.list` Get a list of all published versions of a model, including input and output schemas for each version. diff --git a/index.d.ts b/index.d.ts index 601e15b8..a3e2ee04 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,5 +1,6 @@ declare module 'replicate' { type Status = 'starting' | 'processing' | 'succeeded' | 'failed' | 'canceled'; + type Visibility = 'public' | 'private'; type WebhookEventType = 'start' | 'output' | 'logs' | 'completed'; export interface ApiError extends Error { @@ -14,6 +15,11 @@ declare module 'replicate' { models?: Model[]; } + export interface Hardware { + sku: string; + name: string + } + export interface Model { url: string; owner: string; @@ -115,9 +121,40 @@ declare module 'replicate' { get(collection_slug: string): Promise; }; + deployments: { + predictions: { + create( + deployment_owner: string, + deployment_name: string, + options: { + input: object; + stream?: boolean; + webhook?: string; + webhook_events_filter?: WebhookEventType[]; + } + ): Promise; + }; + }; + + hardware: { + list(): Promise + } + models: { get(model_owner: string, model_name: string): Promise; list(): Promise>; + create( + model_owner: string, + model_name: string, + options: { + visibility: Visibility; + hardware: string; + description?: string; + github_url?: string; + paper_url?: string; + license_url?: string; + cover_image_url?: string; + }): Promise; versions: { list(model_owner: string, model_name: string): Promise; get( @@ -157,20 +194,5 @@ declare module 'replicate' { cancel(training_id: string): Promise; list(): Promise>; }; - - deployments: { - predictions: { - create( - deployment_owner: string, - deployment_name: string, - options: { - input: object; - stream?: boolean; - webhook?: string; - webhook_events_filter?: WebhookEventType[]; - } - ): Promise; - }; - }; } } diff --git a/index.js b/index.js index 902ba572..acb07eb7 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,7 @@ const { withAutomaticRetries } = require('./lib/util'); const collections = require('./lib/collections'); const deployments = require('./lib/deployments'); +const hardware = require('./lib/hardware'); const models = require('./lib/models'); const predictions = require('./lib/predictions'); const trainings = require('./lib/trainings'); @@ -49,9 +50,20 @@ class Replicate { get: collections.get.bind(this), }; + this.deployments = { + predictions: { + create: deployments.predictions.create.bind(this), + } + }; + + this.hardware = { + list: hardware.list.bind(this), + }; + this.models = { get: models.get.bind(this), list: models.list.bind(this), + create: models.create.bind(this), versions: { list: models.versions.list.bind(this), get: models.versions.get.bind(this), @@ -71,12 +83,6 @@ class Replicate { cancel: trainings.cancel.bind(this), list: trainings.list.bind(this), }; - - this.deployments = { - predictions: { - create: deployments.predictions.create.bind(this), - } - }; } /** diff --git a/index.test.ts b/index.test.ts index ab4e9d66..377357b1 100644 --- a/index.test.ts +++ b/index.test.ts @@ -136,12 +136,12 @@ describe('Replicate client', () => { nock(BASE_URL) .get('/models') .reply(200, { - results: [{ url: 'https://replicate.com/some-user/model-1' }], + results: [ { url: 'https://replicate.com/some-user/model-1' } ], next: 'https://api.replicate.com/v1/models?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw', }) .get('/models?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw') .reply(200, { - results: [{ url: 'https://replicate.com/some-user/model-2' }], + results: [ { url: 'https://replicate.com/some-user/model-2' } ], next: null, }); @@ -149,7 +149,7 @@ describe('Replicate client', () => { for await (const batch of client.paginate(client.models.list)) { results.push(...batch); } - expect(results).toEqual([{ url: 'https://replicate.com/some-user/model-1' }, { url: 'https://replicate.com/some-user/model-2' }]); + expect(results).toEqual([ { url: 'https://replicate.com/some-user/model-1' }, { url: 'https://replicate.com/some-user/model-2' } ]); // Add more tests for error handling, edge cases, etc. }); @@ -662,6 +662,54 @@ describe('Replicate client', () => { // Add more tests for error handling, edge cases, etc. }); + describe('hardware.list', () => { + test('Calls the correct API route', async () => { + nock(BASE_URL) + .get('/hardware') + .reply(200, [ + { name: "CPU", sku: "cpu" }, + { name: "Nvidia T4 GPU", sku: "gpu-t4" }, + { name: "Nvidia A40 GPU", sku: "gpu-a40-small" }, + { name: "Nvidia A40 (Large) GPU", sku: "gpu-a40-large" }, + ]); + + const hardware = await client.hardware.list(); + expect(hardware.length).toBe(4); + expect(hardware[ 0 ].name).toBe('CPU'); + expect(hardware[ 0 ].sku).toBe('cpu'); + }); + // Add more tests for error handling, edge cases, etc. + }); + + describe('models.create', () => { + test('Calls the correct API route with the correct payload', async () => { + nock(BASE_URL) + .post('/models') + .reply(200, { + owner: 'test-owner', + name: 'test-model', + visibility: 'public', + hardware: 'cpu', + description: 'A test model', + }); + + const model = await client.models.create( + 'test-owner', + 'test-model', + { + visibility: 'public', + hardware: 'cpu', + description: 'A test model', + }); + + expect(model.owner).toBe('test-owner'); + expect(model.name).toBe('test-model'); + expect(model.visibility).toBe('public'); + // expect(model.hardware).toBe('cpu'); + expect(model.description).toBe('A test model'); + }); + }); + describe('run', () => { test('Calls the correct API routes', async () => { let firstPollingRequest = true; diff --git a/lib/hardware.js b/lib/hardware.js new file mode 100644 index 00000000..487f3b88 --- /dev/null +++ b/lib/hardware.js @@ -0,0 +1,16 @@ +/** + * List hardware + * + * @returns {Promise} Resolves with the array of hardware + */ +async function listHardware() { + const response = await this.request('/hardware', { + method: 'GET', + }); + + return response.json(); +} + +module.exports = { + list: listHardware, +}; diff --git a/lib/models.js b/lib/models.js index be057503..3c4e5b1d 100644 --- a/lib/models.js +++ b/lib/models.js @@ -57,8 +57,35 @@ async function listModels() { return response.json(); } +/** + * Create a new model + * + * @param {string} model_owner - Required. The name of the user or organization that will own the model. This must be the same as the user or organization that is making the API request. In other words, the API token used in the request must belong to this user or organization. + * @param {string} model_name - Required. The name of the model. This must be unique among all models owned by the user or organization. + * @param {object} options + * @param {("public"|"private")} options.visibility - Required. Whether the model should be public or private. A public model can be viewed and run by anyone, whereas a private model can be viewed and run only by the user or organization members that own the model. + * @param {string} options.hardware - Required. The SKU for the hardware used to run the model. Possible values can be found by calling `Replicate.hardware.list()`. + * @param {string} options.description - A description of the model. + * @param {string} options.github_url - A URL for the model's source code on GitHub. + * @param {string} options.paper_url - A URL for the model's paper. + * @param {string} options.license_url - A URL for the model's license. + * @param {string} options.cover_image_url - A URL for the model's cover image. This should be an image file. + * @returns {Promise} Resolves with the model version data + */ +async function createModel(model_owner, model_name, options) { + const data = { owner: model_owner, name: model_name, ...options }; + + const response = await this.request('/models', { + method: 'POST', + data, + }); + + return response.json(); +} + module.exports = { get: getModel, list: listModels, + create: createModel, versions: { list: listModelVersions, get: getModelVersion }, }; From 85b0b9d70799b4a310bb7a0e8d7bfb61128ee4f1 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Mon, 6 Nov 2023 03:58:30 -0800 Subject: [PATCH 054/207] 0.21.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ff3fde5..bed1d6fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.20.1", + "version": "0.21.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.20.1", + "version": "0.21.0", "license": "Apache-2.0", "devDependencies": { "@types/jest": "^29.5.3", diff --git a/package.json b/package.json index 2f537435..597df68f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.20.1", + "version": "0.21.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 8c515451184bc633faec24a3150183eea04c3d6c Mon Sep 17 00:00:00 2001 From: Mattt Date: Mon, 6 Nov 2023 12:34:13 -0800 Subject: [PATCH 055/207] Update TypeScript type definition to make wait options optional (#155) --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index a3e2ee04..d2402ca9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -110,7 +110,7 @@ declare module 'replicate' { wait( prediction: Prediction, - options: { + options?: { interval?: number; }, stop?: (prediction: Prediction) => Promise From 9a0cc53dd0ce54f1ab0ebabe887389530242087f Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Mon, 6 Nov 2023 12:34:40 -0800 Subject: [PATCH 056/207] 0.21.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index bed1d6fe..fb10a1ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.21.0", + "version": "0.21.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.21.0", + "version": "0.21.1", "license": "Apache-2.0", "devDependencies": { "@types/jest": "^29.5.3", diff --git a/package.json b/package.json index 597df68f..1b1d1efd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.21.0", + "version": "0.21.1", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From d16416330a06cae99a5847fadbba6c613599d1bb Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 13 Nov 2023 11:54:51 +0000 Subject: [PATCH 057/207] Add user research to README (#158) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 1eb7a2b5..4b4a7e18 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +> **Important** +> [Help us make Replicate better by taking part in user research](https://forms.gle/qUVQ84mjiqzwzSb48)
+> If you take part in an interview, we'll give you $100 of Replicate credits for your time. + # Replicate Node.js client A Node.js client for [Replicate](https://replicate.com). From 7dad954163ae57c1a47667360b8426efc9b3af81 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 14 Nov 2023 22:51:33 +0000 Subject: [PATCH 058/207] Revert update to README.md (#159) --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 4b4a7e18..1eb7a2b5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,3 @@ -> **Important** -> [Help us make Replicate better by taking part in user research](https://forms.gle/qUVQ84mjiqzwzSb48)
-> If you take part in an interview, we'll give you $100 of Replicate credits for your time. - # Replicate Node.js client A Node.js client for [Replicate](https://replicate.com). From 2b02d20510d35972dbd253056891cb7aa31ee5d9 Mon Sep 17 00:00:00 2001 From: Mattt Date: Thu, 16 Nov 2023 09:08:03 -0500 Subject: [PATCH 059/207] Add model field to Prediction type definition (#160) --- index.d.ts | 1 + index.test.ts | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index d2402ca9..bf9cd5d7 100644 --- a/index.d.ts +++ b/index.d.ts @@ -45,6 +45,7 @@ declare module 'replicate' { export interface Prediction { id: string; status: Status; + model: string; version: string; input: object; output?: any; diff --git a/index.test.ts b/index.test.ts index 377357b1..d0339024 100644 --- a/index.test.ts +++ b/index.test.ts @@ -161,6 +161,7 @@ describe('Replicate client', () => { .post('/predictions') .reply(200, { id: 'ufawqhfynnddngldkgtslldrkq', + model: 'replicate/hello-world', version: '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', urls: { @@ -290,6 +291,7 @@ describe('Replicate client', () => { .get('/predictions/rrr4z55ocneqzikepnug6xezpe') .reply(200, { id: 'rrr4z55ocneqzikepnug6xezpe', + model: 'stability-ai/stable-diffusion', version: 'be04660a5b93ef2aff61e3668dedb4cbeb14941e62a3fd5998364a32d613e35e', urls: { @@ -361,6 +363,7 @@ describe('Replicate client', () => { .post('/predictions/ufawqhfynnddngldkgtslldrkq/cancel') .reply(200, { id: 'ufawqhfynnddngldkgtslldrkq', + model: 'replicate/hello-world', version: '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', urls: { @@ -400,6 +403,7 @@ describe('Replicate client', () => { results: [ { id: 'jpzd7hm5gfcapbfyt4mqytarku', + model: 'stability-ai/stable-diffusion', version: 'b21cbe271e65c1718f2999b038c18b45e21e4fba961181fbfae9342fc53b9e05', urls: { @@ -457,7 +461,7 @@ describe('Replicate client', () => { ) .reply(200, { id: 'zz4ibbonubfz7carwiefibzgga', - version: '{version}', + version: '632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532', status: 'starting', input: { text: '...', @@ -576,6 +580,7 @@ describe('Replicate client', () => { results: [ { id: 'jpzd7hm5gfcapbfyt4mqytarku', + model: 'stability-ai/sdxl', version: 'b21cbe271e65c1718f2999b038c18b45e21e4fba961181fbfae9342fc53b9e05', urls: { @@ -631,6 +636,7 @@ describe('Replicate client', () => { .post('/deployments/replicate/greeter/predictions') .reply(200, { id: 'mfrgcyzzme2wkmbwgzrgmntcg', + model: 'replicate/hello-world', version: '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', urls: { From eefbcd48170429703b1c4c582374c5ee2fb9dea9 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Thu, 16 Nov 2023 09:10:03 -0500 Subject: [PATCH 060/207] 0.22.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index fb10a1ed..061b8c08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.21.1", + "version": "0.22.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.21.1", + "version": "0.22.0", "license": "Apache-2.0", "devDependencies": { "@types/jest": "^29.5.3", diff --git a/package.json b/package.json index 1b1d1efd..64c392e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.21.1", + "version": "0.22.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 9f500dd21d95e208c8b342f8941f973a5ad4a6c0 Mon Sep 17 00:00:00 2001 From: Mattt Date: Wed, 29 Nov 2023 09:38:33 -0800 Subject: [PATCH 061/207] Add support for models.predictions.create endpoint (#163) --- index.d.ts | 12 ++++++++++++ index.js | 3 +++ index.test.ts | 32 ++++++++++++++++++++++++++++++++ lib/models.js | 24 ++++++++++++++++++++++++ 4 files changed, 71 insertions(+) diff --git a/index.d.ts b/index.d.ts index bf9cd5d7..c415bf08 100644 --- a/index.d.ts +++ b/index.d.ts @@ -164,6 +164,18 @@ declare module 'replicate' { version_id: string ): Promise; }; + predictions: { + create( + model_owner: string, + model_name: string, + options: { + input: object; + stream?: boolean; + webhook?: string; + webhook_events_filter?: WebhookEventType[]; + } + ): Promise; + }; }; predictions: { diff --git a/index.js b/index.js index acb07eb7..0552ea51 100644 --- a/index.js +++ b/index.js @@ -68,6 +68,9 @@ class Replicate { list: models.versions.list.bind(this), get: models.versions.get.bind(this), }, + predictions: { + create: models.predictions.create.bind(this), + }, }; this.predictions = { diff --git a/index.test.ts b/index.test.ts index d0339024..ec9e523d 100644 --- a/index.test.ts +++ b/index.test.ts @@ -668,6 +668,38 @@ describe('Replicate client', () => { // Add more tests for error handling, edge cases, etc. }); + describe('models.predictions.create', () => { + test('Calls the correct API route with the correct payload', async () => { + nock(BASE_URL) + .post('/models/meta/llama-2-70b-chat/predictions') + .reply(200, { + id: "heat2o3bzn3ahtr6bjfftvbaci", + model: "replicate/lifeboat-70b", + version: "d-c6559c5791b50af57b69f4a73f8e021c", + input: { + prompt: "Please write a haiku about llamas." + }, + logs: "", + error: null, + status: "starting", + created_at: "2023-11-27T13:35:45.99397566Z", + urls: { + cancel: "https://api.replicate.com/v1/predictions/heat2o3bzn3ahtr6bjfftvbaci/cancel", + get: "https://api.replicate.com/v1/predictions/heat2o3bzn3ahtr6bjfftvbaci" + } + }); + const prediction = await client.models.predictions.create("meta", "llama-2-70b-chat", { + input: { + prompt: "Please write a haiku about llamas." + }, + webhook: 'http://test.host/webhook', + webhook_events_filter: [ 'output', 'completed' ], + }); + expect(prediction.id).toBe('heat2o3bzn3ahtr6bjfftvbaci'); + }); + // Add more tests for error handling, edge cases, etc. + }); + describe('hardware.list', () => { test('Calls the correct API route', async () => { nock(BASE_URL) diff --git a/lib/models.js b/lib/models.js index 3c4e5b1d..12419fc2 100644 --- a/lib/models.js +++ b/lib/models.js @@ -83,9 +83,33 @@ async function createModel(model_owner, model_name, options) { return response.json(); } +/** + * Create a new prediction + * + * @param {string} model_owner - Required. The name of the user or organization that owns the model + * @param {string} model_name - Required. The name of the model + * @param {object} options + * @param {object} options.input - Required. An object with the model inputs + * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output + * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) + * @param {boolean} [options.stream] - Whether to stream the prediction output. Defaults to false + * @returns {Promise} Resolves with the created prediction + */ +async function createPrediction(model_owner, model_name, options) { + const { stream, ...data } = options; + + const response = await this.request(`/models/${model_owner}/${model_name}/predictions`, { + method: 'POST', + data: { ...data, stream }, + }); + + return response.json(); +} + module.exports = { get: getModel, list: listModels, create: createModel, versions: { list: listModelVersions, get: getModelVersion }, + predictions: { create: createPrediction }, }; From 89d88a00445f12ec862e77103cc81a9e3498a13c Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Wed, 6 Dec 2023 10:23:30 -0800 Subject: [PATCH 062/207] 0.23.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 061b8c08..7d1654e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.22.0", + "version": "0.23.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.22.0", + "version": "0.23.0", "license": "Apache-2.0", "devDependencies": { "@types/jest": "^29.5.3", diff --git a/package.json b/package.json index 64c392e2..d1ce61b0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.22.0", + "version": "0.23.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From d5498143f753d9781905814895b5880beeb69cee Mon Sep 17 00:00:00 2001 From: Mattt Date: Fri, 8 Dec 2023 05:13:59 -0800 Subject: [PATCH 063/207] Allow `run` method to take model argument, when supported (#167) * Extract model version identifier into separate component * Allow `run` method to take model argument, when supported --- index.d.ts | 2 +- index.js | 41 +++++++++++++----------------- index.test.ts | 63 ++++++++++++++++++++++++++++++++++++++++++++--- lib/identifier.js | 35 ++++++++++++++++++++++++++ 4 files changed, 113 insertions(+), 28 deletions(-) create mode 100644 lib/identifier.js diff --git a/index.d.ts b/index.d.ts index c415bf08..1218cf68 100644 --- a/index.d.ts +++ b/index.d.ts @@ -89,7 +89,7 @@ declare module 'replicate' { fetch: Function; run( - identifier: `${string}/${string}:${string}`, + identifier: `${string}/${string}` | `${string}/${string}:${string}`, options: { input: object; wait?: { interval?: number }; diff --git a/index.js b/index.js index 0552ea51..8736bb8f 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,5 @@ const ApiError = require('./lib/error'); +const ModelVersionIdentifier = require('./lib/identifier'); const { withAutomaticRetries } = require('./lib/util'); const collections = require('./lib/collections'); @@ -91,7 +92,7 @@ class Replicate { /** * Run a model and wait for its output. * - * @param {string} identifier - Required. The model version identifier in the format "{owner}/{name}:{version}" + * @param {string} ref - Required. The model version identifier in the format "owner/name" or "owner/name:version" * @param {object} options * @param {object} options.input - Required. An object with the model inputs * @param {object} [options.wait] - Options for waiting for the prediction to finish @@ -100,37 +101,29 @@ class Replicate { * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) * @param {AbortSignal} [options.signal] - AbortSignal to cancel the prediction * @param {Function} [progress] - Callback function that receives the prediction object as it's updated. The function is called when the prediction is created, each time its updated while polling for completion, and when it's completed. + * @throws {Error} If the reference is invalid * @throws {Error} If the prediction failed * @returns {Promise} - Resolves with the output of running the model */ - async run(identifier, options, progress) { + async run(ref, options, progress) { const { wait, ...data } = options; - // Define a pattern for owner and model names that allows - // letters, digits, and certain special characters. - // Example: "user123", "abc__123", "user.name" - const namePattern = /[a-zA-Z0-9]+(?:(?:[._]|__|[-]*)[a-zA-Z0-9]+)*/; - - // Define a pattern for "owner/name:version" format with named capturing groups. - // Example: "user123/repo_a:1a2b3c" - const pattern = new RegExp( - `^(?${namePattern.source})/(?${namePattern.source}):(?[0-9a-fA-F]+)$` - ); - - const match = identifier.match(pattern); - if (!match || !match.groups) { - throw new Error( - 'Invalid version. It must be in the format "owner/name:version"' + const identifier = ModelVersionIdentifier.parse(ref); + + let prediction; + if (identifier.version) { + prediction = await this.predictions.create({ + ...data, + version: identifier.version, + }); + } else { + prediction = await this.models.predictions.create( + identifier.owner, + identifier.name, + data ); } - const { version } = match.groups; - - let prediction = await this.predictions.create({ - ...data, - version, - }); - // Call progress callback with the initial prediction object if (progress) { progress(prediction); diff --git a/index.test.ts b/index.test.ts index ec9e523d..2684b85f 100644 --- a/index.test.ts +++ b/index.test.ts @@ -749,7 +749,7 @@ describe('Replicate client', () => { }); describe('run', () => { - test('Calls the correct API routes', async () => { + test('Calls the correct API routes for a version', async () => { let firstPollingRequest = true; nock(BASE_URL) @@ -808,6 +808,65 @@ describe('Replicate client', () => { expect(progress).toHaveBeenCalledTimes(4); }); + test('Calls the correct API routes for a model', async () => { + let firstPollingRequest = true; + + nock(BASE_URL) + .post('/models/replicate/hello-world/predictions') + .reply(201, { + id: 'ufawqhfynnddngldkgtslldrkq', + status: 'starting', + }) + .get('/predictions/ufawqhfynnddngldkgtslldrkq') + .twice() + .reply(200, { + id: 'ufawqhfynnddngldkgtslldrkq', + status: 'processing', + }) + .get('/predictions/ufawqhfynnddngldkgtslldrkq') + .reply(200, { + id: 'ufawqhfynnddngldkgtslldrkq', + status: 'succeeded', + output: 'Goodbye!', + }); + + const progress = jest.fn(); + + const output = await client.run( + 'replicate/hello-world', + { + input: { text: 'Hello, world!' }, + wait: { interval: 1 } + }, + progress + ); + + expect(output).toBe('Goodbye!'); + + expect(progress).toHaveBeenNthCalledWith(1, { + id: 'ufawqhfynnddngldkgtslldrkq', + status: 'starting', + }); + + expect(progress).toHaveBeenNthCalledWith(2, { + id: 'ufawqhfynnddngldkgtslldrkq', + status: 'processing', + }); + + expect(progress).toHaveBeenNthCalledWith(3, { + id: 'ufawqhfynnddngldkgtslldrkq', + status: 'processing', + }); + + expect(progress).toHaveBeenNthCalledWith(4, { + id: 'ufawqhfynnddngldkgtslldrkq', + status: 'succeeded', + output: 'Goodbye!', + }); + + expect(progress).toHaveBeenCalledTimes(4); + }); + test('Does not throw an error for identifier containing hyphen and full stop', async () => { nock(BASE_URL) .post('/predictions') @@ -828,8 +887,6 @@ describe('Replicate client', () => { test('Throws an error for invalid identifiers', async () => { const options = { input: { text: 'Hello, world!' } } - await expect(client.run('owner/model:invalid', options)).rejects.toThrow(); - // @ts-expect-error await expect(client.run('owner:abc123', options)).rejects.toThrow(); diff --git a/lib/identifier.js b/lib/identifier.js new file mode 100644 index 00000000..07e21d1e --- /dev/null +++ b/lib/identifier.js @@ -0,0 +1,35 @@ +/* + * A reference to a model version in the format `owner/name` or `owner/name:version`. + */ +class ModelVersionIdentifier { + /* + * @param {string} Required. The model owner. + * @param {string} Required. The model name. + * @param {string} The model version. + */ + constructor(owner, name, version = null) { + this.owner = owner; + this.name = name; + this.version = version; + } + + /* + * Parse a reference to a model version + * + * @param {string} + * @returns {ModelVersionIdentifier} + * @throws {Error} If the reference is invalid. + */ + static parse(ref) { + const match = ref.match(/^(?[^/]+)\/(?[^/:]+)(:(?.+))?$/); + if (!match) { + throw new Error(`Invalid reference to model version: ${ref}. Expected format: owner/name or owner/name:version`); + } + + const { owner, name, version } = match.groups; + + return new ModelVersionIdentifier(owner, name, version); + } +} + +module.exports = ModelVersionIdentifier; From 33d2c5a38c5591198e802cca70677ed71226850c Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 8 Dec 2023 05:25:58 -0800 Subject: [PATCH 064/207] Replace eslint with biome --- .eslintrc.js | 28 - biome.json | 32 + package-lock.json | 1542 +++++++-------------------------------------- package.json | 10 +- 4 files changed, 275 insertions(+), 1337 deletions(-) delete mode 100644 .eslintrc.js create mode 100644 biome.json diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index e33833fb..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,28 +0,0 @@ -module.exports = { - env: { - browser: true, - commonjs: true, - es2021: true, - node: true, - }, - extends: [ - 'airbnb-base', - 'plugin:jest/recommended', - 'plugin:jsdoc/recommended', - ], - overrides: [], - parserOptions: { - ecmaVersion: 'latest', - project: './jsconfig.json', - }, - plugins: ['jest', 'jsdoc'], - rules: { - camelcase: 'off', - 'comma-dangle': 'off', - 'generator-star-spacing': 'off', - 'max-len': 'off', - 'operator-linebreak': 'off', - 'jsdoc/require-param-description': 'off', - 'jsdoc/tag-lines': ['error', 'any', { startLines: 1 }], - }, -}; diff --git a/biome.json b/biome.json new file mode 100644 index 00000000..63919df3 --- /dev/null +++ b/biome.json @@ -0,0 +1,32 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.0.0/schema.json", + "formatter": { + "indentStyle": "space", + "indentWidth": 2 + }, + "javascript": { + "formatter": { + "trailingComma": "es5" + } + }, + "linter": { + "enabled": true, + "rules": { + "a11y": { + "useAltText": "off", + "useMediaCaption": "off", + "noSvgWithoutTitle": "off" + }, + "performance": { + "noAccumulatingSpread": "off" + }, + "suspicious": { + "noArrayIndexKey": "off", + "noExplicitAny": "off" + }, + "complexity": { + "useOptionalChain": "off" + } + } + } +} diff --git a/package-lock.json b/package-lock.json index 7d1654e0..1c211cff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,16 +9,10 @@ "version": "0.23.0", "license": "Apache-2.0", "devDependencies": { + "@biomejs/biome": "^1.4.1", "@types/jest": "^29.5.3", "@typescript-eslint/eslint-plugin": "^5.56.0", "cross-fetch": "^3.1.5", - "eslint": "^8.36.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-jest": "^27.2.1", - "eslint-plugin-jsdoc": "^46.2.6", - "eslint-plugin-n": "^15.6.1", - "eslint-plugin-promise": "^6.1.1", "jest": "^29.6.2", "nock": "^13.3.0", "ts-jest": "^29.1.0", @@ -716,18 +710,125 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "node_modules/@es-joy/jsdoccomment": { - "version": "0.39.4", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.39.4.tgz", - "integrity": "sha512-Jvw915fjqQct445+yron7Dufix9A+m9j1fCJYlCo1FWlRvTxa3pjJelxdSTdaLWcTwRU6vbL+NYjO4YuNIS5Qg==", + "node_modules/@biomejs/biome": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.4.1.tgz", + "integrity": "sha512-JccVAwPbhi37pdxbAGmaOBjUTKEwEjWAhl7rKkVVuXHo4MLASXJ5HR8BTgrImi4/7rTBsGz1tgVD1Kwv1CHGRg==", "dev": true, - "dependencies": { - "comment-parser": "1.3.1", - "esquery": "^1.5.0", - "jsdoc-type-pratt-parser": "~4.0.0" + "hasInstallScript": true, + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "1.4.1", + "@biomejs/cli-darwin-x64": "1.4.1", + "@biomejs/cli-linux-arm64": "1.4.1", + "@biomejs/cli-linux-x64": "1.4.1", + "@biomejs/cli-win32-arm64": "1.4.1", + "@biomejs/cli-win32-x64": "1.4.1" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.4.1.tgz", + "integrity": "sha512-PZWy2Idndqux38p6AXSDQM2ldRAWi32bvb7bMbTN0ALzpWYMYnxd71ornatumSSJYoNhKmxzDLq+jct7nZJ79w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.*" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.4.1.tgz", + "integrity": "sha512-soj3BWhnsM1M2JlzR09cibUzG1owJqetwj/Oo7yg0foijo9lNH9XWXZfJBYDKgW/6Fomn+CC2EcUS+hisQzt9g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.*" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.4.1.tgz", + "integrity": "sha512-YIZqfJUg4F+fPsBTXxgD7EU2E5OAYbmYSl/snf4PevwfQCWE/omOFZv+NnIQmjYj9I7ParDgcJvanoA3/kO0JQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.*" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.4.1.tgz", + "integrity": "sha512-9YOZw3qBd/KUj63A6Hn2zZgzGb2nbESM0qNmeMXgmqinVKM//uc4OgY5TuKITuGjMSvcVxxd4dX1IzYjV9qvNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.*" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.4.1.tgz", + "integrity": "sha512-nWQbvkNKxYn/kCQ0yVF8kCaS3VzaGvtFSmItXiMknU4521LDjJ7tNWH12Gol+pIslrCbd4E1LhJa0a3ThRsBVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.*" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.4.1.tgz", + "integrity": "sha512-88fR2CQxQ4YLs2BUDuywWYQpUKgU3A3sTezANFc/4LGKQFFLV2yX+F7QAdZVkMHfA+RD9Xg178HomM/6mnTNPA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=16" + "node": ">=14.*" } }, "node_modules/@eslint-community/eslint-utils": { @@ -759,6 +860,7 @@ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.1.tgz", "integrity": "sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==", "dev": true, + "peer": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -782,6 +884,7 @@ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.36.0.tgz", "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==", "dev": true, + "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -791,6 +894,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", "dev": true, + "peer": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", @@ -805,6 +909,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "peer": true, "engines": { "node": ">=12.22" }, @@ -817,7 +922,8 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", @@ -1401,12 +1507,6 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true - }, "node_modules/@types/node": { "version": "18.15.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.5.tgz", @@ -1634,6 +1734,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1646,6 +1747,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "peer": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -1655,6 +1757,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1730,52 +1833,12 @@ "node": ">= 8" } }, - "node_modules/are-docs-informative": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", - "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", - "dev": true, - "engines": { - "node": ">=14" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-includes": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "peer": true }, "node_modules/array-union": { "version": "2.1.0", @@ -1786,54 +1849,6 @@ "node": ">=8" } }, - "node_modules/array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/babel-jest": { "version": "29.6.2", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.2.tgz", @@ -2008,40 +2023,6 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/builtins": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", - "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", - "dev": true, - "dependencies": { - "semver": "^7.0.0" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2170,27 +2151,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/comment-parser": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.3.1.tgz", - "integrity": "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==", - "dev": true, - "engines": { - "node": ">= 12.0.0" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "node_modules/confusing-browser-globals": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", - "dev": true - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2255,7 +2221,8 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/deepmerge": { "version": "4.3.1", @@ -2266,22 +2233,6 @@ "node": ">=0.10.0" } }, - "node_modules/define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "dev": true, - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2317,6 +2268,7 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, + "peer": true, "dependencies": { "esutils": "^2.0.2" }, @@ -2357,94 +2309,6 @@ "is-arrayish": "^0.2.1" } }, - "node_modules/es-abstract": { - "version": "1.21.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", - "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.0", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -2459,6 +2323,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "peer": true, "engines": { "node": ">=10" }, @@ -2471,6 +2336,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.36.0.tgz", "integrity": "sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", @@ -2523,349 +2389,65 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-config-airbnb-base": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", - "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "dependencies": { - "confusing-browser-globals": "^1.0.10", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5", - "semver": "^6.3.0" + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.2" + "node": ">=8.0.0" } }, - "node_modules/eslint-config-airbnb-base/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "dev": true, - "bin": { - "semver": "bin/semver.js" + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", - "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", "dev": true, + "peer": true, "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "dependencies": { - "ms": "^2.1.1" + "peer": true, + "engines": { + "node": ">=4.0" } }, - "node_modules/eslint-module-utils": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "node_modules/espree": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz", + "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==", "dev": true, + "peer": true, "dependencies": { - "debug": "^3.2.7" + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-es": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", - "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", - "dev": true, - "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-es/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.27.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", - "has": "^1.0.3", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-jest": { - "version": "27.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.2.1.tgz", - "integrity": "sha512-l067Uxx7ZT8cO9NJuf+eJHvt6bqJyz2Z29wykyEdz/OtmcELQl2MQGQLX8J94O1cSJWAwUSEvCjwjA7KEK3Hmg==", - "dev": true, - "dependencies": { - "@typescript-eslint/utils": "^5.10.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.0.0", - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - }, - "jest": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-jsdoc": { - "version": "46.2.6", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.2.6.tgz", - "integrity": "sha512-zIaK3zbSrKuH12bP+SPybPgcHSM6MFzh3HFeaODzmsF1N8C1l8dzJ22cW1aq4g0+nayU1VMjmNf7hg0dpShLrA==", - "dev": true, - "dependencies": { - "@es-joy/jsdoccomment": "~0.39.4", - "are-docs-informative": "^0.0.2", - "comment-parser": "1.3.1", - "debug": "^4.3.4", - "escape-string-regexp": "^4.0.0", - "esquery": "^1.5.0", - "is-builtin-module": "^3.2.1", - "semver": "^7.5.1", - "spdx-expression-parse": "^3.0.1" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/eslint-plugin-n": { - "version": "15.6.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.6.1.tgz", - "integrity": "sha512-R9xw9OtCRxxaxaszTQmQAlPgM+RdGjaL1akWuY/Fv9fRAi8Wj4CUKc6iYVG8QNRjRuo8/BqVYIpfqberJUEacA==", - "dev": true, - "dependencies": { - "builtins": "^5.0.1", - "eslint-plugin-es": "^4.1.0", - "eslint-utils": "^3.0.0", - "ignore": "^5.1.1", - "is-core-module": "^2.11.0", - "minimatch": "^3.1.2", - "resolve": "^1.22.1", - "semver": "^7.3.8" - }, - "engines": { - "node": ">=12.22.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-promise": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", - "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/espree": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz", - "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==", - "dev": true, - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -2889,6 +2471,7 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, + "peer": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -2901,6 +2484,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "peer": true, "engines": { "node": ">=4.0" } @@ -2940,6 +2524,7 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -2997,7 +2582,8 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "peer": true }, "node_modules/fast-glob": { "version": "3.2.12", @@ -3037,7 +2623,8 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/fastq": { "version": "1.15.0", @@ -3062,6 +2649,7 @@ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, + "peer": true, "dependencies": { "flat-cache": "^3.0.4" }, @@ -3086,6 +2674,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "peer": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -3102,6 +2691,7 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, + "peer": true, "dependencies": { "flatted": "^3.1.0", "rimraf": "^3.0.2" @@ -3114,16 +2704,8 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", "dev": true, - "dependencies": { - "is-callable": "^1.1.3" - } + "peer": true }, "node_modules/fs.realpath": { "version": "1.0.0", @@ -3151,33 +2733,6 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3196,20 +2751,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -3231,22 +2772,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -3272,6 +2797,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "peer": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -3284,6 +2810,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, + "peer": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -3294,21 +2821,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -3329,18 +2841,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -3365,15 +2865,6 @@ "node": ">= 0.4.0" } }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3383,57 +2874,6 @@ "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -3463,6 +2903,7 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, + "peer": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -3518,95 +2959,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "node_modules/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dev": true, - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-core-module": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", @@ -3619,21 +2977,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3646,177 +2989,62 @@ "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.12.0" } }, - "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - }, + "peer": true, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2" + "engines": { + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/isexe": { @@ -4460,6 +3688,7 @@ "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", "dev": true, + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/js-sdsl" @@ -4476,6 +3705,7 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "peer": true, "dependencies": { "argparse": "^2.0.1" }, @@ -4483,15 +3713,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsdoc-type-pratt-parser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", - "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", - "dev": true, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -4514,13 +3735,15 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/json-stringify-safe": { "version": "5.0.1", @@ -4563,6 +3786,7 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "peer": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -4582,6 +3806,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "peer": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -4608,7 +3833,8 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/lru-cache": { "version": "5.1.1", @@ -4698,15 +3924,6 @@ "node": "*" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -4793,73 +4010,6 @@ "node": ">=8" } }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.entries": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", - "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.values": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4889,6 +4039,7 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "dev": true, + "peer": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -4921,6 +4072,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "peer": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -4945,6 +4097,7 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "peer": true, "dependencies": { "callsites": "^3.0.0" }, @@ -5108,6 +4261,7 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "peer": true, "engines": { "node": ">= 0.8.0" } @@ -5165,6 +4319,7 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "dev": true, + "peer": true, "engines": { "node": ">=6" } @@ -5211,35 +4366,6 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "node_modules/regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -5292,6 +4418,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "peer": true, "engines": { "node": ">=4" } @@ -5320,6 +4447,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, + "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -5353,20 +4481,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -5421,20 +4535,6 @@ "node": ">=8" } }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -5475,28 +4575,6 @@ "source-map": "^0.6.0" } }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", - "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", - "dev": true - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -5551,51 +4629,6 @@ "node": ">=8" } }, - "node_modules/string.prototype.trim": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", - "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -5680,7 +4713,8 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/tmpl": { "version": "1.0.5", @@ -5758,39 +4792,6 @@ } } }, - "node_modules/tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", - "dev": true, - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/tsconfig-paths/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -5817,6 +4818,7 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "peer": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -5838,6 +4840,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "peer": true, "engines": { "node": ">=10" }, @@ -5845,20 +4848,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/typescript": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", @@ -5872,21 +4861,6 @@ "node": ">=12.20" } }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/update-browserslist-db": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", @@ -5918,6 +4892,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "peer": true, "dependencies": { "punycode": "^2.1.0" } @@ -5982,47 +4957,12 @@ "node": ">= 8" } }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/word-wrap": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", "dev": true, + "peer": true, "engines": { "node": ">=0.10.0" } diff --git a/package.json b/package.json index d1ce61b0..7656ee37 100644 --- a/package.json +++ b/package.json @@ -15,20 +15,14 @@ }, "scripts": { "check": "tsc", - "lint": "eslint .", + "lint": "biome lint .", "test": "jest" }, "devDependencies": { + "@biomejs/biome": "^1.4.1", "@types/jest": "^29.5.3", "@typescript-eslint/eslint-plugin": "^5.56.0", "cross-fetch": "^3.1.5", - "eslint": "^8.36.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-jest": "^27.2.1", - "eslint-plugin-jsdoc": "^46.2.6", - "eslint-plugin-n": "^15.6.1", - "eslint-plugin-promise": "^6.1.1", "jest": "^29.6.2", "nock": "^13.3.0", "ts-jest": "^29.1.0", From 5ce21cbed821ebfe0ed5f0b942c0fa77d57aea60 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 8 Dec 2023 05:26:20 -0800 Subject: [PATCH 065/207] Add biomejs.biome to list of VSCode extension recommendations --- .vscode/extensions.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .vscode/extensions.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..699ed733 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["biomejs.biome"] +} From b5bf14712081b48c06c4a5149454ff5b01ba911e Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 8 Dec 2023 05:26:35 -0800 Subject: [PATCH 066/207] Configure VSCode to use biome as default formatter --- .vscode/settings.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index f89ed5f1..3d9e69cf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,10 @@ { - "editor.formatOnSave": true + "editor.formatOnSave": true, + "biome.requireConfiguration": true, + "[javascript]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" + } } From 86feeab2211de4b887b6a364301fadf87abc3651 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 8 Dec 2023 05:27:26 -0800 Subject: [PATCH 067/207] Apply automatic fixes to biome linting failures --- index.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.test.ts b/index.test.ts index 2684b85f..c5c30d59 100644 --- a/index.test.ts +++ b/index.test.ts @@ -750,7 +750,7 @@ describe('Replicate client', () => { describe('run', () => { test('Calls the correct API routes for a version', async () => { - let firstPollingRequest = true; + const firstPollingRequest = true; nock(BASE_URL) .post('/predictions') @@ -809,7 +809,7 @@ describe('Replicate client', () => { }); test('Calls the correct API routes for a model', async () => { - let firstPollingRequest = true; + const firstPollingRequest = true; nock(BASE_URL) .post('/models/replicate/hello-world/predictions') From 003590cc2529b26e7b114c72f71cc7a6f34be1b4 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 8 Dec 2023 05:35:46 -0800 Subject: [PATCH 068/207] Fix linting errors --- index.d.ts | 6 +++--- index.js | 8 ++++---- index.test.ts | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/index.d.ts b/index.d.ts index 1218cf68..6e72d369 100644 --- a/index.d.ts +++ b/index.d.ts @@ -69,7 +69,7 @@ declare module 'replicate' { export type Training = Prediction; - interface Page { + export interface Page { previous?: string; next?: string; results: T[]; @@ -80,13 +80,13 @@ declare module 'replicate' { auth?: string; userAgent?: string; baseUrl?: string; - fetch?: Function; + fetch?: (input: Request | string, init?: RequestInit) => Promise }); auth: string; userAgent?: string; baseUrl?: string; - fetch: Function; + fetch: (input: Request | string, init?: RequestInit) => Promise run( identifier: `${string}/${string}` | `${string}/${string}:${string}`, diff --git a/index.js b/index.js index 8736bb8f..3bd13a2b 100644 --- a/index.js +++ b/index.js @@ -188,9 +188,9 @@ class Replicate { data, } = options; - Object.entries(params).forEach(([key, value]) => { + for (const [key, value] of Object.entries(params)) { url.searchParams.append(key, value); - }); + } const headers = new Headers(); if (auth) { @@ -199,9 +199,9 @@ class Replicate { headers.append('Content-Type', 'application/json'); headers.append('User-Agent', userAgent); if (options.headers) { - options.headers.forEach((value, key) => { + for (const [key, value] of options.headers.entries()) { headers.append(key, value); - }); + } } const init = { diff --git a/index.test.ts b/index.test.ts index c5c30d59..5e03ca26 100644 --- a/index.test.ts +++ b/index.test.ts @@ -9,7 +9,7 @@ const BASE_URL = 'https://api.replicate.com/v1'; nock.disableNetConnect(); describe('Replicate client', () => { - let unmatched: Object[] = []; + let unmatched: any[] = []; const handleNoMatch = (req: unknown, options: any, body: string) => unmatched.push({ req, options, body }); From 0b69c4c8aeea32f722c51cd24057c09cd2c289f9 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 8 Dec 2023 05:46:45 -0800 Subject: [PATCH 069/207] Add format script --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 7656ee37..3bb36b6e 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ }, "scripts": { "check": "tsc", + "format": "biome format . --write", "lint": "biome lint .", "test": "jest" }, From 66d81afa121fb205cfbe46cfe7e2845183b1b237 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 8 Dec 2023 05:46:54 -0800 Subject: [PATCH 070/207] Apply automatic formatting --- index.d.ts | 45 ++- index.js | 106 +++--- index.test.ts | 840 +++++++++++++++++++++++---------------------- jest.config.js | 13 +- jsconfig.json | 26 +- lib/collections.js | 6 +- lib/deployments.js | 15 +- lib/error.js | 2 +- lib/hardware.js | 4 +- lib/identifier.js | 8 +- lib/models.js | 39 ++- lib/predictions.js | 14 +- lib/trainings.js | 21 +- lib/util.js | 14 +- 14 files changed, 612 insertions(+), 541 deletions(-) diff --git a/index.d.ts b/index.d.ts index 6e72d369..3b9d80c4 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,7 +1,7 @@ -declare module 'replicate' { - type Status = 'starting' | 'processing' | 'succeeded' | 'failed' | 'canceled'; - type Visibility = 'public' | 'private'; - type WebhookEventType = 'start' | 'output' | 'logs' | 'completed'; +declare module "replicate" { + type Status = "starting" | "processing" | "succeeded" | "failed" | "canceled"; + type Visibility = "public" | "private"; + type WebhookEventType = "start" | "output" | "logs" | "completed"; export interface ApiError extends Error { request: Request; @@ -17,7 +17,7 @@ declare module 'replicate' { export interface Hardware { sku: string; - name: string + name: string; } export interface Model { @@ -25,7 +25,7 @@ declare module 'replicate' { owner: string; name: string; description?: string; - visibility: 'public' | 'private'; + visibility: "public" | "private"; github_url?: string; paper_url?: string; license_url?: string; @@ -49,7 +49,7 @@ declare module 'replicate' { version: string; input: object; output?: any; - source: 'api' | 'web'; + source: "api" | "web"; error?: any; logs?: string; metrics?: { @@ -80,13 +80,16 @@ declare module 'replicate' { auth?: string; userAgent?: string; baseUrl?: string; - fetch?: (input: Request | string, init?: RequestInit) => Promise + fetch?: ( + input: Request | string, + init?: RequestInit + ) => Promise; }); auth: string; userAgent?: string; baseUrl?: string; - fetch: (input: Request | string, init?: RequestInit) => Promise + fetch: (input: Request | string, init?: RequestInit) => Promise; run( identifier: `${string}/${string}` | `${string}/${string}:${string}`, @@ -100,14 +103,17 @@ declare module 'replicate' { progress?: (prediction: Prediction) => void ): Promise; - request(route: string | URL, options: { - method?: string; - headers?: object | Headers; - params?: object; - data?: object; - }): Promise; + request( + route: string | URL, + options: { + method?: string; + headers?: object | Headers; + params?: object; + data?: object; + } + ): Promise; - paginate(endpoint: () => Promise>): AsyncGenerator<[ T ]>; + paginate(endpoint: () => Promise>): AsyncGenerator<[T]>; wait( prediction: Prediction, @@ -138,8 +144,8 @@ declare module 'replicate' { }; hardware: { - list(): Promise - } + list(): Promise; + }; models: { get(model_owner: string, model_name: string): Promise; @@ -155,7 +161,8 @@ declare module 'replicate' { paper_url?: string; license_url?: string; cover_image_url?: string; - }): Promise; + } + ): Promise; versions: { list(model_owner: string, model_name: string): Promise; get( diff --git a/index.js b/index.js index 3bd13a2b..b9082260 100644 --- a/index.js +++ b/index.js @@ -1,15 +1,15 @@ -const ApiError = require('./lib/error'); -const ModelVersionIdentifier = require('./lib/identifier'); -const { withAutomaticRetries } = require('./lib/util'); +const ApiError = require("./lib/error"); +const ModelVersionIdentifier = require("./lib/identifier"); +const { withAutomaticRetries } = require("./lib/util"); -const collections = require('./lib/collections'); -const deployments = require('./lib/deployments'); -const hardware = require('./lib/hardware'); -const models = require('./lib/models'); -const predictions = require('./lib/predictions'); -const trainings = require('./lib/trainings'); +const collections = require("./lib/collections"); +const deployments = require("./lib/deployments"); +const hardware = require("./lib/hardware"); +const models = require("./lib/models"); +const predictions = require("./lib/predictions"); +const trainings = require("./lib/trainings"); -const packageJSON = require('./package.json'); +const packageJSON = require("./package.json"); /** * Replicate API client library @@ -43,7 +43,7 @@ class Replicate { this.auth = options.auth || process.env.REPLICATE_API_TOKEN; this.userAgent = options.userAgent || `replicate-javascript/${packageJSON.version}`; - this.baseUrl = options.baseUrl || 'https://api.replicate.com/v1'; + this.baseUrl = options.baseUrl || "https://api.replicate.com/v1"; this.fetch = options.fetch || globalThis.fetch; this.collections = { @@ -54,7 +54,7 @@ class Replicate { this.deployments = { predictions: { create: deployments.predictions.create.bind(this), - } + }, }; this.hardware = { @@ -131,26 +131,30 @@ class Replicate { const { signal } = options; - prediction = await this.wait(prediction, wait || {}, async (updatedPrediction) => { - // Call progress callback with the updated prediction object - if (progress) { - progress(updatedPrediction); - } - - if (signal && signal.aborted) { - await this.predictions.cancel(updatedPrediction.id); - return true; // stop polling + prediction = await this.wait( + prediction, + wait || {}, + async (updatedPrediction) => { + // Call progress callback with the updated prediction object + if (progress) { + progress(updatedPrediction); + } + + if (signal && signal.aborted) { + await this.predictions.cancel(updatedPrediction.id); + return true; // stop polling + } + + return false; // continue polling } - - return false; // continue polling - }); + ); // Call progress callback with the completed prediction object if (progress) { progress(prediction); } - if (prediction.status === 'failed') { + if (prediction.status === "failed") { throw new Error(`Prediction failed: ${prediction.error}`); } @@ -177,16 +181,12 @@ class Replicate { url = route; } else { url = new URL( - route.startsWith('/') ? route.slice(1) : route, - baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/` + route.startsWith("/") ? route.slice(1) : route, + baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/` ); } - const { - method = 'GET', - params = {}, - data, - } = options; + const { method = "GET", params = {}, data } = options; for (const [key, value] of Object.entries(params)) { url.searchParams.append(key, value); @@ -194,10 +194,10 @@ class Replicate { const headers = new Headers(); if (auth) { - headers.append('Authorization', `Token ${auth}`); + headers.append("Authorization", `Token ${auth}`); } - headers.append('Content-Type', 'application/json'); - headers.append('User-Agent', userAgent); + headers.append("Content-Type", "application/json"); + headers.append("User-Agent", userAgent); if (options.headers) { for (const [key, value] of options.headers.entries()) { headers.append(key, value); @@ -210,14 +210,17 @@ class Replicate { body: data ? JSON.stringify(data) : undefined, }; - const shouldRetry = method === 'GET' ? - (response) => (response.status === 429 || response.status >= 500) : - (response) => (response.status === 429); + const shouldRetry = + method === "GET" + ? (response) => response.status === 429 || response.status >= 500 + : (response) => response.status === 429; // Workaround to fix `TypeError: Illegal invocation` error in Cloudflare Workers // https://github.com/replicate/replicate-javascript/issues/134 const _fetch = this.fetch; // eslint-disable-line no-underscore-dangle - const response = await withAutomaticRetries(async () => _fetch(url, init), { shouldRetry }); + const response = await withAutomaticRetries(async () => _fetch(url, init), { + shouldRetry, + }); if (!response.ok) { const request = new Request(url, init); @@ -225,7 +228,7 @@ class Replicate { throw new ApiError( `Request to ${url} failed with status ${response.status} ${response.statusText}: ${responseText}.`, request, - response, + response ); } @@ -243,11 +246,12 @@ class Replicate { * @param {Function} endpoint - Function that returns a promise for the next page of results * @yields {object[]} Each page of results */ - async * paginate(endpoint) { + async *paginate(endpoint) { const response = await endpoint(); yield response.results; if (response.next) { - const nextPage = () => this.request(response.next, { method: 'GET' }).then((r) => r.json()); + const nextPage = () => + this.request(response.next, { method: "GET" }).then((r) => r.json()); yield* this.paginate(nextPage); } } @@ -271,13 +275,13 @@ class Replicate { async wait(prediction, options, stop) { const { id } = prediction; if (!id) { - throw new Error('Invalid prediction'); + throw new Error("Invalid prediction"); } if ( - prediction.status === 'succeeded' || - prediction.status === 'failed' || - prediction.status === 'canceled' + prediction.status === "succeeded" || + prediction.status === "failed" || + prediction.status === "canceled" ) { return prediction; } @@ -290,12 +294,12 @@ class Replicate { let updatedPrediction = await this.predictions.get(id); while ( - updatedPrediction.status !== 'succeeded' && - updatedPrediction.status !== 'failed' && - updatedPrediction.status !== 'canceled' + updatedPrediction.status !== "succeeded" && + updatedPrediction.status !== "failed" && + updatedPrediction.status !== "canceled" ) { /* eslint-disable no-await-in-loop */ - if (stop && await stop(updatedPrediction) === true) { + if (stop && (await stop(updatedPrediction)) === true) { break; } @@ -304,7 +308,7 @@ class Replicate { /* eslint-enable no-await-in-loop */ } - if (updatedPrediction.status === 'failed') { + if (updatedPrediction.status === "failed") { throw new Error(`Prediction failed: ${updatedPrediction.error}`); } diff --git a/index.test.ts b/index.test.ts index 5e03ca26..98bff4ac 100644 --- a/index.test.ts +++ b/index.test.ts @@ -1,20 +1,20 @@ -import { expect, jest, test } from '@jest/globals'; -import Replicate, { ApiError, Model, Prediction } from 'replicate'; -import nock from 'nock'; -import fetch from 'cross-fetch'; +import { expect, jest, test } from "@jest/globals"; +import Replicate, { ApiError, Model, Prediction } from "replicate"; +import nock from "nock"; +import fetch from "cross-fetch"; let client: Replicate; -const BASE_URL = 'https://api.replicate.com/v1'; +const BASE_URL = "https://api.replicate.com/v1"; nock.disableNetConnect(); -describe('Replicate client', () => { +describe("Replicate client", () => { let unmatched: any[] = []; const handleNoMatch = (req: unknown, options: any, body: string) => unmatched.push({ req, options, body }); beforeEach(() => { - client = new Replicate({ auth: 'test-token' }); + client = new Replicate({ auth: "test-token" }); client.fetch = fetch; unmatched = []; @@ -29,59 +29,60 @@ describe('Replicate client', () => { nock.cleanAll(); }); - describe('constructor', () => { - test('Sets default baseUrl', () => { - expect(client.baseUrl).toBe('https://api.replicate.com/v1'); + describe("constructor", () => { + test("Sets default baseUrl", () => { + expect(client.baseUrl).toBe("https://api.replicate.com/v1"); }); - test('Sets custom baseUrl', () => { + test("Sets custom baseUrl", () => { const clientWithCustomBaseUrl = new Replicate({ - baseUrl: 'https://example.com/', - auth: 'test-token', + baseUrl: "https://example.com/", + auth: "test-token", }); - expect(clientWithCustomBaseUrl.baseUrl).toBe('https://example.com/'); + expect(clientWithCustomBaseUrl.baseUrl).toBe("https://example.com/"); }); - test('Sets custom userAgent', () => { + test("Sets custom userAgent", () => { const clientWithCustomUserAgent = new Replicate({ - userAgent: 'my-app/1.2.3', - auth: 'test-token', + userAgent: "my-app/1.2.3", + auth: "test-token", }); - expect(clientWithCustomUserAgent.userAgent).toBe('my-app/1.2.3'); + expect(clientWithCustomUserAgent.userAgent).toBe("my-app/1.2.3"); }); - test('Does not throw error if auth token is not provided', () => { - process.env.REPLICATE_API_TOKEN = 'test-token'; + test("Does not throw error if auth token is not provided", () => { + process.env.REPLICATE_API_TOKEN = "test-token"; expect(() => { const clientWithImplicitAuth = new Replicate(); - expect(clientWithImplicitAuth.auth).toBe('test-token'); + expect(clientWithImplicitAuth.auth).toBe("test-token"); }).not.toThrow(); }); - test('Does not throw error if blank auth token is provided', () => { + test("Does not throw error if blank auth token is provided", () => { expect(() => { new Replicate({ auth: "" }); }).not.toThrow(); }); }); - describe('collections.list', () => { - test('Calls the correct API route', async () => { + describe("collections.list", () => { + test("Calls the correct API route", async () => { nock(BASE_URL) - .get('/collections') + .get("/collections") .reply(200, { results: [ { - name: 'Super resolution', - slug: 'super-resolution', - description: 'Upscaling models that create high-quality images from low-quality images.', + name: "Super resolution", + slug: "super-resolution", + description: + "Upscaling models that create high-quality images from low-quality images.", }, { - name: 'Image classification', - slug: 'image-classification', - description: 'Models that classify images.', + name: "Image classification", + slug: "image-classification", + description: "Models that classify images.", }, ], next: null, @@ -94,54 +95,57 @@ describe('Replicate client', () => { // Add more tests for error handling, edge cases, etc. }); - describe('collections.get', () => { - test('Calls the correct API route', async () => { - nock(BASE_URL).get('/collections/super-resolution').reply(200, { - name: 'Super resolution', - slug: 'super-resolution', - description: 'Upscaling models that create high-quality images from low-quality images.', + describe("collections.get", () => { + test("Calls the correct API route", async () => { + nock(BASE_URL).get("/collections/super-resolution").reply(200, { + name: "Super resolution", + slug: "super-resolution", + description: + "Upscaling models that create high-quality images from low-quality images.", models: [], }); - const collection = await client.collections.get('super-resolution'); - expect(collection.name).toBe('Super resolution'); + const collection = await client.collections.get("super-resolution"); + expect(collection.name).toBe("Super resolution"); }); // Add more tests for error handling, edge cases, etc. }); - describe('models.get', () => { - test('Calls the correct API route', async () => { - nock(BASE_URL).get('/models/replicate/hello-world').reply(200, { - url: 'https://replicate.com/replicate/hello-world', - owner: 'replicate', - name: 'hello-world', - description: 'A tiny model that says hello', - visibility: 'public', - github_url: 'https://github.com/replicate/cog-examples', + describe("models.get", () => { + test("Calls the correct API route", async () => { + nock(BASE_URL).get("/models/replicate/hello-world").reply(200, { + url: "https://replicate.com/replicate/hello-world", + owner: "replicate", + name: "hello-world", + description: "A tiny model that says hello", + visibility: "public", + github_url: "https://github.com/replicate/cog-examples", paper_url: null, license_url: null, run_count: 12345, - cover_image_url: '', + cover_image_url: "", default_example: {}, latest_version: {}, }); - await client.models.get('replicate', 'hello-world'); + await client.models.get("replicate", "hello-world"); }); // Add more tests for error handling, edge cases, etc. }); - describe('models.list', () => { - test('Paginates results', async () => { + describe("models.list", () => { + test("Paginates results", async () => { nock(BASE_URL) - .get('/models') + .get("/models") .reply(200, { - results: [ { url: 'https://replicate.com/some-user/model-1' } ], - next: 'https://api.replicate.com/v1/models?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw', + results: [{ url: "https://replicate.com/some-user/model-1" }], + next: "https://api.replicate.com/v1/models?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw", }) - .get('/models?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw') + .get( + "/models?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw" + ) .reply(200, { - results: [ { url: 'https://replicate.com/some-user/model-2' } ], + results: [{ url: "https://replicate.com/some-user/model-2" }], next: null, }); @@ -149,32 +153,35 @@ describe('Replicate client', () => { for await (const batch of client.paginate(client.models.list)) { results.push(...batch); } - expect(results).toEqual([ { url: 'https://replicate.com/some-user/model-1' }, { url: 'https://replicate.com/some-user/model-2' } ]); + expect(results).toEqual([ + { url: "https://replicate.com/some-user/model-1" }, + { url: "https://replicate.com/some-user/model-2" }, + ]); // Add more tests for error handling, edge cases, etc. }); }); - describe('predictions.create', () => { - test('Calls the correct API route with the correct payload', async () => { + describe("predictions.create", () => { + test("Calls the correct API route with the correct payload", async () => { nock(BASE_URL) - .post('/predictions') + .post("/predictions") .reply(200, { - id: 'ufawqhfynnddngldkgtslldrkq', - model: 'replicate/hello-world', + id: "ufawqhfynnddngldkgtslldrkq", + model: "replicate/hello-world", version: - '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", urls: { - get: 'https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq', + get: "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", cancel: - 'https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel', + "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel", }, - created_at: '2022-04-26T22:13:06.224088Z', + created_at: "2022-04-26T22:13:06.224088Z", started_at: null, completed_at: null, - status: 'starting', + status: "starting", input: { - text: 'Alice', + text: "Alice", }, output: null, error: null, @@ -183,200 +190,220 @@ describe('Replicate client', () => { }); const prediction = await client.predictions.create({ version: - '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { - text: 'Alice', + text: "Alice", }, - webhook: 'http://test.host/webhook', - webhook_events_filter: [ 'output', 'completed' ], + webhook: "http://test.host/webhook", + webhook_events_filter: ["output", "completed"], }); - expect(prediction.id).toBe('ufawqhfynnddngldkgtslldrkq'); + expect(prediction.id).toBe("ufawqhfynnddngldkgtslldrkq"); }); - test('Passes stream parameter to API endpoint', async () => { + test("Passes stream parameter to API endpoint", async () => { nock(BASE_URL) - .post('/predictions') + .post("/predictions") .reply(201, (_uri, body) => { expect((body as any).stream).toBe(true); - return body - }) + return body; + }); await client.predictions.create({ version: - '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { - prompt: 'Tell me a story', + prompt: "Tell me a story", }, - stream: true + stream: true, }); }); - test('Throws an error if webhook URL is invalid', async () => { + test("Throws an error if webhook URL is invalid", async () => { await expect(async () => { await client.predictions.create({ - version: '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { - text: 'Alice', + text: "Alice", }, - webhook: 'invalid-url', + webhook: "invalid-url", }); - }).rejects.toThrow('Invalid webhook URL'); + }).rejects.toThrow("Invalid webhook URL"); }); - test('Throws an error with details failing response is JSON', async () => { - nock(BASE_URL) - .post('/predictions') - .reply(400, { + test("Throws an error with details failing response is JSON", async () => { + nock(BASE_URL).post("/predictions").reply( + 400, + { status: 400, detail: "Invalid input", - }, { "Content-Type": "application/json" }) + }, + { "Content-Type": "application/json" } + ); try { expect.hasAssertions(); await client.predictions.create({ - version: '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { text: null, }, }); } catch (error) { expect((error as ApiError).response.status).toBe(400); - expect((error as ApiError).message).toContain("Invalid input") + expect((error as ApiError).message).toContain("Invalid input"); } - }) + }); - test('Automatically retries on 429', async () => { + test("Automatically retries on 429", async () => { nock(BASE_URL) - .post('/predictions') - .reply(429, { - detail: "Too many requests", - }, { "Content-Type": "application/json", "Retry-After": "1" }) - .post('/predictions') + .post("/predictions") + .reply( + 429, + { + detail: "Too many requests", + }, + { "Content-Type": "application/json", "Retry-After": "1" } + ) + .post("/predictions") .reply(201, { - id: 'ufawqhfynnddngldkgtslldrkq', + id: "ufawqhfynnddngldkgtslldrkq", }); const prediction = await client.predictions.create({ version: - '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { - text: 'Alice', + text: "Alice", }, }); - expect(prediction.id).toBe('ufawqhfynnddngldkgtslldrkq'); + expect(prediction.id).toBe("ufawqhfynnddngldkgtslldrkq"); }); - test('Does not automatically retry on 500', async () => { - nock(BASE_URL) - .post('/predictions') - .reply(500, { + test("Does not automatically retry on 500", async () => { + nock(BASE_URL).post("/predictions").reply( + 500, + { detail: "Internal server error", - }, { "Content-Type": "application/json" }); + }, + { "Content-Type": "application/json" } + ); await expect( client.predictions.create({ version: - '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { - text: 'Alice', + text: "Alice", }, }) - ).rejects.toThrow(`Request to https://api.replicate.com/v1/predictions failed with status 500 Internal Server Error: {"detail":"Internal server error"}.`) + ).rejects.toThrow( + `Request to https://api.replicate.com/v1/predictions failed with status 500 Internal Server Error: {"detail":"Internal server error"}.` + ); }); }); - describe('predictions.get', () => { - test('Calls the correct API route with the correct payload', async () => { + describe("predictions.get", () => { + test("Calls the correct API route with the correct payload", async () => { nock(BASE_URL) - .get('/predictions/rrr4z55ocneqzikepnug6xezpe') + .get("/predictions/rrr4z55ocneqzikepnug6xezpe") .reply(200, { - id: 'rrr4z55ocneqzikepnug6xezpe', - model: 'stability-ai/stable-diffusion', + id: "rrr4z55ocneqzikepnug6xezpe", + model: "stability-ai/stable-diffusion", version: - 'be04660a5b93ef2aff61e3668dedb4cbeb14941e62a3fd5998364a32d613e35e', + "be04660a5b93ef2aff61e3668dedb4cbeb14941e62a3fd5998364a32d613e35e", urls: { - get: 'https://api.replicate.com/v1/predictions/rrr4z55ocneqzikepnug6xezpe', + get: "https://api.replicate.com/v1/predictions/rrr4z55ocneqzikepnug6xezpe", cancel: - 'https://api.replicate.com/v1/predictions/rrr4z55ocneqzikepnug6xezpe/cancel', + "https://api.replicate.com/v1/predictions/rrr4z55ocneqzikepnug6xezpe/cancel", }, - created_at: '2022-09-13T22:54:18.578761Z', - started_at: '2022-09-13T22:54:19.438525Z', - completed_at: '2022-09-13T22:54:23.236610Z', - source: 'api', - status: 'succeeded', + created_at: "2022-09-13T22:54:18.578761Z", + started_at: "2022-09-13T22:54:19.438525Z", + completed_at: "2022-09-13T22:54:23.236610Z", + source: "api", + status: "succeeded", input: { - prompt: 'oak tree with boletus growing on its branches', + prompt: "oak tree with boletus growing on its branches", }, output: [ - 'https://replicate.com/api/models/stability-ai/stable-diffusion/files/9c3b6fe4-2d37-4571-a17a-83951b1cb120/out-0.png', + "https://replicate.com/api/models/stability-ai/stable-diffusion/files/9c3b6fe4-2d37-4571-a17a-83951b1cb120/out-0.png", ], error: null, - logs: 'Using seed: 36941...', + logs: "Using seed: 36941...", metrics: { predict_time: 4.484541, }, }); const prediction = await client.predictions.get( - 'rrr4z55ocneqzikepnug6xezpe' + "rrr4z55ocneqzikepnug6xezpe" ); - expect(prediction.id).toBe('rrr4z55ocneqzikepnug6xezpe'); + expect(prediction.id).toBe("rrr4z55ocneqzikepnug6xezpe"); }); - test('Automatically retries on 429', async () => { + test("Automatically retries on 429", async () => { nock(BASE_URL) - .get('/predictions/rrr4z55ocneqzikepnug6xezpe') - .reply(429, { - detail: "Too many requests", - }, { "Content-Type": "application/json", "Retry-After": "1" }) - .get('/predictions/rrr4z55ocneqzikepnug6xezpe') + .get("/predictions/rrr4z55ocneqzikepnug6xezpe") + .reply( + 429, + { + detail: "Too many requests", + }, + { "Content-Type": "application/json", "Retry-After": "1" } + ) + .get("/predictions/rrr4z55ocneqzikepnug6xezpe") .reply(200, { - id: 'rrr4z55ocneqzikepnug6xezpe', + id: "rrr4z55ocneqzikepnug6xezpe", }); const prediction = await client.predictions.get( - 'rrr4z55ocneqzikepnug6xezpe' + "rrr4z55ocneqzikepnug6xezpe" ); - expect(prediction.id).toBe('rrr4z55ocneqzikepnug6xezpe'); + expect(prediction.id).toBe("rrr4z55ocneqzikepnug6xezpe"); }); - test('Automatically retries on 500', async () => { + test("Automatically retries on 500", async () => { nock(BASE_URL) - .get('/predictions/rrr4z55ocneqzikepnug6xezpe') - .reply(500, { - detail: "Internal server error", - }, { "Content-Type": "application/json" }) - .get('/predictions/rrr4z55ocneqzikepnug6xezpe') + .get("/predictions/rrr4z55ocneqzikepnug6xezpe") + .reply( + 500, + { + detail: "Internal server error", + }, + { "Content-Type": "application/json" } + ) + .get("/predictions/rrr4z55ocneqzikepnug6xezpe") .reply(200, { - id: 'rrr4z55ocneqzikepnug6xezpe', + id: "rrr4z55ocneqzikepnug6xezpe", }); const prediction = await client.predictions.get( - 'rrr4z55ocneqzikepnug6xezpe' + "rrr4z55ocneqzikepnug6xezpe" ); - expect(prediction.id).toBe('rrr4z55ocneqzikepnug6xezpe'); + expect(prediction.id).toBe("rrr4z55ocneqzikepnug6xezpe"); }); }); - describe('predictions.cancel', () => { - test('Calls the correct API route with the correct payload', async () => { + describe("predictions.cancel", () => { + test("Calls the correct API route with the correct payload", async () => { nock(BASE_URL) - .post('/predictions/ufawqhfynnddngldkgtslldrkq/cancel') + .post("/predictions/ufawqhfynnddngldkgtslldrkq/cancel") .reply(200, { - id: 'ufawqhfynnddngldkgtslldrkq', - model: 'replicate/hello-world', + id: "ufawqhfynnddngldkgtslldrkq", + model: "replicate/hello-world", version: - '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", urls: { - get: 'https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq', + get: "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", cancel: - 'https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel', + "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel", }, - created_at: '2022-04-26T22:13:06.224088Z', - started_at: '2022-04-26T22:13:06.224088Z', - completed_at: '2022-04-26T22:14:06.224088Z', - status: 'canceled', + created_at: "2022-04-26T22:13:06.224088Z", + started_at: "2022-04-26T22:13:06.224088Z", + completed_at: "2022-04-26T22:14:06.224088Z", + status: "canceled", input: { - text: 'Alice', + text: "Alice", }, output: null, error: null, @@ -385,58 +412,58 @@ describe('Replicate client', () => { }); const prediction = await client.predictions.cancel( - 'ufawqhfynnddngldkgtslldrkq' + "ufawqhfynnddngldkgtslldrkq" ); - expect(prediction.status).toBe('canceled'); + expect(prediction.status).toBe("canceled"); }); // Add more tests for error handling, edge cases, etc. }); - describe('predictions.list', () => { - test('Calls the correct API route with the correct payload', async () => { + describe("predictions.list", () => { + test("Calls the correct API route with the correct payload", async () => { nock(BASE_URL) - .get('/predictions') + .get("/predictions") .reply(200, { - next: 'https://api.replicate.com/v1/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw', + next: "https://api.replicate.com/v1/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw", previous: null, results: [ { - id: 'jpzd7hm5gfcapbfyt4mqytarku', - model: 'stability-ai/stable-diffusion', + id: "jpzd7hm5gfcapbfyt4mqytarku", + model: "stability-ai/stable-diffusion", version: - 'b21cbe271e65c1718f2999b038c18b45e21e4fba961181fbfae9342fc53b9e05', + "b21cbe271e65c1718f2999b038c18b45e21e4fba961181fbfae9342fc53b9e05", urls: { - get: 'https://api.replicate.com/v1/predictions/jpzd7hm5gfcapbfyt4mqytarku', + get: "https://api.replicate.com/v1/predictions/jpzd7hm5gfcapbfyt4mqytarku", cancel: - 'https://api.replicate.com/v1/predictions/jpzd7hm5gfcapbfyt4mqytarku/cancel', + "https://api.replicate.com/v1/predictions/jpzd7hm5gfcapbfyt4mqytarku/cancel", }, - created_at: '2022-04-26T20:00:40.658234Z', - started_at: '2022-04-26T20:00:84.583803Z', - completed_at: '2022-04-26T20:02:27.648305Z', - source: 'web', - status: 'succeeded', + created_at: "2022-04-26T20:00:40.658234Z", + started_at: "2022-04-26T20:00:84.583803Z", + completed_at: "2022-04-26T20:02:27.648305Z", + source: "web", + status: "succeeded", }, ], }); const predictions = await client.predictions.list(); expect(predictions.results.length).toBe(1); - expect(predictions.results[ 0 ].id).toBe('jpzd7hm5gfcapbfyt4mqytarku'); + expect(predictions.results[0].id).toBe("jpzd7hm5gfcapbfyt4mqytarku"); }); - test('Paginates results', async () => { + test("Paginates results", async () => { nock(BASE_URL) - .get('/predictions') + .get("/predictions") .reply(200, { - results: [ { id: 'ufawqhfynnddngldkgtslldrkq' } ], - next: 'https://api.replicate.com/v1/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw', + results: [{ id: "ufawqhfynnddngldkgtslldrkq" }], + next: "https://api.replicate.com/v1/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw", }) .get( - '/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw' + "/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw" ) .reply(200, { - results: [ { id: 'rrr4z55ocneqzikepnug6xezpe' } ], + results: [{ id: "rrr4z55ocneqzikepnug6xezpe" }], next: null, }); @@ -445,175 +472,176 @@ describe('Replicate client', () => { results.push(...batch); } expect(results).toEqual([ - { id: 'ufawqhfynnddngldkgtslldrkq' }, - { id: 'rrr4z55ocneqzikepnug6xezpe' }, + { id: "ufawqhfynnddngldkgtslldrkq" }, + { id: "rrr4z55ocneqzikepnug6xezpe" }, ]); // Add more tests for error handling, edge cases, etc. }); }); - describe('trainings.create', () => { - test('Calls the correct API route with the correct payload', async () => { + describe("trainings.create", () => { + test("Calls the correct API route with the correct payload", async () => { nock(BASE_URL) .post( - '/models/owner/model/versions/632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532/trainings' + "/models/owner/model/versions/632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532/trainings" ) .reply(200, { - id: 'zz4ibbonubfz7carwiefibzgga', - version: '632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532', - status: 'starting', + id: "zz4ibbonubfz7carwiefibzgga", + version: + "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", + status: "starting", input: { - text: '...', + text: "...", }, output: null, error: null, logs: null, started_at: null, - created_at: '2023-03-28T21:47:58.566434Z', + created_at: "2023-03-28T21:47:58.566434Z", completed_at: null, }); const training = await client.trainings.create( - 'owner', - 'model', - '632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532', + "owner", + "model", + "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", { - destination: 'new_owner/new_model', + destination: "new_owner/new_model", input: { - text: '...', + text: "...", }, } ); - expect(training.id).toBe('zz4ibbonubfz7carwiefibzgga'); + expect(training.id).toBe("zz4ibbonubfz7carwiefibzgga"); }); - test('Throws an error if webhook is not a valid URL', async () => { + test("Throws an error if webhook is not a valid URL", async () => { await expect( client.trainings.create( - 'owner', - 'model', - '632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532', + "owner", + "model", + "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", { - destination: 'new_owner/new_model', + destination: "new_owner/new_model", input: { - text: '...', + text: "...", }, - webhook: 'invalid-url', + webhook: "invalid-url", } ) - ).rejects.toThrow('Invalid webhook URL'); + ).rejects.toThrow("Invalid webhook URL"); }); // Add more tests for error handling, edge cases, etc. }); - describe('trainings.get', () => { - test('Calls the correct API route with the correct payload', async () => { + describe("trainings.get", () => { + test("Calls the correct API route with the correct payload", async () => { nock(BASE_URL) - .get('/trainings/zz4ibbonubfz7carwiefibzgga') + .get("/trainings/zz4ibbonubfz7carwiefibzgga") .reply(200, { - id: 'zz4ibbonubfz7carwiefibzgga', - version: '{version}', - status: 'succeeded', + id: "zz4ibbonubfz7carwiefibzgga", + version: "{version}", + status: "succeeded", input: { - data: '...', - param1: '...', + data: "...", + param1: "...", }, output: { - version: '...', + version: "...", }, error: null, logs: null, webhook_completed: null, started_at: null, - created_at: '2023-03-28T21:47:58.566434Z', + created_at: "2023-03-28T21:47:58.566434Z", completed_at: null, }); - const training = await client.trainings.get('zz4ibbonubfz7carwiefibzgga'); - expect(training.status).toBe('succeeded'); + const training = await client.trainings.get("zz4ibbonubfz7carwiefibzgga"); + expect(training.status).toBe("succeeded"); }); // Add more tests for error handling, edge cases, etc. }); - describe('trainings.cancel', () => { - test('Calls the correct API route with the correct payload', async () => { + describe("trainings.cancel", () => { + test("Calls the correct API route with the correct payload", async () => { nock(BASE_URL) - .post('/trainings/zz4ibbonubfz7carwiefibzgga/cancel') + .post("/trainings/zz4ibbonubfz7carwiefibzgga/cancel") .reply(200, { - id: 'zz4ibbonubfz7carwiefibzgga', - version: '{version}', - status: 'canceled', + id: "zz4ibbonubfz7carwiefibzgga", + version: "{version}", + status: "canceled", input: { - data: '...', - param1: '...', + data: "...", + param1: "...", }, output: { - version: '...', + version: "...", }, error: null, logs: null, webhook_completed: null, started_at: null, - created_at: '2023-03-28T21:47:58.566434Z', + created_at: "2023-03-28T21:47:58.566434Z", completed_at: null, }); const training = await client.trainings.cancel( - 'zz4ibbonubfz7carwiefibzgga' + "zz4ibbonubfz7carwiefibzgga" ); - expect(training.status).toBe('canceled'); + expect(training.status).toBe("canceled"); }); // Add more tests for error handling, edge cases, etc. }); - describe('trainings.list', () => { - test('Calls the correct API route with the correct payload', async () => { + describe("trainings.list", () => { + test("Calls the correct API route with the correct payload", async () => { nock(BASE_URL) - .get('/trainings') + .get("/trainings") .reply(200, { - next: 'https://api.replicate.com/v1/trainings?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw', + next: "https://api.replicate.com/v1/trainings?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw", previous: null, results: [ { - id: 'jpzd7hm5gfcapbfyt4mqytarku', - model: 'stability-ai/sdxl', + id: "jpzd7hm5gfcapbfyt4mqytarku", + model: "stability-ai/sdxl", version: - 'b21cbe271e65c1718f2999b038c18b45e21e4fba961181fbfae9342fc53b9e05', + "b21cbe271e65c1718f2999b038c18b45e21e4fba961181fbfae9342fc53b9e05", urls: { - get: 'https://api.replicate.com/v1/trainings/jpzd7hm5gfcapbfyt4mqytarku', + get: "https://api.replicate.com/v1/trainings/jpzd7hm5gfcapbfyt4mqytarku", cancel: - 'https://api.replicate.com/v1/trainings/jpzd7hm5gfcapbfyt4mqytarku/cancel', + "https://api.replicate.com/v1/trainings/jpzd7hm5gfcapbfyt4mqytarku/cancel", }, - created_at: '2022-04-26T20:00:40.658234Z', - started_at: '2022-04-26T20:00:84.583803Z', - completed_at: '2022-04-26T20:02:27.648305Z', - source: 'web', - status: 'succeeded', + created_at: "2022-04-26T20:00:40.658234Z", + started_at: "2022-04-26T20:00:84.583803Z", + completed_at: "2022-04-26T20:02:27.648305Z", + source: "web", + status: "succeeded", }, ], }); const trainings = await client.trainings.list(); expect(trainings.results.length).toBe(1); - expect(trainings.results[ 0 ].id).toBe('jpzd7hm5gfcapbfyt4mqytarku'); + expect(trainings.results[0].id).toBe("jpzd7hm5gfcapbfyt4mqytarku"); }); - test('Paginates results', async () => { + test("Paginates results", async () => { nock(BASE_URL) - .get('/trainings') + .get("/trainings") .reply(200, { - results: [ { id: 'ufawqhfynnddngldkgtslldrkq' } ], - next: 'https://api.replicate.com/v1/trainings?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw', + results: [{ id: "ufawqhfynnddngldkgtslldrkq" }], + next: "https://api.replicate.com/v1/trainings?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw", }) .get( - '/trainings?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw' + "/trainings?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw" ) .reply(200, { - results: [ { id: 'rrr4z55ocneqzikepnug6xezpe' } ], + results: [{ id: "rrr4z55ocneqzikepnug6xezpe" }], next: null, }); @@ -622,88 +650,97 @@ describe('Replicate client', () => { results.push(...batch); } expect(results).toEqual([ - { id: 'ufawqhfynnddngldkgtslldrkq' }, - { id: 'rrr4z55ocneqzikepnug6xezpe' }, + { id: "ufawqhfynnddngldkgtslldrkq" }, + { id: "rrr4z55ocneqzikepnug6xezpe" }, ]); // Add more tests for error handling, edge cases, etc. }); }); - describe('deployments.predictions.create', () => { - test('Calls the correct API route with the correct payload', async () => { + describe("deployments.predictions.create", () => { + test("Calls the correct API route with the correct payload", async () => { nock(BASE_URL) - .post('/deployments/replicate/greeter/predictions') + .post("/deployments/replicate/greeter/predictions") .reply(200, { - id: 'mfrgcyzzme2wkmbwgzrgmntcg', - model: 'replicate/hello-world', + id: "mfrgcyzzme2wkmbwgzrgmntcg", + model: "replicate/hello-world", version: - '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", urls: { - get: 'https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq', + get: "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", cancel: - 'https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel', + "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel", }, - created_at: '2022-09-10T09:44:22.165836Z', + created_at: "2022-09-10T09:44:22.165836Z", started_at: null, completed_at: null, - status: 'starting', + status: "starting", input: { - text: 'Alice', + text: "Alice", }, output: null, error: null, logs: null, metrics: {}, }); - const prediction = await client.deployments.predictions.create("replicate", "greeter", { - input: { - text: 'Alice', - }, - webhook: 'http://test.host/webhook', - webhook_events_filter: [ 'output', 'completed' ], - }); - expect(prediction.id).toBe('mfrgcyzzme2wkmbwgzrgmntcg'); + const prediction = await client.deployments.predictions.create( + "replicate", + "greeter", + { + input: { + text: "Alice", + }, + webhook: "http://test.host/webhook", + webhook_events_filter: ["output", "completed"], + } + ); + expect(prediction.id).toBe("mfrgcyzzme2wkmbwgzrgmntcg"); }); // Add more tests for error handling, edge cases, etc. }); - describe('models.predictions.create', () => { - test('Calls the correct API route with the correct payload', async () => { + describe("models.predictions.create", () => { + test("Calls the correct API route with the correct payload", async () => { nock(BASE_URL) - .post('/models/meta/llama-2-70b-chat/predictions') + .post("/models/meta/llama-2-70b-chat/predictions") .reply(200, { id: "heat2o3bzn3ahtr6bjfftvbaci", model: "replicate/lifeboat-70b", version: "d-c6559c5791b50af57b69f4a73f8e021c", input: { - prompt: "Please write a haiku about llamas." + prompt: "Please write a haiku about llamas.", }, logs: "", error: null, status: "starting", created_at: "2023-11-27T13:35:45.99397566Z", urls: { - cancel: "https://api.replicate.com/v1/predictions/heat2o3bzn3ahtr6bjfftvbaci/cancel", - get: "https://api.replicate.com/v1/predictions/heat2o3bzn3ahtr6bjfftvbaci" - } + cancel: + "https://api.replicate.com/v1/predictions/heat2o3bzn3ahtr6bjfftvbaci/cancel", + get: "https://api.replicate.com/v1/predictions/heat2o3bzn3ahtr6bjfftvbaci", + }, }); - const prediction = await client.models.predictions.create("meta", "llama-2-70b-chat", { - input: { - prompt: "Please write a haiku about llamas." - }, - webhook: 'http://test.host/webhook', - webhook_events_filter: [ 'output', 'completed' ], - }); - expect(prediction.id).toBe('heat2o3bzn3ahtr6bjfftvbaci'); + const prediction = await client.models.predictions.create( + "meta", + "llama-2-70b-chat", + { + input: { + prompt: "Please write a haiku about llamas.", + }, + webhook: "http://test.host/webhook", + webhook_events_filter: ["output", "completed"], + } + ); + expect(prediction.id).toBe("heat2o3bzn3ahtr6bjfftvbaci"); }); // Add more tests for error handling, edge cases, etc. }); - describe('hardware.list', () => { - test('Calls the correct API route', async () => { + describe("hardware.list", () => { + test("Calls the correct API route", async () => { nock(BASE_URL) - .get('/hardware') + .get("/hardware") .reply(200, [ { name: "CPU", sku: "cpu" }, { name: "Nvidia T4 GPU", sku: "gpu-t4" }, @@ -713,233 +750,232 @@ describe('Replicate client', () => { const hardware = await client.hardware.list(); expect(hardware.length).toBe(4); - expect(hardware[ 0 ].name).toBe('CPU'); - expect(hardware[ 0 ].sku).toBe('cpu'); + expect(hardware[0].name).toBe("CPU"); + expect(hardware[0].sku).toBe("cpu"); }); // Add more tests for error handling, edge cases, etc. }); - describe('models.create', () => { - test('Calls the correct API route with the correct payload', async () => { - nock(BASE_URL) - .post('/models') - .reply(200, { - owner: 'test-owner', - name: 'test-model', - visibility: 'public', - hardware: 'cpu', - description: 'A test model', - }); + describe("models.create", () => { + test("Calls the correct API route with the correct payload", async () => { + nock(BASE_URL).post("/models").reply(200, { + owner: "test-owner", + name: "test-model", + visibility: "public", + hardware: "cpu", + description: "A test model", + }); - const model = await client.models.create( - 'test-owner', - 'test-model', - { - visibility: 'public', - hardware: 'cpu', - description: 'A test model', - }); + const model = await client.models.create("test-owner", "test-model", { + visibility: "public", + hardware: "cpu", + description: "A test model", + }); - expect(model.owner).toBe('test-owner'); - expect(model.name).toBe('test-model'); - expect(model.visibility).toBe('public'); + expect(model.owner).toBe("test-owner"); + expect(model.name).toBe("test-model"); + expect(model.visibility).toBe("public"); // expect(model.hardware).toBe('cpu'); - expect(model.description).toBe('A test model'); + expect(model.description).toBe("A test model"); }); }); - describe('run', () => { - test('Calls the correct API routes for a version', async () => { + describe("run", () => { + test("Calls the correct API routes for a version", async () => { const firstPollingRequest = true; nock(BASE_URL) - .post('/predictions') + .post("/predictions") .reply(201, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'starting', + id: "ufawqhfynnddngldkgtslldrkq", + status: "starting", }) - .get('/predictions/ufawqhfynnddngldkgtslldrkq') + .get("/predictions/ufawqhfynnddngldkgtslldrkq") .twice() .reply(200, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'processing', + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", }) - .get('/predictions/ufawqhfynnddngldkgtslldrkq') + .get("/predictions/ufawqhfynnddngldkgtslldrkq") .reply(200, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'succeeded', - output: 'Goodbye!', + id: "ufawqhfynnddngldkgtslldrkq", + status: "succeeded", + output: "Goodbye!", }); const progress = jest.fn(); const output = await client.run( - 'owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", { - input: { text: 'Hello, world!' }, - wait: { interval: 1 } + input: { text: "Hello, world!" }, + wait: { interval: 1 }, }, progress ); - expect(output).toBe('Goodbye!'); + expect(output).toBe("Goodbye!"); expect(progress).toHaveBeenNthCalledWith(1, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'starting', + id: "ufawqhfynnddngldkgtslldrkq", + status: "starting", }); expect(progress).toHaveBeenNthCalledWith(2, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'processing', + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", }); expect(progress).toHaveBeenNthCalledWith(3, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'processing', + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", }); expect(progress).toHaveBeenNthCalledWith(4, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'succeeded', - output: 'Goodbye!', + id: "ufawqhfynnddngldkgtslldrkq", + status: "succeeded", + output: "Goodbye!", }); expect(progress).toHaveBeenCalledTimes(4); }); - test('Calls the correct API routes for a model', async () => { + test("Calls the correct API routes for a model", async () => { const firstPollingRequest = true; nock(BASE_URL) - .post('/models/replicate/hello-world/predictions') + .post("/models/replicate/hello-world/predictions") .reply(201, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'starting', + id: "ufawqhfynnddngldkgtslldrkq", + status: "starting", }) - .get('/predictions/ufawqhfynnddngldkgtslldrkq') + .get("/predictions/ufawqhfynnddngldkgtslldrkq") .twice() .reply(200, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'processing', + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", }) - .get('/predictions/ufawqhfynnddngldkgtslldrkq') + .get("/predictions/ufawqhfynnddngldkgtslldrkq") .reply(200, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'succeeded', - output: 'Goodbye!', + id: "ufawqhfynnddngldkgtslldrkq", + status: "succeeded", + output: "Goodbye!", }); const progress = jest.fn(); const output = await client.run( - 'replicate/hello-world', + "replicate/hello-world", { - input: { text: 'Hello, world!' }, - wait: { interval: 1 } + input: { text: "Hello, world!" }, + wait: { interval: 1 }, }, progress ); - expect(output).toBe('Goodbye!'); + expect(output).toBe("Goodbye!"); expect(progress).toHaveBeenNthCalledWith(1, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'starting', + id: "ufawqhfynnddngldkgtslldrkq", + status: "starting", }); expect(progress).toHaveBeenNthCalledWith(2, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'processing', + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", }); expect(progress).toHaveBeenNthCalledWith(3, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'processing', + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", }); expect(progress).toHaveBeenNthCalledWith(4, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'succeeded', - output: 'Goodbye!', + id: "ufawqhfynnddngldkgtslldrkq", + status: "succeeded", + output: "Goodbye!", }); expect(progress).toHaveBeenCalledTimes(4); }); - test('Does not throw an error for identifier containing hyphen and full stop', async () => { + test("Does not throw an error for identifier containing hyphen and full stop", async () => { nock(BASE_URL) - .post('/predictions') + .post("/predictions") .reply(200, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'processing', + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", }) - .get('/predictions/ufawqhfynnddngldkgtslldrkq') + .get("/predictions/ufawqhfynnddngldkgtslldrkq") .reply(200, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'succeeded', - output: 'foobar', + id: "ufawqhfynnddngldkgtslldrkq", + status: "succeeded", + output: "foobar", }); - await expect(client.run('a/b-1.0:abc123', { input: { text: 'Hello, world!' } })).resolves.not.toThrow(); + await expect( + client.run("a/b-1.0:abc123", { input: { text: "Hello, world!" } }) + ).resolves.not.toThrow(); }); - test('Throws an error for invalid identifiers', async () => { - const options = { input: { text: 'Hello, world!' } } + test("Throws an error for invalid identifiers", async () => { + const options = { input: { text: "Hello, world!" } }; // @ts-expect-error - await expect(client.run('owner:abc123', options)).rejects.toThrow(); + await expect(client.run("owner:abc123", options)).rejects.toThrow(); - await expect(client.run('/model:abc123', options)).rejects.toThrow(); + await expect(client.run("/model:abc123", options)).rejects.toThrow(); // @ts-expect-error - await expect(client.run(':abc123', options)).rejects.toThrow(); + await expect(client.run(":abc123", options)).rejects.toThrow(); }); - test('Throws an error if webhook URL is invalid', async () => { + test("Throws an error if webhook URL is invalid", async () => { await expect(async () => { await client.run( - 'owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', { - input: { - text: 'Alice', - }, - webhook: 'invalid-url', - }); - }).rejects.toThrow('Invalid webhook URL'); + "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + { + input: { + text: "Alice", + }, + webhook: "invalid-url", + } + ); + }).rejects.toThrow("Invalid webhook URL"); }); - test('Aborts the operation when abort signal is invoked', async () => { + test("Aborts the operation when abort signal is invoked", async () => { const controller = new AbortController(); const { signal } = controller; const scope = nock(BASE_URL) - .post('/predictions', (body) => { + .post("/predictions", (body) => { controller.abort(); return body; }) .reply(201, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'processing', + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", }) .persist() - .get('/predictions/ufawqhfynnddngldkgtslldrkq') + .get("/predictions/ufawqhfynnddngldkgtslldrkq") .reply(200, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'processing', + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", }) - .post('/predictions/ufawqhfynnddngldkgtslldrkq/cancel') + .post("/predictions/ufawqhfynnddngldkgtslldrkq/cancel") .reply(200, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'canceled', - });; + id: "ufawqhfynnddngldkgtslldrkq", + status: "canceled", + }); await client.run( - 'owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", { - input: { text: 'Hello, world!' }, + input: { text: "Hello, world!" }, signal, } - ) + ); expect(signal.aborted).toBe(true); diff --git a/jest.config.js b/jest.config.js index 36d66b5b..821539fb 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,11 +1,14 @@ // eslint-disable-next-line jsdoc/valid-types /** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', + preset: "ts-jest", + testEnvironment: "node", transform: { - '^.+\\.ts?$': ['ts-jest', { - tsconfig: 'tsconfig.json' - }], + "^.+\\.ts?$": [ + "ts-jest", + { + tsconfig: "tsconfig.json", + }, + ], }, }; diff --git a/jsconfig.json b/jsconfig.json index 6ea54689..b83b3f36 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,15 +1,15 @@ { - "compilerOptions": { - "checkJs": true, - "module": "ESNext", - "moduleResolution": "Node", - "target": "ES2020", - "resolveJsonModule": true, - "strictNullChecks": true, - "strictFunctionTypes": true - }, - "exclude": [ - "node_modules", - "**/node_modules/*" - ] + "compilerOptions": { + "checkJs": true, + "module": "ESNext", + "moduleResolution": "Node", + "target": "ES2020", + "resolveJsonModule": true, + "strictNullChecks": true, + "strictFunctionTypes": true + }, + "exclude": [ + "node_modules", + "**/node_modules/*" + ] } diff --git a/lib/collections.js b/lib/collections.js index b40d0913..9332aaab 100644 --- a/lib/collections.js +++ b/lib/collections.js @@ -6,7 +6,7 @@ */ async function getCollection(collection_slug) { const response = await this.request(`/collections/${collection_slug}`, { - method: 'GET', + method: "GET", }); return response.json(); @@ -18,8 +18,8 @@ async function getCollection(collection_slug) { * @returns {Promise} - Resolves with the collections data */ async function listCollections() { - const response = await this.request('/collections', { - method: 'GET', + const response = await this.request("/collections", { + method: "GET", }); return response.json(); diff --git a/lib/deployments.js b/lib/deployments.js index 4682c9bd..6f32cdba 100644 --- a/lib/deployments.js +++ b/lib/deployments.js @@ -18,14 +18,17 @@ async function createPrediction(deployment_owner, deployment_name, options) { // eslint-disable-next-line no-new new URL(data.webhook); } catch (err) { - throw new Error('Invalid webhook URL'); + throw new Error("Invalid webhook URL"); } } - const response = await this.request(`/deployments/${deployment_owner}/${deployment_name}/predictions`, { - method: 'POST', - data: { ...data, stream }, - }); + const response = await this.request( + `/deployments/${deployment_owner}/${deployment_name}/predictions`, + { + method: "POST", + data: { ...data, stream }, + } + ); return response.json(); } @@ -33,5 +36,5 @@ async function createPrediction(deployment_owner, deployment_name, options) { module.exports = { predictions: { create: createPrediction, - } + }, }; diff --git a/lib/error.js b/lib/error.js index 81dbe473..cf05cd12 100644 --- a/lib/error.js +++ b/lib/error.js @@ -12,7 +12,7 @@ class ApiError extends Error { */ constructor(message, request, response) { super(message); - this.name = 'ApiError'; + this.name = "ApiError"; this.request = request; this.response = response; } diff --git a/lib/hardware.js b/lib/hardware.js index 487f3b88..d717548f 100644 --- a/lib/hardware.js +++ b/lib/hardware.js @@ -4,8 +4,8 @@ * @returns {Promise} Resolves with the array of hardware */ async function listHardware() { - const response = await this.request('/hardware', { - method: 'GET', + const response = await this.request("/hardware", { + method: "GET", }); return response.json(); diff --git a/lib/identifier.js b/lib/identifier.js index 07e21d1e..86e23ee4 100644 --- a/lib/identifier.js +++ b/lib/identifier.js @@ -21,9 +21,13 @@ class ModelVersionIdentifier { * @throws {Error} If the reference is invalid. */ static parse(ref) { - const match = ref.match(/^(?[^/]+)\/(?[^/:]+)(:(?.+))?$/); + const match = ref.match( + /^(?[^/]+)\/(?[^/:]+)(:(?.+))?$/ + ); if (!match) { - throw new Error(`Invalid reference to model version: ${ref}. Expected format: owner/name or owner/name:version`); + throw new Error( + `Invalid reference to model version: ${ref}. Expected format: owner/name or owner/name:version` + ); } const { owner, name, version } = match.groups; diff --git a/lib/models.js b/lib/models.js index 12419fc2..cb32e9e0 100644 --- a/lib/models.js +++ b/lib/models.js @@ -7,7 +7,7 @@ */ async function getModel(model_owner, model_name) { const response = await this.request(`/models/${model_owner}/${model_name}`, { - method: 'GET', + method: "GET", }); return response.json(); @@ -21,9 +21,12 @@ async function getModel(model_owner, model_name) { * @returns {Promise} Resolves with the list of model versions */ async function listModelVersions(model_owner, model_name) { - const response = await this.request(`/models/${model_owner}/${model_name}/versions`, { - method: 'GET', - }); + const response = await this.request( + `/models/${model_owner}/${model_name}/versions`, + { + method: "GET", + } + ); return response.json(); } @@ -37,9 +40,12 @@ async function listModelVersions(model_owner, model_name) { * @returns {Promise} Resolves with the model version data */ async function getModelVersion(model_owner, model_name, version_id) { - const response = await this.request(`/models/${model_owner}/${model_name}/versions/${version_id}`, { - method: 'GET', - }); + const response = await this.request( + `/models/${model_owner}/${model_name}/versions/${version_id}`, + { + method: "GET", + } + ); return response.json(); } @@ -50,8 +56,8 @@ async function getModelVersion(model_owner, model_name, version_id) { * @returns {Promise} Resolves with the model version data */ async function listModels() { - const response = await this.request('/models', { - method: 'GET', + const response = await this.request("/models", { + method: "GET", }); return response.json(); @@ -75,8 +81,8 @@ async function listModels() { async function createModel(model_owner, model_name, options) { const data = { owner: model_owner, name: model_name, ...options }; - const response = await this.request('/models', { - method: 'POST', + const response = await this.request("/models", { + method: "POST", data, }); @@ -98,10 +104,13 @@ async function createModel(model_owner, model_name, options) { async function createPrediction(model_owner, model_name, options) { const { stream, ...data } = options; - const response = await this.request(`/models/${model_owner}/${model_name}/predictions`, { - method: 'POST', - data: { ...data, stream }, - }); + const response = await this.request( + `/models/${model_owner}/${model_name}/predictions`, + { + method: "POST", + data: { ...data, stream }, + } + ); return response.json(); } diff --git a/lib/predictions.js b/lib/predictions.js index 32b18ade..b1f7fe59 100644 --- a/lib/predictions.js +++ b/lib/predictions.js @@ -17,12 +17,12 @@ async function createPrediction(options) { // eslint-disable-next-line no-new new URL(data.webhook); } catch (err) { - throw new Error('Invalid webhook URL'); + throw new Error("Invalid webhook URL"); } } - const response = await this.request('/predictions', { - method: 'POST', + const response = await this.request("/predictions", { + method: "POST", data: { ...data, stream }, }); @@ -37,7 +37,7 @@ async function createPrediction(options) { */ async function getPrediction(prediction_id) { const response = await this.request(`/predictions/${prediction_id}`, { - method: 'GET', + method: "GET", }); return response.json(); @@ -51,7 +51,7 @@ async function getPrediction(prediction_id) { */ async function cancelPrediction(prediction_id) { const response = await this.request(`/predictions/${prediction_id}/cancel`, { - method: 'POST', + method: "POST", }); return response.json(); @@ -63,8 +63,8 @@ async function cancelPrediction(prediction_id) { * @returns {Promise} - Resolves with a page of predictions */ async function listPredictions() { - const response = await this.request('/predictions', { - method: 'GET', + const response = await this.request("/predictions", { + method: "GET", }); return response.json(); diff --git a/lib/trainings.js b/lib/trainings.js index edb037c4..6b13dca2 100644 --- a/lib/trainings.js +++ b/lib/trainings.js @@ -19,14 +19,17 @@ async function createTraining(model_owner, model_name, version_id, options) { // eslint-disable-next-line no-new new URL(data.webhook); } catch (err) { - throw new Error('Invalid webhook URL'); + throw new Error("Invalid webhook URL"); } } - const response = await this.request(`/models/${model_owner}/${model_name}/versions/${version_id}/trainings`, { - method: 'POST', - data, - }); + const response = await this.request( + `/models/${model_owner}/${model_name}/versions/${version_id}/trainings`, + { + method: "POST", + data, + } + ); return response.json(); } @@ -39,7 +42,7 @@ async function createTraining(model_owner, model_name, version_id, options) { */ async function getTraining(training_id) { const response = await this.request(`/trainings/${training_id}`, { - method: 'GET', + method: "GET", }); return response.json(); @@ -53,7 +56,7 @@ async function getTraining(training_id) { */ async function cancelTraining(training_id) { const response = await this.request(`/trainings/${training_id}/cancel`, { - method: 'POST', + method: "POST", }); return response.json(); @@ -65,8 +68,8 @@ async function cancelTraining(training_id) { * @returns {Promise} - Resolves with a page of trainings */ async function listTrainings() { - const response = await this.request('/trainings', { - method: 'GET', + const response = await this.request("/trainings", { + method: "GET", }); return response.json(); diff --git a/lib/util.js b/lib/util.js index 1b9cf9d2..7b12633c 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,4 +1,4 @@ -const ApiError = require('./error'); +const ApiError = require("./error"); /** * Automatically retry a request if it fails with an appropriate status code. @@ -20,7 +20,7 @@ const ApiError = require('./error'); * @throws {ApiError} If the request failed */ async function withAutomaticRetries(request, options = {}) { - const shouldRetry = options.shouldRetry || (() => (false)); + const shouldRetry = options.shouldRetry || (() => false); const maxRetries = options.maxRetries || 5; const interval = options.interval || 500; const jitter = options.jitter || 100; @@ -30,7 +30,7 @@ async function withAutomaticRetries(request, options = {}) { let attempts = 0; do { - let delay = (interval * (2 ** attempts)) + (Math.random() * jitter); + let delay = interval * 2 ** attempts + Math.random() * jitter; /* eslint-disable no-await-in-loop */ try { @@ -40,14 +40,16 @@ async function withAutomaticRetries(request, options = {}) { } } catch (error) { if (error instanceof ApiError) { - const retryAfter = error.response.headers.get('Retry-After'); + const retryAfter = error.response.headers.get("Retry-After"); if (retryAfter) { - if (!Number.isInteger(retryAfter)) { // Retry-After is a date + if (!Number.isInteger(retryAfter)) { + // Retry-After is a date const date = new Date(retryAfter); if (!Number.isNaN(date.getTime())) { delay = date.getTime() - new Date().getTime(); } - } else { // Retry-After is a number of seconds + } else { + // Retry-After is a number of seconds delay = retryAfter * 1000; } } From cee886eb21e062c7429ee44cb9117764f3bb801a Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 8 Dec 2023 05:56:23 -0800 Subject: [PATCH 071/207] Add .git-blame-ignore-revs --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..006ebf9b --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Apply automatic formatting +66d81afa121fb205cfbe46cfe7e2845183b1b237 From 91c03f54ca21306c3872c17f3c94e3e9394ed7a0 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 8 Dec 2023 13:28:41 -0800 Subject: [PATCH 072/207] 0.24.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1c211cff..e95e5366 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.23.0", + "version": "0.24.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.23.0", + "version": "0.24.0", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 3bb36b6e..991e2008 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.23.0", + "version": "0.24.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From cbde2c1b09f4c679178a473ebf1d915e685c85f5 Mon Sep 17 00:00:00 2001 From: Mattt Date: Mon, 11 Dec 2023 03:29:00 -0800 Subject: [PATCH 073/207] Add `replicate.stream` method (#169) * Add replicate.stream method * Update README --- README.md | 43 ++++++++++++++++++ index.d.ts | 17 +++++++ index.js | 42 +++++++++++++++++ lib/stream.js | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 225 insertions(+) create mode 100644 lib/stream.js diff --git a/README.md b/README.md index 1eb7a2b5..fd13c705 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,49 @@ const input = { prompt: "a 19th century portrait of a raccoon gentleman wearing const output = await replicate.run(model, { input }); ``` +### `replicate.stream` + +Run a model and stream its output. Unlike [`replicate.prediction.create`](#replicatepredictionscreate), this method returns only the prediction output rather than the entire prediction object. + +```js +for await (const event of replicate.stream(identifier, options)) { /* ... */ } +``` + +| name | type | description | +| ------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `identifier` | string | **Required**. The model version identifier in the format `{owner}/{name}` or `{owner}/{name}:{version}`, for example `meta/llama-2-70b-chat` | +| `options.input` | object | **Required**. An object with the model inputs. | +| `options.webhook` | string | An HTTPS URL for receiving a webhook when the prediction has new output | +| `options.webhook_events_filter` | string[] | An array of events which should trigger [webhooks](https://replicate.com/docs/webhooks). Allowable values are `start`, `output`, `logs`, and `completed` | +| `options.signal` | object | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the prediction | + +Throws `Error` if the prediction failed. + +Returns `AsyncGenerator` which yields the events of running the model. + +Example: + +```js +for await (const event of replicate.stream("meta/llama-2-70b-chat")) { + process.stdout.write(`${event}`); +} +``` + +### Server-sent events + +A stream generates server-sent events with the following properties: + +| name | type | description | +| ------- | ------ | ---------------------------------------------------------------------------- | +| `event` | string | The type of event. Possible values are `output`, `logs`, `error`, and `done` | +| `data` | string | The event data | +| `id` | string | The event id | +| `retry` | number | The number of milliseconds to wait before reconnecting to the server | + +As the prediction runs, the generator yields `output` and `logs` events. If an error occurs, the generator yields an `error` event with a JSON object containing the error message set to the `data` property. When the prediction is done, the generator yields a `done` event with an empty JSON object set to the `data` property. + +Events with the `output` event type have their `toString()` method overridden to return the event data as a string. Other event types return an empty string. + ### `replicate.models.get` Get metadata for a public model or a private model that you own. diff --git a/index.d.ts b/index.d.ts index 3b9d80c4..05edbe6e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -75,6 +75,13 @@ declare module "replicate" { results: T[]; } + export interface ServerSentEvent { + event: string; + data: string; + id?: string; + retry?: number; + } + export default class Replicate { constructor(options?: { auth?: string; @@ -103,6 +110,16 @@ declare module "replicate" { progress?: (prediction: Prediction) => void ): Promise; + stream( + identifier: `${string}/${string}` | `${string}/${string}:${string}`, + options: { + input: object; + webhook?: string; + webhook_events_filter?: WebhookEventType[]; + signal?: AbortSignal; + } + ): AsyncGenerator; + request( route: string | URL, options: { diff --git a/index.js b/index.js index b9082260..5a6c5320 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ const ApiError = require("./lib/error"); const ModelVersionIdentifier = require("./lib/identifier"); +const { Stream } = require("./lib/stream"); const { withAutomaticRetries } = require("./lib/util"); const collections = require("./lib/collections"); @@ -235,6 +236,47 @@ class Replicate { return response; } + /** + * Stream a model and wait for its output. + * + * @param {string} identifier - Required. The model version identifier in the format "{owner}/{name}:{version}" + * @param {object} options + * @param {object} options.input - Required. An object with the model inputs + * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output + * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) + * @param {AbortSignal} [options.signal] - AbortSignal to cancel the prediction + * @throws {Error} If the prediction failed + * @yields {ServerSentEvent} Each streamed event from the prediction + */ + async *stream(ref, options) { + const { wait, ...data } = options; + + const identifier = ModelVersionIdentifier.parse(ref); + + let prediction; + if (identifier.version) { + prediction = await this.predictions.create({ + ...data, + version: identifier.version, + stream: true, + }); + } else { + prediction = await this.models.predictions.create( + identifier.owner, + identifier.name, + { ...data, stream: true } + ); + } + + if (prediction.urls && prediction.urls.stream) { + const { signal } = options; + const stream = new Stream(prediction.urls.stream, { signal }); + yield* stream; + } else { + throw new Error("Prediction does not support streaming"); + } + } + /** * Paginate through a list of results. * diff --git a/lib/stream.js b/lib/stream.js new file mode 100644 index 00000000..dfc7c619 --- /dev/null +++ b/lib/stream.js @@ -0,0 +1,123 @@ +const { Readable } = require("stream"); + +/** + * A server-sent event. + */ +class ServerSentEvent { + /** + * Create a new server-sent event. + * + * @param {string} event The event name. + * @param {string} data The event data. + * @param {string} id The event ID. + * @param {number} retry The retry time. + */ + constructor(event, data, id, retry) { + this.event = event; + this.data = data; + this.id = id; + this.retry = retry; + } + + /** + * Convert the event to a string. + */ + toString() { + if (this.event === "output") { + return this.data; + } + + return ""; + } +} + +/** + * A stream of server-sent events. + */ +class Stream extends Readable { + /** + * Create a new stream of server-sent events. + * + * @param {string} url The URL to connect to. + * @param {object} options The fetch options. + */ + constructor(url, options) { + super(); + this.url = url; + this.options = options; + + this.event = null; + this.data = []; + this.lastEventId = null; + this.retry = null; + } + + decode(line) { + if (!line) { + if (!this.event && !this.data.length && !this.lastEventId) { + return null; + } + + const sse = new ServerSentEvent( + this.event, + this.data.join("\n"), + this.lastEventId + ); + + this.event = null; + this.data = []; + this.retry = null; + + return sse; + } + + if (line.startsWith(":")) { + return null; + } + + const [field, value] = line.split(": "); + if (field === "event") { + this.event = value; + } else if (field === "data") { + this.data.push(value); + } else if (field === "id") { + this.lastEventId = value; + } + + return null; + } + + async *[Symbol.asyncIterator]() { + const response = await fetch(this.url, { + ...this.options, + headers: { + Accept: "text/event-stream", + }, + }); + + for await (const chunk of response.body) { + const decoder = new TextDecoder("utf-8"); + const text = decoder.decode(chunk); + const lines = text.split("\n"); + for (const line of lines) { + const sse = this.decode(line); + if (sse) { + if (sse.event === "error") { + throw new Error(sse.data); + } + + yield sse; + + if (sse.event === "done") { + return; + } + } + } + } + } +} + +module.exports = { + Stream, + ServerSentEvent, +}; From 8fd6c080e9fb2892dea3f786240b17468f88dc00 Mon Sep 17 00:00:00 2001 From: Mattt Date: Mon, 11 Dec 2023 03:46:30 -0800 Subject: [PATCH 074/207] Allow `replicate.predictions.create` to accept `version` or `model` options (#170) * Allow replicate.predictions.create to accept version or model options * Remove models.predictions.create method --- index.d.ts | 29 ++++++++++------------------- index.js | 26 +++++++++++++------------- index.test.ts | 21 +++++++++------------ lib/models.js | 27 --------------------------- lib/predictions.js | 23 +++++++++++++++++------ 5 files changed, 49 insertions(+), 77 deletions(-) diff --git a/index.d.ts b/index.d.ts index 05edbe6e..5620f3be 100644 --- a/index.d.ts +++ b/index.d.ts @@ -188,28 +188,19 @@ declare module "replicate" { version_id: string ): Promise; }; - predictions: { - create( - model_owner: string, - model_name: string, - options: { - input: object; - stream?: boolean; - webhook?: string; - webhook_events_filter?: WebhookEventType[]; - } - ): Promise; - }; }; predictions: { - create(options: { - version: string; - input: object; - stream?: boolean; - webhook?: string; - webhook_events_filter?: WebhookEventType[]; - }): Promise; + create( + options: { + model?: string; + version?: string; + input: object; + stream?: boolean; + webhook?: string; + webhook_events_filter?: WebhookEventType[]; + } & ({ version: string } | { model: string }) + ): Promise; get(prediction_id: string): Promise; cancel(prediction_id: string): Promise; list(): Promise>; diff --git a/index.js b/index.js index 5a6c5320..a3ed7f8f 100644 --- a/index.js +++ b/index.js @@ -70,9 +70,6 @@ class Replicate { list: models.versions.list.bind(this), get: models.versions.get.bind(this), }, - predictions: { - create: models.predictions.create.bind(this), - }, }; this.predictions = { @@ -117,12 +114,13 @@ class Replicate { ...data, version: identifier.version, }); + } else if (identifier.owner && identifier.name) { + prediction = await this.predictions.create({ + ...data, + model: `${identifier.owner}/${identifier.name}`, + }); } else { - prediction = await this.models.predictions.create( - identifier.owner, - identifier.name, - data - ); + throw new Error("Invalid model version identifier"); } // Call progress callback with the initial prediction object @@ -260,12 +258,14 @@ class Replicate { version: identifier.version, stream: true, }); + } else if (identifier.owner && identifier.name) { + prediction = await this.predictions.create({ + ...data, + model: `${identifier.owner}/${identifier.name}`, + stream: true, + }); } else { - prediction = await this.models.predictions.create( - identifier.owner, - identifier.name, - { ...data, stream: true } - ); + throw new Error("Invalid model version identifier"); } if (prediction.urls && prediction.urls.stream) { diff --git a/index.test.ts b/index.test.ts index 98bff4ac..5b5a1ddc 100644 --- a/index.test.ts +++ b/index.test.ts @@ -700,7 +700,7 @@ describe("Replicate client", () => { // Add more tests for error handling, edge cases, etc. }); - describe("models.predictions.create", () => { + describe("predictions.create with model", () => { test("Calls the correct API route with the correct payload", async () => { nock(BASE_URL) .post("/models/meta/llama-2-70b-chat/predictions") @@ -721,17 +721,14 @@ describe("Replicate client", () => { get: "https://api.replicate.com/v1/predictions/heat2o3bzn3ahtr6bjfftvbaci", }, }); - const prediction = await client.models.predictions.create( - "meta", - "llama-2-70b-chat", - { - input: { - prompt: "Please write a haiku about llamas.", - }, - webhook: "http://test.host/webhook", - webhook_events_filter: ["output", "completed"], - } - ); + const prediction = await client.predictions.create({ + model: "meta/llama-2-70b-chat", + input: { + prompt: "Please write a haiku about llamas.", + }, + webhook: "http://test.host/webhook", + webhook_events_filter: ["output", "completed"], + }); expect(prediction.id).toBe("heat2o3bzn3ahtr6bjfftvbaci"); }); // Add more tests for error handling, edge cases, etc. diff --git a/lib/models.js b/lib/models.js index cb32e9e0..c6a02fca 100644 --- a/lib/models.js +++ b/lib/models.js @@ -89,36 +89,9 @@ async function createModel(model_owner, model_name, options) { return response.json(); } -/** - * Create a new prediction - * - * @param {string} model_owner - Required. The name of the user or organization that owns the model - * @param {string} model_name - Required. The name of the model - * @param {object} options - * @param {object} options.input - Required. An object with the model inputs - * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output - * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) - * @param {boolean} [options.stream] - Whether to stream the prediction output. Defaults to false - * @returns {Promise} Resolves with the created prediction - */ -async function createPrediction(model_owner, model_name, options) { - const { stream, ...data } = options; - - const response = await this.request( - `/models/${model_owner}/${model_name}/predictions`, - { - method: "POST", - data: { ...data, stream }, - } - ); - - return response.json(); -} - module.exports = { get: getModel, list: listModels, create: createModel, versions: { list: listModelVersions, get: getModelVersion }, - predictions: { create: createPrediction }, }; diff --git a/lib/predictions.js b/lib/predictions.js index b1f7fe59..294e8d97 100644 --- a/lib/predictions.js +++ b/lib/predictions.js @@ -2,7 +2,8 @@ * Create a new prediction * * @param {object} options - * @param {string} options.version - Required. The model version + * @param {string} options.model - The model. + * @param {string} options.version - The model version. * @param {object} options.input - Required. An object with the model inputs * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) @@ -10,7 +11,7 @@ * @returns {Promise} Resolves with the created prediction */ async function createPrediction(options) { - const { stream, ...data } = options; + const { model, version, stream, ...data } = options; if (data.webhook) { try { @@ -21,10 +22,20 @@ async function createPrediction(options) { } } - const response = await this.request("/predictions", { - method: "POST", - data: { ...data, stream }, - }); + let response; + if (version) { + response = await this.request("/predictions", { + method: "POST", + data: { ...data, stream, version }, + }); + } else if (model) { + response = await this.request(`/models/${model}/predictions`, { + method: "POST", + data: { ...data, stream }, + }); + } else { + throw new Error("Either model or version must be specified"); + } return response.json(); } From b1cfdc49a7880f322891586c352a49002c4c01e3 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Mon, 11 Dec 2023 04:00:41 -0800 Subject: [PATCH 075/207] 0.25.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e95e5366..e7e2699f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.24.0", + "version": "0.25.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.24.0", + "version": "0.25.0", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 991e2008..3551bbd2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.24.0", + "version": "0.25.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From d39cb4aac6b5f94cf15a60efe2e5c0f41f554c62 Mon Sep 17 00:00:00 2001 From: Mattt Date: Tue, 12 Dec 2023 05:39:42 -0800 Subject: [PATCH 076/207] Make streaming functionality optional (#172) * Add readable-stream as optional dependency * Attempt to use readable-stream if available, falling back on native stream, and if neither are available, throwing an error when calling stream --- lib/stream.js | 18 +++++- package-lock.json | 151 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 + 3 files changed, 171 insertions(+), 1 deletion(-) diff --git a/lib/stream.js b/lib/stream.js index dfc7c619..012d6d03 100644 --- a/lib/stream.js +++ b/lib/stream.js @@ -1,4 +1,14 @@ -const { Readable } = require("stream"); +// Attempt to use readable-stream if available, attempt to use the built-in stream module. +let Readable; +try { + Readable = require("readable-stream").Readable; +} catch (e) { + try { + Readable = require("stream").Readable; + } catch (e) { + Readable = null; + } +} /** * A server-sent event. @@ -42,6 +52,12 @@ class Stream extends Readable { * @param {object} options The fetch options. */ constructor(url, options) { + if (!Readable) { + throw new Error( + "Readable streams are not supported. Please use Node.js 18 or later, or install the readable-stream package." + ); + } + super(); this.url = url; this.options = options; diff --git a/package-lock.json b/package-lock.json index e7e2699f..62c0c782 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,9 @@ "node": ">=18.0.0", "npm": ">=7.19.0", "yarn": ">=1.7.0" + }, + "optionalDependencies": { + "readable-stream": ">=4.0.0" } }, "node_modules/@ampproject/remapping": { @@ -1729,6 +1732,18 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "optional": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", @@ -1946,6 +1961,26 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2017,6 +2052,30 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2529,6 +2588,24 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "optional": true, + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -2889,6 +2966,26 @@ "node": ">=10.17.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -4292,6 +4389,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "optional": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -4366,6 +4472,22 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/readable-stream": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", + "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", + "optional": true, + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -4481,6 +4603,26 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -4602,6 +4744,15 @@ "node": ">=8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", diff --git a/package.json b/package.json index 3551bbd2..58782812 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,9 @@ "lint": "biome lint .", "test": "jest" }, + "optionalDependencies": { + "readable-stream": ">=4.0.0" + }, "devDependencies": { "@biomejs/biome": "^1.4.1", "@types/jest": "^29.5.3", From 0a3b7256a219d7c7d8168cf4539d9836bb5c71c6 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 12 Dec 2023 05:40:09 -0800 Subject: [PATCH 077/207] 0.25.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 62c0c782..b560a277 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.25.0", + "version": "0.25.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.25.0", + "version": "0.25.1", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 58782812..b576735f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.25.0", + "version": "0.25.1", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 4324b48cf6938315e86018dd9db576fc5e73f9c5 Mon Sep 17 00:00:00 2001 From: Mattt Date: Fri, 15 Dec 2023 05:14:28 -0800 Subject: [PATCH 078/207] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fd13c705..0afa9c5a 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ It lets you run models from your Node.js code, and everything else you can do with [Replicate's HTTP API](https://replicate.com/docs/reference/http). -> **Warning** +> [!IMPORTANT] > This library can't interact with Replicate's API directly from a browser. > For more information about how to build a web application > check out our ["Build a website with Next.js"](https://replicate.com/docs/get-started/nextjs) guide. From 16c47e6870d50b9cb0d780bad6c6d2f9799f38f9 Mon Sep 17 00:00:00 2001 From: Mattt Date: Tue, 19 Dec 2023 02:54:54 -0800 Subject: [PATCH 079/207] Replace use of `Headers` with object (#174) * Turn of biome linter/complexity/useLiteralKeys linter rule * Replace use of Headers with Object * Update README * Add Node 20 to CI matrix --- .github/workflows/ci.yml | 4 ++-- README.md | 4 ++-- biome.json | 7 ++++--- index.js | 12 ++++++------ 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e41e8aef..155ea01f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,8 +12,8 @@ jobs: strategy: matrix: - node-version: [18.x] - # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + node-version: [18.x, 20.x] + # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases steps: - uses: actions/checkout@v3 diff --git a/README.md b/README.md index 0afa9c5a..4d6e7280 100644 --- a/README.md +++ b/README.md @@ -132,8 +132,8 @@ such as injecting headers or adding log statements. ```js replicate.fetch = (url, options) => { - const headers = new Headers(options && options.headers); - headers.append("X-Custom-Header", "some value"); + const headers = options && options.headers ? { ...options.headers } : {}; + headers["X-Custom-Header"] = "some value"; console.log("fetch", { url, ...options, headers }); diff --git a/biome.json b/biome.json index 63919df3..807b9013 100644 --- a/biome.json +++ b/biome.json @@ -17,15 +17,16 @@ "useMediaCaption": "off", "noSvgWithoutTitle": "off" }, + "complexity": { + "useLiteralKeys": "off", + "useOptionalChain": "off" + }, "performance": { "noAccumulatingSpread": "off" }, "suspicious": { "noArrayIndexKey": "off", "noExplicitAny": "off" - }, - "complexity": { - "useOptionalChain": "off" } } } diff --git a/index.js b/index.js index a3ed7f8f..ce407f91 100644 --- a/index.js +++ b/index.js @@ -191,15 +191,15 @@ class Replicate { url.searchParams.append(key, value); } - const headers = new Headers(); + const headers = {}; if (auth) { - headers.append("Authorization", `Token ${auth}`); + headers["Authorization"] = `Token ${auth}`; } - headers.append("Content-Type", "application/json"); - headers.append("User-Agent", userAgent); + headers["Content-Type"] = "application/json"; + headers["User-Agent"] = userAgent; if (options.headers) { - for (const [key, value] of options.headers.entries()) { - headers.append(key, value); + for (const [key, value] of Object.entries(options.headers)) { + headers[key] = value; } } From a9e4326d969f91bb1773eb5eca00314b7f4b58da Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 19 Dec 2023 02:55:12 -0800 Subject: [PATCH 080/207] 0.25.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b560a277..19133da4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.25.1", + "version": "0.25.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.25.1", + "version": "0.25.2", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index b576735f..24a088b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.25.1", + "version": "0.25.2", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From e204eff314d8eb8bbdab4bb770b493066d529118 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Mon, 8 Jan 2024 13:16:16 +0000 Subject: [PATCH 081/207] Encourage passing `fetch` option rather than patching the instance (#183) Minor stylistic change, but means that we're not committing to a `replicate.fetch` top level function and instead encouraging any custom functionality to be passed to the constructor rather than monkey patching the api. --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4d6e7280..bd7e43d7 100644 --- a/README.md +++ b/README.md @@ -127,11 +127,11 @@ import fetch from "cross-fetch"; const replicate = new Replicate({ fetch }); ``` -You can override the `fetch` property to add custom behavior to client requests, +You can also use the `fetch` option to add custom behavior to client requests, such as injecting headers or adding log statements. ```js -replicate.fetch = (url, options) => { +const customFetch = (url, options) => { const headers = options && options.headers ? { ...options.headers } : {}; headers["X-Custom-Header"] = "some value"; @@ -139,6 +139,8 @@ replicate.fetch = (url, options) => { return fetch(url, { ...options, headers }); }; + +const replicate = new Replicate({ fetch: customFetch }); ``` ### `replicate.run` From c26c3f0d71b222ac13c14bf4b66e05fb08565f5d Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Sat, 13 Jan 2024 21:49:26 +0000 Subject: [PATCH 082/207] Integration tests for common runtimes (#186) This commit adds some basic integration tests and a new ci workflow for our three common runtimes, nodejs with commonjs and esm as well as TypeScript. The aim here is to ensure that the interface is consistent even if we refactor/restructure the package. The change does require a `REPLICATE_API_TOKEN` to be available in the CI environment. Co-authored-by: Zeke Sikelianos --- .github/workflows/ci.yml | 23 +++++ integration/commonjs/index.js | 16 ++++ integration/commonjs/index.test.js | 8 ++ integration/commonjs/package-lock.json | 42 +++++++++ integration/commonjs/package.json | 13 +++ integration/esm/index.js | 16 ++++ integration/esm/index.test.js | 8 ++ integration/esm/package-lock.json | 43 +++++++++ integration/esm/package.json | 14 +++ integration/typescript/index.test.ts | 24 +++++ integration/typescript/index.ts | 16 ++++ integration/typescript/package-lock.json | 70 +++++++++++++++ integration/typescript/package.json | 16 ++++ integration/typescript/tsconfig.json | 109 +++++++++++++++++++++++ jest.config.js | 1 + 15 files changed, 419 insertions(+) create mode 100644 integration/commonjs/index.js create mode 100644 integration/commonjs/index.test.js create mode 100644 integration/commonjs/package-lock.json create mode 100644 integration/commonjs/package.json create mode 100644 integration/esm/index.js create mode 100644 integration/esm/index.test.js create mode 100644 integration/esm/package-lock.json create mode 100644 integration/esm/package.json create mode 100644 integration/typescript/index.test.ts create mode 100644 integration/typescript/index.ts create mode 100644 integration/typescript/package-lock.json create mode 100644 integration/typescript/package.json create mode 100644 integration/typescript/tsconfig.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 155ea01f..de7cec3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,3 +26,26 @@ jobs: - run: npm run test - run: npm run check - run: npm run lint + + integration: + needs: test + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x, 20.x] + suite: [commonjs, esm, typescript] + # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases + + env: + REPLICATE_API_TOKEN: ${{ secrets.REPLICATE_API_TOKEN }} + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" + - run: npm --prefix integration/${{ matrix.suite }} ci --omit=dev + - run: npm --prefix integration/${{ matrix.suite }} test diff --git a/integration/commonjs/index.js b/integration/commonjs/index.js new file mode 100644 index 00000000..abe859a2 --- /dev/null +++ b/integration/commonjs/index.js @@ -0,0 +1,16 @@ +const Replicate = require("replicate"); + +const replicate = new Replicate({ + auth: process.env.REPLICATE_API_TOKEN, +}); + +module.exports = async function main() { + return await replicate.run( + "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + { + input: { + text: "Claire CommonJS" + } + } + ); +}; diff --git a/integration/commonjs/index.test.js b/integration/commonjs/index.test.js new file mode 100644 index 00000000..5ef7b63c --- /dev/null +++ b/integration/commonjs/index.test.js @@ -0,0 +1,8 @@ +const { test } = require('node:test'); +const assert = require('node:assert'); +const main = require('./index'); + +test('main', async () => { + const output = await main(); + assert.equal(output, "hello Claire CommonJS"); +}); diff --git a/integration/commonjs/package-lock.json b/integration/commonjs/package-lock.json new file mode 100644 index 00000000..1584af5e --- /dev/null +++ b/integration/commonjs/package-lock.json @@ -0,0 +1,42 @@ +{ + "name": "replicate-app-commonjs", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "replicate-app-commonjs", + "version": "0.0.0", + "dependencies": { + "replicate": "file:../../" + } + }, + "../..": { + "version": "0.25.2", + "license": "Apache-2.0", + "devDependencies": { + "@biomejs/biome": "^1.4.1", + "@types/jest": "^29.5.3", + "@typescript-eslint/eslint-plugin": "^5.56.0", + "cross-fetch": "^3.1.5", + "jest": "^29.6.2", + "nock": "^13.3.0", + "ts-jest": "^29.1.0", + "typescript": "^5.0.2" + }, + "engines": { + "git": ">=2.11.0", + "node": ">=18.0.0", + "npm": ">=7.19.0", + "yarn": ">=1.7.0" + }, + "optionalDependencies": { + "readable-stream": ">=4.0.0" + } + }, + "node_modules/replicate": { + "resolved": "../..", + "link": true + } + } +} diff --git a/integration/commonjs/package.json b/integration/commonjs/package.json new file mode 100644 index 00000000..7fb6fc8a --- /dev/null +++ b/integration/commonjs/package.json @@ -0,0 +1,13 @@ +{ + "name": "replicate-app-commonjs", + "version": "0.0.0", + "private": true, + "description": "CommonJS integration tests", + "main": "index.js", + "scripts": { + "test": "node --test ./index.test.js" + }, + "dependencies": { + "replicate": "file:../../" + } +} \ No newline at end of file diff --git a/integration/esm/index.js b/integration/esm/index.js new file mode 100644 index 00000000..547b726f --- /dev/null +++ b/integration/esm/index.js @@ -0,0 +1,16 @@ +import Replicate from "replicate"; + +const replicate = new Replicate({ + auth: process.env.REPLICATE_API_TOKEN, +}); + +export default async function main() { + return await replicate.run( + "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + { + input: { + text: "Evelyn ESM" + } + } + ); +}; diff --git a/integration/esm/index.test.js b/integration/esm/index.test.js new file mode 100644 index 00000000..2bd276fd --- /dev/null +++ b/integration/esm/index.test.js @@ -0,0 +1,8 @@ +import { test } from 'node:test'; +import assert from 'node:assert'; +import main from './index.js'; + +test('main', async () => { + const output = await main(); + assert.equal(output, "hello Evelyn ESM"); +}); diff --git a/integration/esm/package-lock.json b/integration/esm/package-lock.json new file mode 100644 index 00000000..2a17c888 --- /dev/null +++ b/integration/esm/package-lock.json @@ -0,0 +1,43 @@ +{ + "name": "replicate-app-esm", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "replicate-app-esm", + "version": "0.0.0", + "dependencies": { + "replicate": "file:../../" + } + }, + "../..": { + "name": "replicate", + "version": "0.25.2", + "license": "Apache-2.0", + "devDependencies": { + "@biomejs/biome": "^1.4.1", + "@types/jest": "^29.5.3", + "@typescript-eslint/eslint-plugin": "^5.56.0", + "cross-fetch": "^3.1.5", + "jest": "^29.6.2", + "nock": "^13.3.0", + "ts-jest": "^29.1.0", + "typescript": "^5.0.2" + }, + "engines": { + "git": ">=2.11.0", + "node": ">=18.0.0", + "npm": ">=7.19.0", + "yarn": ">=1.7.0" + }, + "optionalDependencies": { + "readable-stream": ">=4.0.0" + } + }, + "node_modules/replicate": { + "resolved": "../..", + "link": true + } + } +} diff --git a/integration/esm/package.json b/integration/esm/package.json new file mode 100644 index 00000000..51076d79 --- /dev/null +++ b/integration/esm/package.json @@ -0,0 +1,14 @@ +{ + "name": "replicate-app-esm", + "version": "0.0.0", + "private": true, + "description": "ESM (ECMAScript Modules) integration tests", + "main": "index.js", + "type": "module", + "scripts": { + "test": "node --test ./index.test.js" + }, + "dependencies": { + "replicate": "file:../../" + } +} \ No newline at end of file diff --git a/integration/typescript/index.test.ts b/integration/typescript/index.test.ts new file mode 100644 index 00000000..be4ab90c --- /dev/null +++ b/integration/typescript/index.test.ts @@ -0,0 +1,24 @@ +import { test } from 'node:test'; +import assert from 'node:assert'; +import main from './index.js'; + +// Verify exported types. +import type { + Status, + Visibility, + WebhookEventType, + ApiError, + Collection, + Hardware, + Model, + ModelVersion, + Prediction, + Training, + Page, + ServerSentEvent, +} from "replicate"; + +test('main', async () => { + const output = await main(); + assert.equal(output, "hello Tracy TypeScript"); +}); diff --git a/integration/typescript/index.ts b/integration/typescript/index.ts new file mode 100644 index 00000000..8e27a3ba --- /dev/null +++ b/integration/typescript/index.ts @@ -0,0 +1,16 @@ +import Replicate from "replicate"; + +const replicate = new Replicate({ + auth: process.env.REPLICATE_API_TOKEN, +}); + +export default async function main() { + return await replicate.run( + "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + { + input: { + text: "Tracy TypeScript" + } + } + ); +}; diff --git a/integration/typescript/package-lock.json b/integration/typescript/package-lock.json new file mode 100644 index 00000000..f309b1b1 --- /dev/null +++ b/integration/typescript/package-lock.json @@ -0,0 +1,70 @@ +{ + "name": "replicate-app-esm", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "replicate-app-esm", + "version": "0.0.0", + "dependencies": { + "@types/node": "^20.11.0", + "replicate": "file:../../", + "typescript": "^5.3.3" + } + }, + "../..": { + "name": "replicate", + "version": "0.25.2", + "license": "Apache-2.0", + "devDependencies": { + "@biomejs/biome": "^1.4.1", + "@types/jest": "^29.5.3", + "@typescript-eslint/eslint-plugin": "^5.56.0", + "cross-fetch": "^3.1.5", + "jest": "^29.6.2", + "nock": "^13.3.0", + "ts-jest": "^29.1.0", + "typescript": "^5.0.2" + }, + "engines": { + "git": ">=2.11.0", + "node": ">=18.0.0", + "npm": ">=7.19.0", + "yarn": ">=1.7.0" + }, + "optionalDependencies": { + "readable-stream": ">=4.0.0" + } + }, + "node_modules/@types/node": { + "version": "20.11.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz", + "integrity": "sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/replicate": { + "resolved": "../..", + "link": true + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + } + } +} diff --git a/integration/typescript/package.json b/integration/typescript/package.json new file mode 100644 index 00000000..4adae998 --- /dev/null +++ b/integration/typescript/package.json @@ -0,0 +1,16 @@ +{ + "name": "replicate-app-typescript", + "version": "0.0.0", + "private": true, + "description": "TypeScript integration tests", + "main": "index.js", + "type": "module", + "scripts": { + "test": "tsc && node --test ./dist/index.test.js" + }, + "dependencies": { + "@types/node": "^20.11.0", + "replicate": "file:../../", + "typescript": "^5.3.3" + } +} \ No newline at end of file diff --git a/integration/typescript/tsconfig.json b/integration/typescript/tsconfig.json new file mode 100644 index 00000000..4b733f2c --- /dev/null +++ b/integration/typescript/tsconfig.json @@ -0,0 +1,109 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2018", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "nodenext", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/jest.config.js b/jest.config.js index 821539fb..058816ab 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,6 +3,7 @@ module.exports = { preset: "ts-jest", testEnvironment: "node", + testPathIgnorePatterns: ["integration"], transform: { "^.+\\.ts?$": [ "ts-jest", From 91ff08b3d0a23e23b51c533cecb519b2b5270875 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Mon, 15 Jan 2024 21:01:05 +0000 Subject: [PATCH 083/207] Update README.md to include commonjs examples --- README.md | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bd7e43d7..9d5bef31 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,14 @@ npm install replicate Create the client: ```js +// CommonJS (default or using .cjs extension) +const Replicate = require("replicate"); + +// ESM (where `"module": true` in package.json or using .mjs extension) import Replicate from "replicate"; +``` +``` const replicate = new Replicate({ // get your token from https://replicate.com/account auth: "my api token", // defaults to process.env.REPLICATE_API_TOKEN @@ -69,9 +75,11 @@ console.log(prediction.output); To run a model that takes a file input, pass a URL to a publicly accessible file. Or, for smaller files (<10MB), you can convert file data into a base64-encoded data URI and pass that directly: - ```js -import { promises as fs } from "fs"; +const fs = require("node:fs/promises"); + +// Or when using ESM. +// import fs from "node:fs/promises"; // Read the file into a buffer const data = await fs.readFile("path/to/image.png"); @@ -90,6 +98,10 @@ const output = await replicate.run(model, { input }); // ['https://replicate.delivery/mgxm/e7b0e122-9daa-410e-8cde-006c7308ff4d/output.png'] ``` +## TypeScript + +Currently in order to support the module format used by `replicate` you'll need to set `esModuleInterop` to `true` in your tsconfig.json. + ## API ### Constructor @@ -121,8 +133,12 @@ you can install a fetch function from an external package like and pass it to the `fetch` option in the constructor. ```js -import Replicate from "replicate"; -import fetch from "cross-fetch"; +const Replicate = require("replicate"); +const fetch = require("fetch"); + +// Using ESM: +// import Replicate from "replicate"; +// import fetch from "cross-fetch"; const replicate = new Replicate({ fetch }); ``` From ddef501302b1d7c955e830d095945430b467e9f0 Mon Sep 17 00:00:00 2001 From: Mattt Date: Wed, 17 Jan 2024 06:32:32 -0800 Subject: [PATCH 084/207] Specify type field in package.json (#192) --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 24a088b2..ca06b8bd 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "bugs": "https://github.com/replicate/replicate-javascript/issues", "license": "Apache-2.0", "main": "index.js", + "type": "commonjs", "engines": { "node": ">=18.0.0", "npm": ">=7.19.0", From 685a86dd3a476524348e51b66848eab59739310b Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Mon, 15 Jan 2024 20:36:56 +0000 Subject: [PATCH 085/207] Safelist files to be added to the published package Currently we include all files including tests, ci scripts and tooling config. See: https://npmfs.com/package/replicate/0.25.2/ --- package.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/package.json b/package.json index ca06b8bd..d0d77fb3 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,16 @@ "license": "Apache-2.0", "main": "index.js", "type": "commonjs", + "types": "index.d.ts", + "files": [ + "CONTRIBUTING.md", + "LICENSE", + "README.md", + "index.d.ts", + "index.js", + "lib/**/*.js", + "package.json" + ], "engines": { "node": ">=18.0.0", "npm": ">=7.19.0", From bfef365960d3fcd6ca1936eca0fcf2bdabc710c3 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Wed, 17 Jan 2024 15:35:26 +0000 Subject: [PATCH 086/207] Update CI integration tests to run the Replicate tarball --- .github/workflows/ci.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de7cec3d..b42984ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,5 +47,8 @@ jobs: with: node-version: ${{ matrix.node-version }} cache: "npm" - - run: npm --prefix integration/${{ matrix.suite }} ci --omit=dev - - run: npm --prefix integration/${{ matrix.suite }} test + # Build a production tarball and run the integration tests against it. + - run: | + PKG_TARBALL=$(npm --loglevel error pack) + npm --prefix integration/${{ matrix.suite }} install "file:/./$PKG_TARBALL" + npm --prefix integration/${{ matrix.suite }} test From 6dd5b21884e5dab8d137818e3c43ab14f4335f30 Mon Sep 17 00:00:00 2001 From: Mattt Date: Thu, 18 Jan 2024 14:04:46 -0800 Subject: [PATCH 087/207] Lint project with `publint` (#193) * Add publint to devDependencies * Add lint-publint script Create new composite lint script --- package-lock.json | 154 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 5 +- 2 files changed, 158 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 19133da4..3f748721 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "cross-fetch": "^3.1.5", "jest": "^29.6.2", "nock": "^13.3.0", + "publint": "^0.2.7", "ts-jest": "^29.1.0", "typescript": "^5.0.2" }, @@ -2995,6 +2996,39 @@ "node": ">= 4" } }, + "node_modules/ignore-walk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-5.0.1.tgz", + "integrity": "sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ignore-walk/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/ignore-walk/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -4021,6 +4055,15 @@ "node": "*" } }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -4095,6 +4138,85 @@ "node": ">=0.10.0" } }, + "node_modules/npm-bundled": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-2.0.1.tgz", + "integrity": "sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz", + "integrity": "sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-5.1.3.tgz", + "integrity": "sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg==", + "dev": true, + "dependencies": { + "glob": "^8.0.1", + "ignore-walk": "^5.0.1", + "npm-bundled": "^2.0.0", + "npm-normalize-package-bin": "^2.0.0" + }, + "bin": { + "npm-packlist": "bin/index.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-packlist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm-packlist/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm-packlist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -4420,6 +4542,26 @@ "node": ">= 8" } }, + "node_modules/publint": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/publint/-/publint-0.2.7.tgz", + "integrity": "sha512-tLU4ee3110BxWfAmCZggJmCUnYWgPTr0QLnx08sqpLYa8JHRiOudd+CgzdpfU5x5eOaW2WMkpmOrFshRFYK7Mw==", + "dev": true, + "dependencies": { + "npm-packlist": "^5.1.3", + "picocolors": "^1.0.0", + "sade": "^1.8.1" + }, + "bin": { + "publint": "lib/cli.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://bjornlu.com/sponsor" + } + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -4603,6 +4745,18 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", diff --git a/package.json b/package.json index d0d77fb3..61b4c87a 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,9 @@ "scripts": { "check": "tsc", "format": "biome format . --write", - "lint": "biome lint .", + "lint-biome": "biome lint .", + "lint-publint": "publint", + "lint": "npm run lint-biome && npm run lint-publint", "test": "jest" }, "optionalDependencies": { @@ -40,6 +42,7 @@ "cross-fetch": "^3.1.5", "jest": "^29.6.2", "nock": "^13.3.0", + "publint": "^0.2.7", "ts-jest": "^29.1.0", "typescript": "^5.0.2" } From cafb2a58680df0ca5446e77e3d47c04c01e46c29 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Tue, 30 Jan 2024 03:19:12 -0800 Subject: [PATCH 088/207] improve replicate.stream example (#197) Update README.md --- README.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9d5bef31..90ed1013 100644 --- a/README.md +++ b/README.md @@ -213,9 +213,23 @@ Returns `AsyncGenerator` which yields the events of running the Example: ```js -for await (const event of replicate.stream("meta/llama-2-70b-chat")) { - process.stdout.write(`${event}`); +const model = "meta/llama-2-70b-chat"; +const options = { + input: { + prompt: "Write a poem about machine learning in the style of Mary Oliver.", + }, + // webhook: "https://smee.io/dMUlmOMkzeyRGjW" // optional +}; +const output = []; + +for await (const event of replicate.stream(model, options)) { + console.debug({ event }); + if (event && event === "output") { + output.push(event.data); + } } + +console.log(output.join("").trim()); ``` ### Server-sent events From f6111362a7f46e750c4328f6132fcc8de736eb59 Mon Sep 17 00:00:00 2001 From: Mattt Date: Thu, 8 Feb 2024 02:49:22 -0800 Subject: [PATCH 089/207] Add support for accounts.current endpoint (#178) --- index.d.ts | 11 +++++++++++ index.js | 5 +++++ index.test.ts | 16 ++++++++++++++++ lib/accounts.js | 16 ++++++++++++++++ 4 files changed, 48 insertions(+) create mode 100644 lib/accounts.js diff --git a/index.d.ts b/index.d.ts index 5620f3be..76df9d4f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -8,6 +8,13 @@ declare module "replicate" { response: Response; } + export interface Account { + type: "user" | "organization"; + username: string; + name: string; + github_url?: string; + } + export interface Collection { name: string; slug: string; @@ -140,6 +147,10 @@ declare module "replicate" { stop?: (prediction: Prediction) => Promise ): Promise; + accounts: { + current(): Promise; + }; + collections: { list(): Promise>; get(collection_slug: string): Promise; diff --git a/index.js b/index.js index ce407f91..a85ea4e7 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,7 @@ const ModelVersionIdentifier = require("./lib/identifier"); const { Stream } = require("./lib/stream"); const { withAutomaticRetries } = require("./lib/util"); +const accounts = require("./lib/accounts"); const collections = require("./lib/collections"); const deployments = require("./lib/deployments"); const hardware = require("./lib/hardware"); @@ -47,6 +48,10 @@ class Replicate { this.baseUrl = options.baseUrl || "https://api.replicate.com/v1"; this.fetch = options.fetch || globalThis.fetch; + this.accounts = { + current: accounts.current.bind(this), + }; + this.collections = { list: collections.list.bind(this), get: collections.get.bind(this), diff --git a/index.test.ts b/index.test.ts index 5b5a1ddc..d50ccb47 100644 --- a/index.test.ts +++ b/index.test.ts @@ -67,6 +67,22 @@ describe("Replicate client", () => { }); }); + describe("accounts.current", () => { + test("Calls the correct API route", async () => { + nock(BASE_URL).get("/account").reply(200, { + type: "organization", + username: "replicate", + name: "Replicate", + github_url: "https://github.com/replicate", + }); + + const account = await client.accounts.current(); + expect(account.type).toBe("organization"); + expect(account.username).toBe("replicate"); + }); + // Add more tests for error handling, edge cases, etc. + }); + describe("collections.list", () => { test("Calls the correct API route", async () => { nock(BASE_URL) diff --git a/lib/accounts.js b/lib/accounts.js new file mode 100644 index 00000000..b3bbd9fe --- /dev/null +++ b/lib/accounts.js @@ -0,0 +1,16 @@ +/** + * Get the current account + * + * @returns {Promise} Resolves with the current account + */ +async function getCurrentAccount() { + const response = await this.request("/account", { + method: "GET", + }); + + return response.json(); +} + +module.exports = { + current: getCurrentAccount, +}; From c6fbd332298cad0ea1b3b676c433936a6c6e3d3e Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Thu, 8 Feb 2024 02:55:14 -0800 Subject: [PATCH 090/207] 0.26.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3f748721..35659fad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.25.2", + "version": "0.26.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.25.2", + "version": "0.26.0", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 61b4c87a..1c8680ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.25.2", + "version": "0.26.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From c0d2a018544bb817bb31c47dd9fea82f907e35aa Mon Sep 17 00:00:00 2001 From: Mattt Date: Fri, 16 Feb 2024 07:53:06 -0800 Subject: [PATCH 091/207] Add support for validating webhooks (#200) * Add support for validating webhooks * Use test case from official Svix repo * Add comment about test secret --- index.d.ts | 25 ++++++++++++++ index.js | 12 ++++++- index.test.ts | 41 +++++++++++++++++++++- lib/util.js | 90 ++++++++++++++++++++++++++++++++++++++++++++++++- lib/webhooks.js | 20 +++++++++++ 5 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 lib/webhooks.js diff --git a/index.d.ts b/index.d.ts index 76df9d4f..ab40ed81 100644 --- a/index.d.ts +++ b/index.d.ts @@ -89,6 +89,10 @@ declare module "replicate" { retry?: number; } + export interface WebhookSecret { + key: string; + } + export default class Replicate { constructor(options?: { auth?: string; @@ -233,5 +237,26 @@ declare module "replicate" { cancel(training_id: string): Promise; list(): Promise>; }; + + webhooks: { + default: { + secret: { + get(): Promise; + }; + }; + }; } + + export function validateWebhook( + requestData: + | Request + | { + id?: string; + timestamp?: string; + body: string; + secret?: string; + signature?: string; + }, + secret: string + ): boolean; } diff --git a/index.js b/index.js index a85ea4e7..13207ca2 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ const ApiError = require("./lib/error"); const ModelVersionIdentifier = require("./lib/identifier"); const { Stream } = require("./lib/stream"); -const { withAutomaticRetries } = require("./lib/util"); +const { withAutomaticRetries, validateWebhook } = require("./lib/util"); const accounts = require("./lib/accounts"); const collections = require("./lib/collections"); @@ -10,6 +10,7 @@ const hardware = require("./lib/hardware"); const models = require("./lib/models"); const predictions = require("./lib/predictions"); const trainings = require("./lib/trainings"); +const webhooks = require("./lib/webhooks"); const packageJSON = require("./package.json"); @@ -90,6 +91,14 @@ class Replicate { cancel: trainings.cancel.bind(this), list: trainings.list.bind(this), }; + + this.webhooks = { + default: { + secret: { + get: webhooks.default.secret.get.bind(this), + }, + }, + }; } /** @@ -364,3 +373,4 @@ class Replicate { } module.exports = Replicate; +module.exports.validateWebhook = validateWebhook; diff --git a/index.test.ts b/index.test.ts index d50ccb47..106cc582 100644 --- a/index.test.ts +++ b/index.test.ts @@ -1,5 +1,10 @@ import { expect, jest, test } from "@jest/globals"; -import Replicate, { ApiError, Model, Prediction } from "replicate"; +import Replicate, { + ApiError, + Model, + Prediction, + validateWebhook, +} from "replicate"; import nock from "nock"; import fetch from "cross-fetch"; @@ -996,5 +1001,39 @@ describe("Replicate client", () => { }); }); + describe("webhooks.default.secret.get", () => { + test("Calls the correct API route", async () => { + nock(BASE_URL).get("/webhooks/default/secret").reply(200, { + key: "whsec_5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH", + }); + + const secret = await client.webhooks.default.secret.get(); + expect(secret.key).toBe("whsec_5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH"); + }); + + test("Can be used to validate webhook", async () => { + // Test case from https://github.com/svix/svix-webhooks/blob/b41728cd98a7e7004a6407a623f43977b82fcba4/javascript/src/webhook.test.ts#L190-L200 + const request = new Request("http://test.host/webhook", { + method: "POST", + headers: { + "Content-Type": "application/json", + "Webhook-ID": "msg_p5jXN8AQM9LWM0D4loKWxJek", + "Webhook-Timestamp": "1614265330", + "Webhook-Signature": + "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=", + }, + body: `{"test": 2432232314}`, + }); + + // This is a test secret and should not be used in production + const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"; + + const isValid = await validateWebhook(request, secret); + expect(isValid).toBe(true); + }); + + // Add more tests for error handling, edge cases, etc. + }); + // Continue with tests for other methods }); diff --git a/lib/util.js b/lib/util.js index 7b12633c..6bd70ec1 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,5 +1,93 @@ +const crypto = require("node:crypto"); + const ApiError = require("./error"); +/** + * @see {@link validateWebhook} + * @overload + * @param {object} requestData - The request data + * @param {string} requestData.id - The webhook ID header from the incoming request. + * @param {string} requestData.timestamp - The webhook timestamp header from the incoming request. + * @param {string} requestData.body - The raw body of the incoming webhook request. + * @param {string} requestData.secret - The webhook secret, obtained from `replicate.webhooks.defaul.secret` method. + * @param {string} requestData.signature - The webhook signature header from the incoming request, comprising one or more space-delimited signatures. + */ + +/** + * @see {@link validateWebhook} + * @overload + * @param {object} requestData - The request object + * @param {object} requestData.headers - The request headers + * @param {string} requestData.headers["webhook-id"] - The webhook ID header from the incoming request + * @param {string} requestData.headers["webhook-timestamp"] - The webhook timestamp header from the incoming request + * @param {string} requestData.headers["webhook-signature"] - The webhook signature header from the incoming request, comprising one or more space-delimited signatures + * @param {string} requestData.body - The raw body of the incoming webhook request + * @param {string} secret - The webhook secret, obtained from `replicate.webhooks.defaul.secret` method + */ + +/** + * Validate a webhook signature + * + * @returns {boolean} - True if the signature is valid + * @throws {Error} - If the request is missing required headers, body, or secret + */ +async function validateWebhook(requestData, secret) { + let { id, timestamp, body, signature } = requestData; + const signingSecret = secret || requestData.secret; + + if (requestData && requestData.headers && requestData.body) { + id = requestData.headers.get("webhook-id"); + timestamp = requestData.headers.get("webhook-timestamp"); + signature = requestData.headers.get("webhook-signature"); + body = requestData.body; + } + + if (body instanceof ReadableStream || body.readable) { + try { + const chunks = []; + for await (const chunk of body) { + chunks.push(Buffer.from(chunk)); + } + body = Buffer.concat(chunks).toString("utf8"); + } catch (err) { + throw new Error(`Error reading body: ${err.message}`); + } + } else if (body instanceof Buffer) { + body = body.toString("utf8"); + } else if (typeof body !== "string") { + throw new Error("Invalid body type"); + } + + if (!id || !timestamp || !signature) { + throw new Error("Missing required webhook headers"); + } + + if (!body) { + throw new Error("Missing required body"); + } + + if (!signingSecret) { + throw new Error("Missing required secret"); + } + + const signedContent = `${id}.${timestamp}.${body}`; + + const secretBytes = Buffer.from(signingSecret.split("_")[1], "base64"); + + const computedSignature = crypto + .createHmac("sha256", secretBytes) + .update(signedContent) + .digest("base64"); + + const expectedSignatures = signature + .split(" ") + .map((sig) => sig.split(",")[1]); + + return expectedSignatures.some( + (expectedSignature) => expectedSignature === computedSignature + ); +} + /** * Automatically retry a request if it fails with an appropriate status code. * @@ -68,4 +156,4 @@ async function withAutomaticRetries(request, options = {}) { return request(); } -module.exports = { withAutomaticRetries }; +module.exports = { validateWebhook, withAutomaticRetries }; diff --git a/lib/webhooks.js b/lib/webhooks.js new file mode 100644 index 00000000..f1324ec7 --- /dev/null +++ b/lib/webhooks.js @@ -0,0 +1,20 @@ +/** + * Get the default webhook signing secret + * + * @returns {Promise} Resolves with the signing secret for the default webhook + */ +async function getDefaultWebhookSecret() { + const response = await this.request("/webhooks/default/secret", { + method: "GET", + }); + + return response.json(); +} + +module.exports = { + default: { + secret: { + get: getDefaultWebhookSecret, + }, + }, +}; From 184ad2350704ebc295b1464be4fd12cdf472844e Mon Sep 17 00:00:00 2001 From: Matt Rothenberg Date: Fri, 16 Feb 2024 17:10:21 +0100 Subject: [PATCH 092/207] Add support for `deployments.get` endpoint (#206) * feat: add function for getting deployment * Use Account type for created_by property * Add newline separator in jsdoc * Update test case to match OpenAPI spec * Update operation ID for account.get * Formatting --------- Co-authored-by: Mattt Zmuda --- index.d.ts | 21 +++++++++++++++++++++ index.js | 1 + index.test.ts | 43 ++++++++++++++++++++++++++++++++++++++++++- lib/deployments.js | 19 +++++++++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index ab40ed81..69e651b1 100644 --- a/index.d.ts +++ b/index.d.ts @@ -22,6 +22,23 @@ declare module "replicate" { models?: Model[]; } + export interface Deployment { + owner: string; + name: string; + current_release: { + number: number; + model: string; + version: string; + created_at: string; + created_by: Account; + configuration: { + hardware: string; + min_instances: number; + max_instances: number; + }; + }; + } + export interface Hardware { sku: string; name: string; @@ -173,6 +190,10 @@ declare module "replicate" { } ): Promise; }; + get( + deployment_owner: string, + deployment_name: string + ): Promise; }; hardware: { diff --git a/index.js b/index.js index 13207ca2..83b98887 100644 --- a/index.js +++ b/index.js @@ -59,6 +59,7 @@ class Replicate { }; this.deployments = { + get: deployments.get.bind(this), predictions: { create: deployments.predictions.create.bind(this), }, diff --git a/index.test.ts b/index.test.ts index 106cc582..ae01338e 100644 --- a/index.test.ts +++ b/index.test.ts @@ -72,7 +72,7 @@ describe("Replicate client", () => { }); }); - describe("accounts.current", () => { + describe("account.get", () => { test("Calls the correct API route", async () => { nock(BASE_URL).get("/account").reply(200, { type: "organization", @@ -721,6 +721,47 @@ describe("Replicate client", () => { // Add more tests for error handling, edge cases, etc. }); + describe("deployments.get", () => { + test("Calls the correct API route with the correct payload", async () => { + nock(BASE_URL) + .get("/deployments/acme/my-app-image-generator") + .reply(200, { + owner: "acme", + name: "my-app-image-generator", + current_release: { + number: 1, + model: "stability-ai/sdxl", + version: + "da77bc59ee60423279fd632efb4795ab731d9e3ca9705ef3341091fb989b7eaf", + created_at: "2024-02-15T16:32:57.018467Z", + created_by: { + type: "organization", + username: "acme", + name: "Acme Corp, Inc.", + github_url: "https://github.com/acme", + }, + configuration: { + hardware: "gpu-t4", + scaling: { + min_instances: 1, + max_instances: 5, + }, + }, + }, + }); + + const deployment = await client.deployments.get( + "acme", + "my-app-image-generator" + ); + + expect(deployment.owner).toBe("acme"); + expect(deployment.name).toBe("my-app-image-generator"); + expect(deployment.current_release.model).toBe("stability-ai/sdxl"); + }); + // Add more tests for error handling, edge cases, etc. + }); + describe("predictions.create with model", () => { test("Calls the correct API route with the correct payload", async () => { nock(BASE_URL) diff --git a/lib/deployments.js b/lib/deployments.js index 6f32cdba..8ba5ea31 100644 --- a/lib/deployments.js +++ b/lib/deployments.js @@ -33,8 +33,27 @@ async function createPrediction(deployment_owner, deployment_name, options) { return response.json(); } +/** + * Get a deployment + * + * @param {string} deployment_owner - Required. The username of the user or organization who owns the deployment + * @param {string} deployment_name - Required. The name of the deployment + * @returns {Promise} Resolves with the deployment data + */ +async function getDeployment(deployment_owner, deployment_name) { + const response = await this.request( + `/deployments/${deployment_owner}/${deployment_name}`, + { + method: "GET", + } + ); + + return response.json(); +} + module.exports = { predictions: { create: createPrediction, }, + get: getDeployment, }; From 496d55a455e6dc3994a9d27f4df93d8a5799c438 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 16 Feb 2024 08:23:48 -0800 Subject: [PATCH 093/207] 0.27.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 35659fad..519bc254 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.26.0", + "version": "0.27.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.26.0", + "version": "0.27.0", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 1c8680ad..1234e6e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.26.0", + "version": "0.27.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From d09067cc79ecf2da0d7a5c67be524cfa7b0776a7 Mon Sep 17 00:00:00 2001 From: Dominik Kundel Date: Fri, 16 Feb 2024 14:58:31 -0800 Subject: [PATCH 094/207] Fix streaming example (#205) * Fix streaming example The example to use replicate.stream was wrongly checking the event type and was not producing any output. * Update README.md Co-authored-by: Mattt * Update README.md Co-authored-by: Mattt * Update README.md Removing the console.dir since it's no longer relevant --------- Co-authored-by: Mattt --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 90ed1013..6893da23 100644 --- a/README.md +++ b/README.md @@ -222,10 +222,9 @@ const options = { }; const output = []; -for await (const event of replicate.stream(model, options)) { - console.debug({ event }); - if (event && event === "output") { - output.push(event.data); +for await (const { event, data } of replicate.stream(model, options)) { + if (event === "output") { + output.push(data); } } From 839e47649a5190cd847d7b59797d56734f2f6217 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Sat, 17 Feb 2024 13:33:48 +0000 Subject: [PATCH 095/207] Automatically transform binary inputs into data URIs (#198) Co-authored-by: Mattt Zmuda --- README.md | 13 +------ index.test.ts | 48 ++++++++++++++++++++++++ lib/deployments.js | 10 ++++- lib/predictions.js | 17 +++++++-- lib/util.js | 92 +++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 163 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 6893da23..222deffd 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ console.log(prediction.output); // ['https://replicate.delivery/pbxt/RoaxeXqhL0xaYyLm6w3bpGwF5RaNBjADukfFnMbhOyeoWBdhA/out-0.png'] ``` -To run a model that takes a file input, pass a URL to a publicly accessible file. Or, for smaller files (<10MB), you can convert file data into a base64-encoded data URI and pass that directly: +To run a model that takes a file input, pass a URL to a publicly accessible file. Or, for smaller files (<10MB), you can pass the data directly. ```js const fs = require("node:fs/promises"); @@ -81,18 +81,9 @@ const fs = require("node:fs/promises"); // Or when using ESM. // import fs from "node:fs/promises"; -// Read the file into a buffer -const data = await fs.readFile("path/to/image.png"); -// Convert the buffer into a base64-encoded string -const base64 = data.toString("base64"); -// Set MIME type for PNG image -const mimeType = "image/png"; -// Create the data URI -const dataURI = `data:${mimeType};base64,${base64}`; - const model = "nightmareai/real-esrgan:42fed1c4974146d4d2414e2be2c5277c7fcf05fcc3a73abf41610695738c1d7b"; const input = { - image: dataURI, + image: await fs.readFile("path/to/image.png"), }; const output = await replicate.run(model, { input }); // ['https://replicate.delivery/mgxm/e7b0e122-9daa-410e-8cde-006c7308ff4d/output.png'] diff --git a/index.test.ts b/index.test.ts index ae01338e..f00a7e68 100644 --- a/index.test.ts +++ b/index.test.ts @@ -221,6 +221,54 @@ describe("Replicate client", () => { expect(prediction.id).toBe("ufawqhfynnddngldkgtslldrkq"); }); + test.each([ + // Skip test case if File type is not available + ...(typeof File !== "undefined" + ? [ + { + type: "file", + value: new File(["hello world"], "hello.txt", { + type: "text/plain", + }), + expected: "data:text/plain;base64,aGVsbG8gd29ybGQ=", + }, + ] + : []), + { + type: "blob", + value: new Blob(["hello world"], { type: "text/plain" }), + expected: "data:text/plain;base64,aGVsbG8gd29ybGQ=", + }, + { + type: "buffer", + value: Buffer.from("hello world"), + expected: "data:application/octet-stream;base64,aGVsbG8gd29ybGQ=", + }, + ])( + "converts a $type input into a base64 encoded string", + async ({ value: data, expected }) => { + let actual: Record | undefined; + nock(BASE_URL) + .post("/predictions") + .reply(201, (uri: string, body: Record) => { + actual = body; + return body; + }); + + await client.predictions.create({ + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + input: { + prompt: "Tell me a story", + data, + }, + stream: true, + }); + + expect(actual?.input.data).toEqual(expected); + } + ); + test("Passes stream parameter to API endpoint", async () => { nock(BASE_URL) .post("/predictions") diff --git a/lib/deployments.js b/lib/deployments.js index 8ba5ea31..3e1ceebd 100644 --- a/lib/deployments.js +++ b/lib/deployments.js @@ -1,3 +1,5 @@ +const { transformFileInputs } = require("./util"); + /** * Create a new prediction with a deployment * @@ -11,7 +13,7 @@ * @returns {Promise} Resolves with the created prediction data */ async function createPrediction(deployment_owner, deployment_name, options) { - const { stream, ...data } = options; + const { stream, input, ...data } = options; if (data.webhook) { try { @@ -26,7 +28,11 @@ async function createPrediction(deployment_owner, deployment_name, options) { `/deployments/${deployment_owner}/${deployment_name}/predictions`, { method: "POST", - data: { ...data, stream }, + data: { + ...data, + input: await transformFileInputs(input), + stream, + }, } ); diff --git a/lib/predictions.js b/lib/predictions.js index 294e8d97..5b0370e5 100644 --- a/lib/predictions.js +++ b/lib/predictions.js @@ -1,3 +1,5 @@ +const { transformFileInputs } = require("./util"); + /** * Create a new prediction * @@ -11,7 +13,7 @@ * @returns {Promise} Resolves with the created prediction */ async function createPrediction(options) { - const { model, version, stream, ...data } = options; + const { model, version, stream, input, ...data } = options; if (data.webhook) { try { @@ -26,12 +28,21 @@ async function createPrediction(options) { if (version) { response = await this.request("/predictions", { method: "POST", - data: { ...data, stream, version }, + data: { + ...data, + input: await transformFileInputs(input), + version, + stream, + }, }); } else if (model) { response = await this.request(`/models/${model}/predictions`, { method: "POST", - data: { ...data, stream }, + data: { + ...data, + input: await transformFileInputs(input), + stream, + }, }); } else { throw new Error("Either model or version must be specified"); diff --git a/lib/util.js b/lib/util.js index 6bd70ec1..48d7563c 100644 --- a/lib/util.js +++ b/lib/util.js @@ -156,4 +156,94 @@ async function withAutomaticRetries(request, options = {}) { return request(); } -module.exports = { validateWebhook, withAutomaticRetries }; +const MAX_DATA_URI_SIZE = 10_000_000; + +/** + * Walks the inputs and transforms any binary data found into a + * base64-encoded data URI. + * + * @param {object} inputs - The inputs to transform + * @returns {object} - The transformed inputs + * @throws {Error} If the size of inputs exceeds a given threshould set by MAX_DATA_URI_SIZE + */ +async function transformFileInputs(inputs) { + let totalBytes = 0; + const result = await transform(inputs, async (value) => { + let buffer; + let mime; + + if (value instanceof Blob) { + // Currently we use a NodeJS only API for base64 encoding, as + // we move to support the browser we could support either using + // btoa (which does string encoding), the FileReader API or + // a JavaScript implenentation like base64-js. + // See: https://developer.mozilla.org/en-US/docs/Glossary/Base64 + // See: https://github.com/beatgammit/base64-js + buffer = Buffer.from(await value.arrayBuffer()); + mime = value.type; + } else if (Buffer.isBuffer(value)) { + buffer = value; + } else { + return value; + } + + totalBytes += buffer.byteLength; + if (totalBytes > MAX_DATA_URI_SIZE) { + throw new Error( + `Combined filesize of prediction ${totalBytes} bytes exceeds 10mb limit for inline encoding, please provide URLs instead` + ); + } + + const data = buffer.toString("base64"); + mime = mime ?? "application/octet-stream"; + + return `data:${mime};base64,${data}`; + }); + + return result; +} + +// Walk a JavaScript object and transform the leaf values. +async function transform(value, mapper) { + if (Array.isArray(value)) { + let copy = []; + for (const val of value) { + copy = await transform(val, mapper); + } + return copy; + } + + if (isPlainObject(value)) { + const copy = {}; + for (const key of Object.keys(value)) { + copy[key] = await transform(value[key], mapper); + } + return copy; + } + + return await mapper(value); +} + +// Test for a plain JS object. +// Source: lodash.isPlainObject +function isPlainObject(value) { + const isObjectLike = typeof value === "object" && value !== null; + if (!isObjectLike || String(value) !== "[object Object]") { + return false; + } + const proto = Object.getPrototypeOf(value); + if (proto === null) { + return true; + } + const Ctor = + Object.prototype.hasOwnProperty.call(proto, "constructor") && + proto.constructor; + return ( + typeof Ctor === "function" && + Ctor instanceof Ctor && + Function.prototype.toString.call(Ctor) === + Function.prototype.toString.call(Object) + ); +} + +module.exports = { transformFileInputs, validateWebhook, withAutomaticRetries }; From 497b4342c40f27de729ac629bbbd67d5a6758563 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Sat, 17 Feb 2024 05:35:08 -0800 Subject: [PATCH 096/207] 0.27.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 519bc254..8ba3c038 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.27.0", + "version": "0.27.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.27.0", + "version": "0.27.1", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 1234e6e2..098bb3ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.27.0", + "version": "0.27.1", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From a0b8f96c3d85e4b7d9620c74bf63317da714487d Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Thu, 22 Feb 2024 12:36:40 -0800 Subject: [PATCH 097/207] Add `progress` callback example to `replicate.run` docs (#208) * add progress callback example wording * typo --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 222deffd..75ab0d24 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ const output = await replicate.run(identifier, options, progress); | `options.webhook` | string | An HTTPS URL for receiving a webhook when the prediction has new output | | `options.webhook_events_filter` | string[] | An array of events which should trigger [webhooks](https://replicate.com/docs/webhooks). Allowable values are `start`, `output`, `logs`, and `completed` | | `options.signal` | object | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the prediction | -| `progress` | function | Callback function that receives the prediction object as it's updated. The function is called when the prediction is created, each time its updated while polling for completion, and when it's completed. | +| `progress` | function | Callback function that receives the prediction object as it's updated. The function is called when the prediction is created, each time it's updated while polling for completion, and when it's completed. | Throws `Error` if the prediction failed. @@ -181,6 +181,18 @@ const input = { prompt: "a 19th century portrait of a raccoon gentleman wearing const output = await replicate.run(model, { input }); ``` +Example that logs progress as the model is running: + +```js +const model = "stability-ai/sdxl:8beff3369e81422112d93b89ca01426147de542cd4684c244b673b105188fe5f"; +const input = { prompt: "a 19th century portrait of a raccoon gentleman wearing a suit" }; +const onProgress = (prediction) => { + const last_log_line = prediction.logs.split("\n").pop() + console.log({id: prediction.id, log: last_log_line}) +} +const output = await replicate.run(model, { input }, onProgress) +``` + ### `replicate.stream` Run a model and stream its output. Unlike [`replicate.prediction.create`](#replicatepredictionscreate), this method returns only the prediction output rather than the entire prediction object. From 60a8e185e18fc81ec901dcbb42fcd135f3735e1c Mon Sep 17 00:00:00 2001 From: Mattt Date: Fri, 23 Feb 2024 06:14:53 -0800 Subject: [PATCH 098/207] Add `parseProgress` helper function (#207) * Add parsePredictionProgress helper function * Rename parsePredictionProgress to parseProgress * Annotate possible null return value in jsdoc * Annotate possible null return value in type definition * Rename parseProgress to parseProgressFromLogs * Expand documentation of parseProgressFromLogs --- index.d.ts | 6 +++ index.js | 7 +++- index.test.ts | 110 +++++++++++++++++++++++++++++++++++++++----------- lib/util.js | 53 +++++++++++++++++++++++- 4 files changed, 150 insertions(+), 26 deletions(-) diff --git a/index.d.ts b/index.d.ts index 69e651b1..8dc998a8 100644 --- a/index.d.ts +++ b/index.d.ts @@ -280,4 +280,10 @@ declare module "replicate" { }, secret: string ): boolean; + + export function parseProgressFromLogs(logs: Prediction | string): { + percentage: number; + current: number; + total: number; + } | null; } diff --git a/index.js b/index.js index 83b98887..24376fe1 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,11 @@ const ApiError = require("./lib/error"); const ModelVersionIdentifier = require("./lib/identifier"); const { Stream } = require("./lib/stream"); -const { withAutomaticRetries, validateWebhook } = require("./lib/util"); +const { + withAutomaticRetries, + validateWebhook, + parseProgressFromLogs, +} = require("./lib/util"); const accounts = require("./lib/accounts"); const collections = require("./lib/collections"); @@ -375,3 +379,4 @@ class Replicate { module.exports = Replicate; module.exports.validateWebhook = validateWebhook; +module.exports.parseProgressFromLogs = parseProgressFromLogs; diff --git a/index.test.ts b/index.test.ts index f00a7e68..97abc6fb 100644 --- a/index.test.ts +++ b/index.test.ts @@ -4,6 +4,7 @@ import Replicate, { Model, Prediction, validateWebhook, + parseProgressFromLogs, } from "replicate"; import nock from "nock"; import fetch from "cross-fetch"; @@ -888,29 +889,55 @@ describe("Replicate client", () => { }); describe("run", () => { - test("Calls the correct API routes for a version", async () => { - const firstPollingRequest = true; - + test("Calls the correct API routes", async () => { nock(BASE_URL) .post("/predictions") .reply(201, { id: "ufawqhfynnddngldkgtslldrkq", status: "starting", + logs: null, }) .get("/predictions/ufawqhfynnddngldkgtslldrkq") - .twice() .reply(200, { id: "ufawqhfynnddngldkgtslldrkq", status: "processing", + logs: [ + "Using seed: 12345", + "0%| | 0/5 [00:00 { input: { text: "Hello, world!" }, wait: { interval: 1 }, }, - progress + (prediction) => { + const progress = parseProgressFromLogs(prediction); + callback(prediction, progress); + } ); expect(output).toBe("Goodbye!"); - expect(progress).toHaveBeenNthCalledWith(1, { - id: "ufawqhfynnddngldkgtslldrkq", - status: "starting", - }); + expect(callback).toHaveBeenNthCalledWith( + 1, + { + id: "ufawqhfynnddngldkgtslldrkq", + status: "starting", + logs: null, + }, + null + ); - expect(progress).toHaveBeenNthCalledWith(2, { - id: "ufawqhfynnddngldkgtslldrkq", - status: "processing", - }); + expect(callback).toHaveBeenNthCalledWith( + 2, + { + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", + logs: expect.any(String), + }, + { + percentage: 0.4, + current: 2, + total: 5, + } + ); - expect(progress).toHaveBeenNthCalledWith(3, { - id: "ufawqhfynnddngldkgtslldrkq", - status: "processing", - }); + expect(callback).toHaveBeenNthCalledWith( + 3, + { + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", + logs: expect.any(String), + }, + { + percentage: 0.8, + current: 4, + total: 5, + } + ); - expect(progress).toHaveBeenNthCalledWith(4, { - id: "ufawqhfynnddngldkgtslldrkq", - status: "succeeded", - output: "Goodbye!", - }); + expect(callback).toHaveBeenNthCalledWith( + 4, + { + id: "ufawqhfynnddngldkgtslldrkq", + status: "succeeded", + logs: expect.any(String), + output: "Goodbye!", + }, + { + percentage: 1.0, + current: 5, + total: 5, + } + ); - expect(progress).toHaveBeenCalledTimes(4); + expect(callback).toHaveBeenCalledTimes(4); }); test("Calls the correct API routes for a model", async () => { diff --git a/lib/util.js b/lib/util.js index 48d7563c..949bafa4 100644 --- a/lib/util.js +++ b/lib/util.js @@ -246,4 +246,55 @@ function isPlainObject(value) { ); } -module.exports = { transformFileInputs, validateWebhook, withAutomaticRetries }; +/** + * Parse progress from prediction logs. + * + * This function supports log statements in the following format, + * which are generated by https://github.com/tqdm/tqdm and similar libraries: + * + * ``` + * 76%|████████████████████████████ | 7568/10000 [00:33<00:10, 229.00it/s] + * ``` + * + * @example + * const progress = parseProgressFromLogs("76%|████████████████████████████ | 7568/10000 [00:33<00:10, 229.00it/s]"); + * console.log(progress); + * // { + * // percentage: 0.76, + * // current: 7568, + * // total: 10000, + * // } + * + * @param {object|string} input - A prediction object or string. + * @returns {(object|null)} - An object with the percentage, current, and total, or null if no progress can be parsed. + */ +function parseProgressFromLogs(input) { + const logs = typeof input === "object" && input.logs ? input.logs : input; + if (!logs || typeof logs !== "string") { + return null; + } + + const pattern = /^\s*(\d+)%\s*\|.+?\|\s*(\d+)\/(\d+)/; + const lines = logs.split("\n").reverse(); + + for (const line of lines) { + const matches = line.match(pattern); + + if (matches && matches.length === 4) { + return { + percentage: parseInt(matches[1], 10) / 100, + current: parseInt(matches[2], 10), + total: parseInt(matches[3], 10), + }; + } + } + + return null; +} + +module.exports = { + transformFileInputs, + validateWebhook, + withAutomaticRetries, + parseProgressFromLogs, +}; From 4d4c548f456c99aadd26e1b1a743b964989dd501 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Tue, 5 Mar 2024 14:45:50 +0000 Subject: [PATCH 099/207] Run `npm run format` on integration files --- integration/commonjs/index.js | 4 ++-- integration/commonjs/index.test.js | 8 ++++---- integration/esm/index.js | 6 +++--- integration/esm/index.test.js | 8 ++++---- integration/typescript/index.test.ts | 12 ++++++------ integration/typescript/index.ts | 6 +++--- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/integration/commonjs/index.js b/integration/commonjs/index.js index abe859a2..40f00cd0 100644 --- a/integration/commonjs/index.js +++ b/integration/commonjs/index.js @@ -9,8 +9,8 @@ module.exports = async function main() { "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", { input: { - text: "Claire CommonJS" - } + text: "Claire CommonJS", + }, } ); }; diff --git a/integration/commonjs/index.test.js b/integration/commonjs/index.test.js index 5ef7b63c..6e0f8b2a 100644 --- a/integration/commonjs/index.test.js +++ b/integration/commonjs/index.test.js @@ -1,8 +1,8 @@ -const { test } = require('node:test'); -const assert = require('node:assert'); -const main = require('./index'); +const { test } = require("node:test"); +const assert = require("node:assert"); +const main = require("./index"); -test('main', async () => { +test("main", async () => { const output = await main(); assert.equal(output, "hello Claire CommonJS"); }); diff --git a/integration/esm/index.js b/integration/esm/index.js index 547b726f..9264f3c0 100644 --- a/integration/esm/index.js +++ b/integration/esm/index.js @@ -9,8 +9,8 @@ export default async function main() { "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", { input: { - text: "Evelyn ESM" - } + text: "Evelyn ESM", + }, } ); -}; +} diff --git a/integration/esm/index.test.js b/integration/esm/index.test.js index 2bd276fd..7549e909 100644 --- a/integration/esm/index.test.js +++ b/integration/esm/index.test.js @@ -1,8 +1,8 @@ -import { test } from 'node:test'; -import assert from 'node:assert'; -import main from './index.js'; +import { test } from "node:test"; +import assert from "node:assert"; +import main from "./index.js"; -test('main', async () => { +test("main", async () => { const output = await main(); assert.equal(output, "hello Evelyn ESM"); }); diff --git a/integration/typescript/index.test.ts b/integration/typescript/index.test.ts index be4ab90c..b7928c5d 100644 --- a/integration/typescript/index.test.ts +++ b/integration/typescript/index.test.ts @@ -1,9 +1,9 @@ -import { test } from 'node:test'; -import assert from 'node:assert'; -import main from './index.js'; +import { test } from "node:test"; +import assert from "node:assert"; +import main from "./index.js"; // Verify exported types. -import type { +import type { Status, Visibility, WebhookEventType, @@ -18,7 +18,7 @@ import type { ServerSentEvent, } from "replicate"; -test('main', async () => { +test("main", async () => { const output = await main(); assert.equal(output, "hello Tracy TypeScript"); -}); +}); diff --git a/integration/typescript/index.ts b/integration/typescript/index.ts index 8e27a3ba..e49f75c2 100644 --- a/integration/typescript/index.ts +++ b/integration/typescript/index.ts @@ -9,8 +9,8 @@ export default async function main() { "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", { input: { - text: "Tracy TypeScript" - } + text: "Tracy TypeScript", + }, } ); -}; +} From 91a35e51e217cfdac4b7d46d1d56cfc727e048b9 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Thu, 7 Mar 2024 13:31:46 +0000 Subject: [PATCH 100/207] Cloudflare Worker integration test suite (#215) Add failing cloudflare integration suite This is currently failing but acts as a starting point for verifying that we can run the Replicate library on Cloudflare workers. The test creates a cloudflare worker that uses the streaming API (as this exercises a bunch of the API surface area) and uses the experimental unstable_dev library to run the worker in a test. We then verify that the test receives a successful response. Currently we have the following issues: * `node:crypto` needs to be replaced with web crypto (node >= 15). * replace Buffer with Blob where possible or abstract behind a conditional. * `process.env` also needs to be accessed behind a conditional. Co-authored-by: Mattt --- integration/cloudflare-worker/.gitignore | 2 + integration/cloudflare-worker/index.js | 25 + integration/cloudflare-worker/index.test.js | 28 + .../cloudflare-worker/package-lock.json | 1308 +++++++++++++++++ integration/cloudflare-worker/package.json | 18 + integration/cloudflare-worker/wrangler.toml | 4 + 6 files changed, 1385 insertions(+) create mode 100644 integration/cloudflare-worker/.gitignore create mode 100644 integration/cloudflare-worker/index.js create mode 100644 integration/cloudflare-worker/index.test.js create mode 100644 integration/cloudflare-worker/package-lock.json create mode 100644 integration/cloudflare-worker/package.json create mode 100644 integration/cloudflare-worker/wrangler.toml diff --git a/integration/cloudflare-worker/.gitignore b/integration/cloudflare-worker/.gitignore new file mode 100644 index 00000000..cc27ab85 --- /dev/null +++ b/integration/cloudflare-worker/.gitignore @@ -0,0 +1,2 @@ +.wrangler +node_modules diff --git a/integration/cloudflare-worker/index.js b/integration/cloudflare-worker/index.js new file mode 100644 index 00000000..c2ce4926 --- /dev/null +++ b/integration/cloudflare-worker/index.js @@ -0,0 +1,25 @@ +import Replicate from "replicate"; + +const replicate = new Replicate(); + +export default { + async fetch(_request, _env, _ctx) { + const stream = new ReadableStream({ + async start(controller) { + for await (const event of replicate.stream( + "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + { + input: { + text: "Colin CloudFlare", + }, + } + )) { + controller.enqueue(`${event}`); + } + controller.close(); + }, + }); + + return new Response(stream); + }, +}; diff --git a/integration/cloudflare-worker/index.test.js b/integration/cloudflare-worker/index.test.js new file mode 100644 index 00000000..ce1486f5 --- /dev/null +++ b/integration/cloudflare-worker/index.test.js @@ -0,0 +1,28 @@ +// https://developers.cloudflare.com/workers/wrangler/api/#unstable_dev +import { unstable_dev as dev } from "wrangler"; +import { test, after, before } from "node:test"; +import assert from "node:assert"; + +let worker; + +before(async () => { + worker = await dev("index.js", { + experimental: { disableExperimentalWarning: true }, + }); +}); + +after(async () => { + if (!worker) { + // If no worker the before hook failed to run and the process will hang. + process.exit(1); + } + await worker.stop(); +}); + +test("worker streams back a response", { timeout: 1000 }, async (t) => { + const resp = await worker.fetch("/", { signal: t.signal }); + const text = await resp.text(); + + assert.ok(resp.ok, "status is 2xx"); + assert(text.length > 0, "body.length is greater than 0"); +}); diff --git a/integration/cloudflare-worker/package-lock.json b/integration/cloudflare-worker/package-lock.json new file mode 100644 index 00000000..ae692d95 --- /dev/null +++ b/integration/cloudflare-worker/package-lock.json @@ -0,0 +1,1308 @@ +{ + "name": "cloudflare-worker", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cloudflare-worker", + "version": "0.0.0", + "dependencies": { + "replicate": "file:../../" + }, + "devDependencies": { + "wrangler": "^3.0.0" + } + }, + "../..": { + "version": "0.27.1", + "license": "Apache-2.0", + "dependencies": { + "eventsource-parser": "^1.1.2" + }, + "devDependencies": { + "@biomejs/biome": "^1.4.1", + "@types/jest": "^29.5.3", + "@typescript-eslint/eslint-plugin": "^5.56.0", + "cross-fetch": "^3.1.5", + "jest": "^29.6.2", + "nock": "^14.0.0-beta.4", + "publint": "^0.2.7", + "ts-jest": "^29.1.0", + "typescript": "^5.0.2" + }, + "engines": { + "git": ">=2.11.0", + "node": ">=18.0.0", + "npm": ">=7.19.0", + "yarn": ">=1.7.0" + }, + "optionalDependencies": { + "readable-stream": ">=4.0.0" + } + }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.1.tgz", + "integrity": "sha512-lKN2XCfKCmpKb86a1tl4GIwsJYDy9TGuwjhDELLmpKygQhw8X2xR4dusgpC5Tg7q1pB96Eb0rBo81kxSILQMwA==", + "dev": true, + "dependencies": { + "mime": "^3.0.0" + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20240223.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20240223.1.tgz", + "integrity": "sha512-GgHnvkazLFZ7bmR96+dTX0+WS13a+5CHOOP3qNUSR9oEnR4hHzpNIO75MuZsm9RPAXrvtT7nSJmYwiGCZXh6og==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20240223.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20240223.1.tgz", + "integrity": "sha512-ZF98vUmVlC0EVEd3RRuhMq4HYWFcqmPtMIMPUN2+ivEHR92TE+6E/AvdeE6wcE7fKHQ+fk3dH+ZgB0GcfptfnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20240223.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20240223.1.tgz", + "integrity": "sha512-1kH41ewNTGMmAk2zUX0Xj9VSfidl26GQ0ZrWMdi5kwf6gAHd3oVWNigJN078Jx56SgQxNcqVGX1LunqF949asw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20240223.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20240223.1.tgz", + "integrity": "sha512-Ro8Og5C4evh890JrRm0B8sHyumRtgL+mUqPeNcEsyG45jAQy5xHpapHnmJAMJV6ah+zDc1cZtQq+en39SojXvQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20240223.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20240223.1.tgz", + "integrity": "sha512-eNP5sfaP6WL07DaoigYou5ASPF7jHsFiNzzD2vGOI7yFd5sPlb7sJ4SpIy+BCX0LdqFnjmlUo5Xr+/I6qJ2Nww==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-plugins/node-globals-polyfill": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz", + "integrity": "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==", + "dev": true, + "peerDependencies": { + "esbuild": "*" + } + }, + "node_modules/@esbuild-plugins/node-modules-polyfill": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz", + "integrity": "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^4.0.0", + "rollup-plugin-node-polyfills": "^0.2.1" + }, + "peerDependencies": { + "esbuild": "*" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@types/node": { + "version": "20.11.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.24.tgz", + "integrity": "sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/as-table": { + "version": "1.0.55", + "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", + "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", + "dev": true, + "dependencies": { + "printable-characters": "^1.0.42" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "dev": true + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/capnp-ts": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/capnp-ts/-/capnp-ts-0.7.0.tgz", + "integrity": "sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==", + "dev": true, + "dependencies": { + "debug": "^4.3.1", + "tslib": "^2.2.0" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", + "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + }, + "node_modules/exit-hook": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", + "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-source": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", + "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", + "dev": true, + "dependencies": { + "data-uri-to-buffer": "^2.0.0", + "source-map": "^0.6.1" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/hasown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/miniflare": { + "version": "3.20240223.1", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240223.1.tgz", + "integrity": "sha512-5pP4Kml9JtPpCMhfVkTyzNen08bKl2swxZ6i+gYzQTo/Rcjk6neLqgVGG64JFeJFKzdGEczk5Hnwl9U04OGRiw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "acorn": "^8.8.0", + "acorn-walk": "^8.2.0", + "capnp-ts": "^0.7.0", + "exit-hook": "^2.2.1", + "glob-to-regexp": "^0.4.1", + "stoppable": "^1.1.0", + "undici": "^5.28.2", + "workerd": "1.20240223.1", + "ws": "^8.11.0", + "youch": "^3.2.2", + "zod": "^3.20.6" + }, + "bin": { + "miniflare": "bootstrap.js" + }, + "engines": { + "node": ">=16.13" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "dev": true, + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/printable-characters": { + "version": "1.0.42", + "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", + "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", + "dev": true + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/replicate": { + "resolved": "../..", + "link": true + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/rollup-plugin-inject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz", + "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.", + "dev": true, + "dependencies": { + "estree-walker": "^0.6.1", + "magic-string": "^0.25.3", + "rollup-pluginutils": "^2.8.1" + } + }, + "node_modules/rollup-plugin-node-polyfills": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz", + "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==", + "dev": true, + "dependencies": { + "rollup-plugin-inject": "^3.0.0" + } + }, + "node_modules/rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "dependencies": { + "estree-walker": "^0.6.1" + } + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "dev": true + }, + "node_modules/stacktracey": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", + "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", + "dev": true, + "dependencies": { + "as-table": "^1.0.36", + "get-source": "^2.0.12" + } + }, + "node_modules/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "dev": true, + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/undici": { + "version": "5.28.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz", + "integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==", + "dev": true, + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/workerd": { + "version": "1.20240223.1", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20240223.1.tgz", + "integrity": "sha512-Mo1fwdp6DLva4/fWdL09ZdYllkO45I4YpWG5PbF/YUGFlu2aMk24fmU6Pd6fo5/cWek4F+n3LmYEKKHfqjiJIA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20240223.1", + "@cloudflare/workerd-darwin-arm64": "1.20240223.1", + "@cloudflare/workerd-linux-64": "1.20240223.1", + "@cloudflare/workerd-linux-arm64": "1.20240223.1", + "@cloudflare/workerd-windows-64": "1.20240223.1" + } + }, + "node_modules/wrangler": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.31.0.tgz", + "integrity": "sha512-tanV8E8iD/REmWYkpmyDd/5lUFWBox85KCKRsPU/8biiHZH9VD3/RXJs6837TeSHAStfxV6EpF42U2OdU+trNg==", + "dev": true, + "dependencies": { + "@cloudflare/kv-asset-handler": "0.3.1", + "@esbuild-plugins/node-globals-polyfill": "^0.2.3", + "@esbuild-plugins/node-modules-polyfill": "^0.2.2", + "blake3-wasm": "^2.1.5", + "chokidar": "^3.5.3", + "esbuild": "0.17.19", + "miniflare": "3.20240223.1", + "nanoid": "^3.3.3", + "path-to-regexp": "^6.2.0", + "resolve": "^1.22.8", + "resolve.exports": "^2.0.2", + "selfsigned": "^2.0.1", + "source-map": "0.6.1", + "xxhash-wasm": "^1.0.1" + }, + "bin": { + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=16.17.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20230914.0" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xxhash-wasm": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.0.2.tgz", + "integrity": "sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A==", + "dev": true + }, + "node_modules/youch": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.3.tgz", + "integrity": "sha512-qSFXUk3UZBLfggAW3dJKg0BMblG5biqSF8M34E06o5CSsZtH92u9Hqmj2RzGiHDi64fhe83+4tENFP2DB6t6ZA==", + "dev": true, + "dependencies": { + "cookie": "^0.5.0", + "mustache": "^4.2.0", + "stacktracey": "^2.1.8" + } + }, + "node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/integration/cloudflare-worker/package.json b/integration/cloudflare-worker/package.json new file mode 100644 index 00000000..4885e82f --- /dev/null +++ b/integration/cloudflare-worker/package.json @@ -0,0 +1,18 @@ +{ + "name": "replicate-app-cloudflare", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "deploy": "wrangler deploy", + "dev": "wrangler dev", + "start": "wrangler dev", + "test": "node --test index.test.js" + }, + "dependencies": { + "replicate": "file:../../" + }, + "devDependencies": { + "wrangler": "^3.0.0" + } +} diff --git a/integration/cloudflare-worker/wrangler.toml b/integration/cloudflare-worker/wrangler.toml new file mode 100644 index 00000000..4fbf2093 --- /dev/null +++ b/integration/cloudflare-worker/wrangler.toml @@ -0,0 +1,4 @@ +name = "cloudflare-worker" +main = "index.js" +compatibility_date = "2024-03-04" +compatibility_flags = [ "nodejs_compat" ] From cbfcc62c86e5113aaca84ca96bcc2b9ea9203db0 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Thu, 7 Mar 2024 18:03:06 +0000 Subject: [PATCH 101/207] Remove dependency on node:crypto for verifying webhooks (#216) --- lib/util.js | 68 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/lib/util.js b/lib/util.js index 949bafa4..a9406e33 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,5 +1,3 @@ -const crypto = require("node:crypto"); - const ApiError = require("./error"); /** @@ -72,12 +70,10 @@ async function validateWebhook(requestData, secret) { const signedContent = `${id}.${timestamp}.${body}`; - const secretBytes = Buffer.from(signingSecret.split("_")[1], "base64"); - - const computedSignature = crypto - .createHmac("sha256", secretBytes) - .update(signedContent) - .digest("base64"); + const computedSignature = await createHMACSHA256( + signingSecret.split("_").pop(), + signedContent + ); const expectedSignatures = signature .split(" ") @@ -88,6 +84,62 @@ async function validateWebhook(requestData, secret) { ); } +/** + * @param {string} secret - base64 encoded string + * @param {string} data - text body of request + */ +async function createHMACSHA256(secret, data) { + const encoder = new TextEncoder(); + let crypto = globalThis.crypto; + + // In Node 18 the `crypto` global is behind a --no-experimental-global-webcrypto flag + if (typeof crypto === "undefined" && typeof require === "function") { + crypto = require("node:crypto").webcrypto; + } + + const key = await crypto.subtle.importKey( + "raw", + base64ToBytes(secret), + { name: "HMAC", hash: "SHA-256" }, + false, + ["sign"] + ); + + const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(data)); + return bytesToBase64(signature); +} + +/** + * Convert a base64 encoded string into bytes. + * + * @param {string} the base64 encoded string + * @return {Uint8Array} + * + * Two functions for encoding/decoding base64 strings using web standards. Not + * intended to be used to encode/decode arbitrary string data. + * See: https://developer.mozilla.org/en-US/docs/Glossary/Base64#javascript_support + * See: https://stackoverflow.com/a/31621532 + * + * Performance might take a hit because of the conversion to string and then to binary, + * if this is the case we might want to look at an alternative solution. + * See: https://jsben.ch/wnaZC + */ +function base64ToBytes(base64) { + return Uint8Array.from(atob(base64), (m) => m.codePointAt(0)); +} + +/** + * Convert a base64 encoded string into bytes. + * + * See {@link base64ToBytes} for caveats. + * + * @param {Uint8Array | ArrayBuffer} the base64 encoded string + * @return {string} + */ +function bytesToBase64(bytes) { + return btoa(String.fromCharCode.apply(null, new Uint8Array(bytes))); +} + /** * Automatically retry a request if it fails with an appropriate status code. * From 742c664a9cefd12df8d74f65f5039bc52f1d730a Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Tue, 12 Mar 2024 09:11:27 +0000 Subject: [PATCH 102/207] Support CloudFlare workers (#217) * Update integration tests to exclude package-lock.json * Update CloudFlare worker example to test using streaming LLM * Run the CloudFlare worker integration test in CI * Add support for CloudFlare workers --- .github/workflows/ci.yml | 4 +- index.js | 4 +- integration/cloudflare-worker/.gitignore | 2 + integration/cloudflare-worker/.npmrc | 2 + integration/cloudflare-worker/index.js | 42 +- integration/cloudflare-worker/index.test.js | 44 +- .../cloudflare-worker/package-lock.json | 1308 ----------------- integration/cloudflare-worker/package.json | 2 +- integration/commonjs/.npmrc | 2 + integration/commonjs/package-lock.json | 42 - integration/esm/.npmrc | 2 + integration/esm/package-lock.json | 43 - integration/typescript/.npmrc | 2 + integration/typescript/package-lock.json | 70 - lib/util.js | 30 +- 15 files changed, 87 insertions(+), 1512 deletions(-) create mode 100644 integration/cloudflare-worker/.npmrc delete mode 100644 integration/cloudflare-worker/package-lock.json create mode 100644 integration/commonjs/.npmrc delete mode 100644 integration/commonjs/package-lock.json create mode 100644 integration/esm/.npmrc delete mode 100644 integration/esm/package-lock.json create mode 100644 integration/typescript/.npmrc delete mode 100644 integration/typescript/package-lock.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b42984ce..cc4d90e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: strategy: matrix: node-version: [18.x, 20.x] - suite: [commonjs, esm, typescript] + suite: [commonjs, esm, typescript, cloudflare-worker] # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases env: @@ -49,6 +49,8 @@ jobs: cache: "npm" # Build a production tarball and run the integration tests against it. - run: | + test "${{ matrix.suite }}" = "cloudflare-worker" && echo "REPLICATE_API_TOKEN=${{ secrets.REPLICATE_API_TOKEN }}" > integration/${{ matrix.suite }}/.dev.vars PKG_TARBALL=$(npm --loglevel error pack) + npm --prefix integration/${{ matrix.suite }} install npm --prefix integration/${{ matrix.suite }} install "file:/./$PKG_TARBALL" npm --prefix integration/${{ matrix.suite }} test diff --git a/index.js b/index.js index 24376fe1..8f56ab5a 100644 --- a/index.js +++ b/index.js @@ -47,7 +47,9 @@ class Replicate { * @param {Function} [options.fetch] - Fetch function to use. Defaults to `globalThis.fetch` */ constructor(options = {}) { - this.auth = options.auth || process.env.REPLICATE_API_TOKEN; + this.auth = + options.auth || + (typeof process !== "undefined" ? process.env.REPLICATE_API_TOKEN : null); this.userAgent = options.userAgent || `replicate-javascript/${packageJSON.version}`; this.baseUrl = options.baseUrl || "https://api.replicate.com/v1"; diff --git a/integration/cloudflare-worker/.gitignore b/integration/cloudflare-worker/.gitignore index cc27ab85..8394e05f 100644 --- a/integration/cloudflare-worker/.gitignore +++ b/integration/cloudflare-worker/.gitignore @@ -1,2 +1,4 @@ .wrangler node_modules +.dev.vars + diff --git a/integration/cloudflare-worker/.npmrc b/integration/cloudflare-worker/.npmrc new file mode 100644 index 00000000..b15cbc2c --- /dev/null +++ b/integration/cloudflare-worker/.npmrc @@ -0,0 +1,2 @@ +package-lock=false + diff --git a/integration/cloudflare-worker/index.js b/integration/cloudflare-worker/index.js index c2ce4926..be18d53c 100644 --- a/integration/cloudflare-worker/index.js +++ b/integration/cloudflare-worker/index.js @@ -1,25 +1,31 @@ import Replicate from "replicate"; -const replicate = new Replicate(); - export default { - async fetch(_request, _env, _ctx) { - const stream = new ReadableStream({ - async start(controller) { - for await (const event of replicate.stream( - "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", - { - input: { - text: "Colin CloudFlare", - }, - } - )) { - controller.enqueue(`${event}`); + async fetch(_request, env, _ctx) { + const replicate = new Replicate({ auth: env.REPLICATE_API_TOKEN }); + + try { + const output = replicate.stream( + "replicate/canary:30e22229542eb3f79d4f945dacb58d32001b02cc313ae6f54eef27904edf3272", + { + input: { + text: "Colin CloudFlare", + }, } - controller.close(); - }, - }); + ); + const stream = new ReadableStream({ + async start(controller) { + for await (const event of output) { + controller.enqueue(new TextEncoder().encode(`${event}`)); + } + controller.enqueue(new TextEncoder().encode("\n")); + controller.close(); + }, + }); - return new Response(stream); + return new Response(stream); + } catch (err) { + return Response("", { status: 500 }); + } }, }; diff --git a/integration/cloudflare-worker/index.test.js b/integration/cloudflare-worker/index.test.js index ce1486f5..0c0fc5e2 100644 --- a/integration/cloudflare-worker/index.test.js +++ b/integration/cloudflare-worker/index.test.js @@ -1,28 +1,36 @@ // https://developers.cloudflare.com/workers/wrangler/api/#unstable_dev import { unstable_dev as dev } from "wrangler"; -import { test, after, before } from "node:test"; +import { test, after, before, describe } from "node:test"; import assert from "node:assert"; -let worker; +/** @type {import("wrangler").UnstableDevWorker} */ +describe("CloudFlare Worker", () => { + let worker; -before(async () => { - worker = await dev("index.js", { - experimental: { disableExperimentalWarning: true }, + before(async () => { + worker = await dev("./index.js", { + port: 3000, + experimental: { disableExperimentalWarning: true }, + }); }); -}); -after(async () => { - if (!worker) { - // If no worker the before hook failed to run and the process will hang. - process.exit(1); - } - await worker.stop(); -}); + after(async () => { + if (!worker) { + // If no worker the before hook failed to run and the process will hang. + process.exit(1); + } + await worker.stop(); + }); -test("worker streams back a response", { timeout: 1000 }, async (t) => { - const resp = await worker.fetch("/", { signal: t.signal }); - const text = await resp.text(); + test("worker streams back a response", { timeout: 1000 }, async () => { + const resp = await worker.fetch(); + const text = await resp.text(); - assert.ok(resp.ok, "status is 2xx"); - assert(text.length > 0, "body.length is greater than 0"); + assert.ok(resp.ok, "status is 2xx"); + assert(text.length > 0, "body.length is greater than 0"); + assert( + text.includes("Colin CloudFlare"), + "body includes stream characters" + ); + }); }); diff --git a/integration/cloudflare-worker/package-lock.json b/integration/cloudflare-worker/package-lock.json deleted file mode 100644 index ae692d95..00000000 --- a/integration/cloudflare-worker/package-lock.json +++ /dev/null @@ -1,1308 +0,0 @@ -{ - "name": "cloudflare-worker", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "cloudflare-worker", - "version": "0.0.0", - "dependencies": { - "replicate": "file:../../" - }, - "devDependencies": { - "wrangler": "^3.0.0" - } - }, - "../..": { - "version": "0.27.1", - "license": "Apache-2.0", - "dependencies": { - "eventsource-parser": "^1.1.2" - }, - "devDependencies": { - "@biomejs/biome": "^1.4.1", - "@types/jest": "^29.5.3", - "@typescript-eslint/eslint-plugin": "^5.56.0", - "cross-fetch": "^3.1.5", - "jest": "^29.6.2", - "nock": "^14.0.0-beta.4", - "publint": "^0.2.7", - "ts-jest": "^29.1.0", - "typescript": "^5.0.2" - }, - "engines": { - "git": ">=2.11.0", - "node": ">=18.0.0", - "npm": ">=7.19.0", - "yarn": ">=1.7.0" - }, - "optionalDependencies": { - "readable-stream": ">=4.0.0" - } - }, - "node_modules/@cloudflare/kv-asset-handler": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.1.tgz", - "integrity": "sha512-lKN2XCfKCmpKb86a1tl4GIwsJYDy9TGuwjhDELLmpKygQhw8X2xR4dusgpC5Tg7q1pB96Eb0rBo81kxSILQMwA==", - "dev": true, - "dependencies": { - "mime": "^3.0.0" - } - }, - "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20240223.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20240223.1.tgz", - "integrity": "sha512-GgHnvkazLFZ7bmR96+dTX0+WS13a+5CHOOP3qNUSR9oEnR4hHzpNIO75MuZsm9RPAXrvtT7nSJmYwiGCZXh6og==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20240223.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20240223.1.tgz", - "integrity": "sha512-ZF98vUmVlC0EVEd3RRuhMq4HYWFcqmPtMIMPUN2+ivEHR92TE+6E/AvdeE6wcE7fKHQ+fk3dH+ZgB0GcfptfnA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20240223.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20240223.1.tgz", - "integrity": "sha512-1kH41ewNTGMmAk2zUX0Xj9VSfidl26GQ0ZrWMdi5kwf6gAHd3oVWNigJN078Jx56SgQxNcqVGX1LunqF949asw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20240223.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20240223.1.tgz", - "integrity": "sha512-Ro8Og5C4evh890JrRm0B8sHyumRtgL+mUqPeNcEsyG45jAQy5xHpapHnmJAMJV6ah+zDc1cZtQq+en39SojXvQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20240223.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20240223.1.tgz", - "integrity": "sha512-eNP5sfaP6WL07DaoigYou5ASPF7jHsFiNzzD2vGOI7yFd5sPlb7sJ4SpIy+BCX0LdqFnjmlUo5Xr+/I6qJ2Nww==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-plugins/node-globals-polyfill": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz", - "integrity": "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==", - "dev": true, - "peerDependencies": { - "esbuild": "*" - } - }, - "node_modules/@esbuild-plugins/node-modules-polyfill": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz", - "integrity": "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^4.0.0", - "rollup-plugin-node-polyfills": "^0.2.1" - }, - "peerDependencies": { - "esbuild": "*" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", - "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", - "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", - "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", - "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", - "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", - "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", - "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", - "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", - "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", - "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", - "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", - "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", - "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", - "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", - "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", - "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", - "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", - "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", - "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", - "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", - "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", - "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "dev": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@types/node": { - "version": "20.11.24", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.24.tgz", - "integrity": "sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/node-forge": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", - "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/as-table": { - "version": "1.0.55", - "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", - "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", - "dev": true, - "dependencies": { - "printable-characters": "^1.0.42" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/blake3-wasm": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", - "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", - "dev": true - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/capnp-ts": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/capnp-ts/-/capnp-ts-0.7.0.tgz", - "integrity": "sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==", - "dev": true, - "dependencies": { - "debug": "^4.3.1", - "tslib": "^2.2.0" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", - "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", - "dev": true - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/esbuild": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", - "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.17.19", - "@esbuild/android-arm64": "0.17.19", - "@esbuild/android-x64": "0.17.19", - "@esbuild/darwin-arm64": "0.17.19", - "@esbuild/darwin-x64": "0.17.19", - "@esbuild/freebsd-arm64": "0.17.19", - "@esbuild/freebsd-x64": "0.17.19", - "@esbuild/linux-arm": "0.17.19", - "@esbuild/linux-arm64": "0.17.19", - "@esbuild/linux-ia32": "0.17.19", - "@esbuild/linux-loong64": "0.17.19", - "@esbuild/linux-mips64el": "0.17.19", - "@esbuild/linux-ppc64": "0.17.19", - "@esbuild/linux-riscv64": "0.17.19", - "@esbuild/linux-s390x": "0.17.19", - "@esbuild/linux-x64": "0.17.19", - "@esbuild/netbsd-x64": "0.17.19", - "@esbuild/openbsd-x64": "0.17.19", - "@esbuild/sunos-x64": "0.17.19", - "@esbuild/win32-arm64": "0.17.19", - "@esbuild/win32-ia32": "0.17.19", - "@esbuild/win32-x64": "0.17.19" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", - "dev": true - }, - "node_modules/exit-hook": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", - "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-source": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", - "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", - "dev": true, - "dependencies": { - "data-uri-to-buffer": "^2.0.0", - "source-map": "^0.6.1" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "node_modules/hasown": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", - "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/miniflare": { - "version": "3.20240223.1", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240223.1.tgz", - "integrity": "sha512-5pP4Kml9JtPpCMhfVkTyzNen08bKl2swxZ6i+gYzQTo/Rcjk6neLqgVGG64JFeJFKzdGEczk5Hnwl9U04OGRiw==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "0.8.1", - "acorn": "^8.8.0", - "acorn-walk": "^8.2.0", - "capnp-ts": "^0.7.0", - "exit-hook": "^2.2.1", - "glob-to-regexp": "^0.4.1", - "stoppable": "^1.1.0", - "undici": "^5.28.2", - "workerd": "1.20240223.1", - "ws": "^8.11.0", - "youch": "^3.2.2", - "zod": "^3.20.6" - }, - "bin": { - "miniflare": "bootstrap.js" - }, - "engines": { - "node": ">=16.13" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/mustache": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", - "dev": true, - "bin": { - "mustache": "bin/mustache" - } - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "dev": true, - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-to-regexp": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", - "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/printable-characters": { - "version": "1.0.42", - "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", - "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", - "dev": true - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/replicate": { - "resolved": "../..", - "link": true - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/rollup-plugin-inject": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz", - "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==", - "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.", - "dev": true, - "dependencies": { - "estree-walker": "^0.6.1", - "magic-string": "^0.25.3", - "rollup-pluginutils": "^2.8.1" - } - }, - "node_modules/rollup-plugin-node-polyfills": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz", - "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==", - "dev": true, - "dependencies": { - "rollup-plugin-inject": "^3.0.0" - } - }, - "node_modules/rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dev": true, - "dependencies": { - "estree-walker": "^0.6.1" - } - }, - "node_modules/selfsigned": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", - "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", - "dev": true, - "dependencies": { - "@types/node-forge": "^1.3.0", - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "dev": true - }, - "node_modules/stacktracey": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", - "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", - "dev": true, - "dependencies": { - "as-table": "^1.0.36", - "get-source": "^2.0.12" - } - }, - "node_modules/stoppable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", - "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", - "dev": true, - "engines": { - "node": ">=4", - "npm": ">=6" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, - "node_modules/undici": { - "version": "5.28.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz", - "integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==", - "dev": true, - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, - "engines": { - "node": ">=14.0" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, - "node_modules/workerd": { - "version": "1.20240223.1", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20240223.1.tgz", - "integrity": "sha512-Mo1fwdp6DLva4/fWdL09ZdYllkO45I4YpWG5PbF/YUGFlu2aMk24fmU6Pd6fo5/cWek4F+n3LmYEKKHfqjiJIA==", - "dev": true, - "hasInstallScript": true, - "bin": { - "workerd": "bin/workerd" - }, - "engines": { - "node": ">=16" - }, - "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20240223.1", - "@cloudflare/workerd-darwin-arm64": "1.20240223.1", - "@cloudflare/workerd-linux-64": "1.20240223.1", - "@cloudflare/workerd-linux-arm64": "1.20240223.1", - "@cloudflare/workerd-windows-64": "1.20240223.1" - } - }, - "node_modules/wrangler": { - "version": "3.31.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.31.0.tgz", - "integrity": "sha512-tanV8E8iD/REmWYkpmyDd/5lUFWBox85KCKRsPU/8biiHZH9VD3/RXJs6837TeSHAStfxV6EpF42U2OdU+trNg==", - "dev": true, - "dependencies": { - "@cloudflare/kv-asset-handler": "0.3.1", - "@esbuild-plugins/node-globals-polyfill": "^0.2.3", - "@esbuild-plugins/node-modules-polyfill": "^0.2.2", - "blake3-wasm": "^2.1.5", - "chokidar": "^3.5.3", - "esbuild": "0.17.19", - "miniflare": "3.20240223.1", - "nanoid": "^3.3.3", - "path-to-regexp": "^6.2.0", - "resolve": "^1.22.8", - "resolve.exports": "^2.0.2", - "selfsigned": "^2.0.1", - "source-map": "0.6.1", - "xxhash-wasm": "^1.0.1" - }, - "bin": { - "wrangler": "bin/wrangler.js", - "wrangler2": "bin/wrangler.js" - }, - "engines": { - "node": ">=16.17.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "@cloudflare/workers-types": "^4.20230914.0" - }, - "peerDependenciesMeta": { - "@cloudflare/workers-types": { - "optional": true - } - } - }, - "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xxhash-wasm": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.0.2.tgz", - "integrity": "sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A==", - "dev": true - }, - "node_modules/youch": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.3.tgz", - "integrity": "sha512-qSFXUk3UZBLfggAW3dJKg0BMblG5biqSF8M34E06o5CSsZtH92u9Hqmj2RzGiHDi64fhe83+4tENFP2DB6t6ZA==", - "dev": true, - "dependencies": { - "cookie": "^0.5.0", - "mustache": "^4.2.0", - "stacktracey": "^2.1.8" - } - }, - "node_modules/zod": { - "version": "3.22.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", - "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/integration/cloudflare-worker/package.json b/integration/cloudflare-worker/package.json index 4885e82f..94ae94c1 100644 --- a/integration/cloudflare-worker/package.json +++ b/integration/cloudflare-worker/package.json @@ -13,6 +13,6 @@ "replicate": "file:../../" }, "devDependencies": { - "wrangler": "^3.0.0" + "wrangler": "^3.32.0" } } diff --git a/integration/commonjs/.npmrc b/integration/commonjs/.npmrc new file mode 100644 index 00000000..b15cbc2c --- /dev/null +++ b/integration/commonjs/.npmrc @@ -0,0 +1,2 @@ +package-lock=false + diff --git a/integration/commonjs/package-lock.json b/integration/commonjs/package-lock.json deleted file mode 100644 index 1584af5e..00000000 --- a/integration/commonjs/package-lock.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "replicate-app-commonjs", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "replicate-app-commonjs", - "version": "0.0.0", - "dependencies": { - "replicate": "file:../../" - } - }, - "../..": { - "version": "0.25.2", - "license": "Apache-2.0", - "devDependencies": { - "@biomejs/biome": "^1.4.1", - "@types/jest": "^29.5.3", - "@typescript-eslint/eslint-plugin": "^5.56.0", - "cross-fetch": "^3.1.5", - "jest": "^29.6.2", - "nock": "^13.3.0", - "ts-jest": "^29.1.0", - "typescript": "^5.0.2" - }, - "engines": { - "git": ">=2.11.0", - "node": ">=18.0.0", - "npm": ">=7.19.0", - "yarn": ">=1.7.0" - }, - "optionalDependencies": { - "readable-stream": ">=4.0.0" - } - }, - "node_modules/replicate": { - "resolved": "../..", - "link": true - } - } -} diff --git a/integration/esm/.npmrc b/integration/esm/.npmrc new file mode 100644 index 00000000..b15cbc2c --- /dev/null +++ b/integration/esm/.npmrc @@ -0,0 +1,2 @@ +package-lock=false + diff --git a/integration/esm/package-lock.json b/integration/esm/package-lock.json deleted file mode 100644 index 2a17c888..00000000 --- a/integration/esm/package-lock.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "replicate-app-esm", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "replicate-app-esm", - "version": "0.0.0", - "dependencies": { - "replicate": "file:../../" - } - }, - "../..": { - "name": "replicate", - "version": "0.25.2", - "license": "Apache-2.0", - "devDependencies": { - "@biomejs/biome": "^1.4.1", - "@types/jest": "^29.5.3", - "@typescript-eslint/eslint-plugin": "^5.56.0", - "cross-fetch": "^3.1.5", - "jest": "^29.6.2", - "nock": "^13.3.0", - "ts-jest": "^29.1.0", - "typescript": "^5.0.2" - }, - "engines": { - "git": ">=2.11.0", - "node": ">=18.0.0", - "npm": ">=7.19.0", - "yarn": ">=1.7.0" - }, - "optionalDependencies": { - "readable-stream": ">=4.0.0" - } - }, - "node_modules/replicate": { - "resolved": "../..", - "link": true - } - } -} diff --git a/integration/typescript/.npmrc b/integration/typescript/.npmrc new file mode 100644 index 00000000..b15cbc2c --- /dev/null +++ b/integration/typescript/.npmrc @@ -0,0 +1,2 @@ +package-lock=false + diff --git a/integration/typescript/package-lock.json b/integration/typescript/package-lock.json deleted file mode 100644 index f309b1b1..00000000 --- a/integration/typescript/package-lock.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "name": "replicate-app-esm", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "replicate-app-esm", - "version": "0.0.0", - "dependencies": { - "@types/node": "^20.11.0", - "replicate": "file:../../", - "typescript": "^5.3.3" - } - }, - "../..": { - "name": "replicate", - "version": "0.25.2", - "license": "Apache-2.0", - "devDependencies": { - "@biomejs/biome": "^1.4.1", - "@types/jest": "^29.5.3", - "@typescript-eslint/eslint-plugin": "^5.56.0", - "cross-fetch": "^3.1.5", - "jest": "^29.6.2", - "nock": "^13.3.0", - "ts-jest": "^29.1.0", - "typescript": "^5.0.2" - }, - "engines": { - "git": ">=2.11.0", - "node": ">=18.0.0", - "npm": ">=7.19.0", - "yarn": ">=1.7.0" - }, - "optionalDependencies": { - "readable-stream": ">=4.0.0" - } - }, - "node_modules/@types/node": { - "version": "20.11.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz", - "integrity": "sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/replicate": { - "resolved": "../..", - "link": true - }, - "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - } - } -} diff --git a/lib/util.js b/lib/util.js index a9406e33..ac99e082 100644 --- a/lib/util.js +++ b/lib/util.js @@ -42,16 +42,12 @@ async function validateWebhook(requestData, secret) { if (body instanceof ReadableStream || body.readable) { try { - const chunks = []; - for await (const chunk of body) { - chunks.push(Buffer.from(chunk)); - } - body = Buffer.concat(chunks).toString("utf8"); + body = await new Response(body).text(); } catch (err) { throw new Error(`Error reading body: ${err.message}`); } - } else if (body instanceof Buffer) { - body = body.toString("utf8"); + } else if (isTypedArray(body)) { + body = await new Blob([body]).text(); } else if (typeof body !== "string") { throw new Error("Invalid body type"); } @@ -231,9 +227,9 @@ async function transformFileInputs(inputs) { // a JavaScript implenentation like base64-js. // See: https://developer.mozilla.org/en-US/docs/Glossary/Base64 // See: https://github.com/beatgammit/base64-js - buffer = Buffer.from(await value.arrayBuffer()); + buffer = await value.arrayBuffer(); mime = value.type; - } else if (Buffer.isBuffer(value)) { + } else if (isTypedArray(value)) { buffer = value; } else { return value; @@ -246,7 +242,7 @@ async function transformFileInputs(inputs) { ); } - const data = buffer.toString("base64"); + const data = bytesToBase64(buffer); mime = mime ?? "application/octet-stream"; return `data:${mime};base64,${data}`; @@ -276,6 +272,20 @@ async function transform(value, mapper) { return await mapper(value); } +function isTypedArray(arr) { + return ( + arr instanceof Int8Array || + arr instanceof Int16Array || + arr instanceof Int32Array || + arr instanceof Uint8Array || + arr instanceof Uint8ClampedArray || + arr instanceof Uint16Array || + arr instanceof Uint32Array || + arr instanceof Float32Array || + arr instanceof Float64Array + ); +} + // Test for a plain JS object. // Source: lodash.isPlainObject function isPlainObject(value) { From 6de744fd1c6e1fd4800e734bac9c0e5c48c298af Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Tue, 12 Mar 2024 09:16:50 +0000 Subject: [PATCH 103/207] Refactor Stream to use eventstream-parser library (#214) * Use the fetch() function provided in the Stream implementation * Allow js files to be imported into tests * Fix types for validateWebhook * Handle processing chunked event streams * Await validateWebhook in tests --- README.md | 8 + biome.json | 3 + index.d.ts | 2 +- index.js | 8 +- index.test.ts | 318 +++++++++++++++++++++++++++- lib/stream.js | 143 +++++-------- lib/util.js | 2 +- package-lock.json | 17 +- package.json | 3 +- tsconfig.json | 5 +- vendor/eventsource-parser/stream.js | 198 +++++++++++++++++ 11 files changed, 588 insertions(+), 119 deletions(-) create mode 100644 vendor/eventsource-parser/stream.js diff --git a/README.md b/README.md index 75ab0d24..5341fd41 100644 --- a/README.md +++ b/README.md @@ -811,3 +811,11 @@ You can call this method directly to make other requests to the API. ## TypeScript The `Replicate` constructor and all `replicate.*` methods are fully typed. + +## Vendored Dependencies + +We have a few dependencies that have been bundled into the vendor directory rather than adding external npm dependencies. + +These have been generated using bundlejs.com and copied into the appropriate directory along with the license and repository information. + +* [eventsource-parser/stream](https://bundlejs.com/?bundle&q=eventsource-parser%40latest%2Fstream&config=%7B%22esbuild%22%3A%7B%22format%22%3A%22cjs%22%2C%22minify%22%3Afalse%2C%22platform%22%3A%22neutral%22%7D%7D) diff --git a/biome.json b/biome.json index 807b9013..ecb665f5 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,8 @@ { "$schema": "https://biomejs.dev/schemas/1.0.0/schema.json", + "files": { + "ignore": [".wrangler", "vendor/*"] + }, "formatter": { "indentStyle": "space", "indentWidth": 2 diff --git a/index.d.ts b/index.d.ts index 8dc998a8..abf68dce 100644 --- a/index.d.ts +++ b/index.d.ts @@ -279,7 +279,7 @@ declare module "replicate" { signature?: string; }, secret: string - ): boolean; + ): Promise; export function parseProgressFromLogs(logs: Prediction | string): { percentage: number; diff --git a/index.js b/index.js index 8f56ab5a..cd299f4b 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ const ApiError = require("./lib/error"); const ModelVersionIdentifier = require("./lib/identifier"); -const { Stream } = require("./lib/stream"); +const { createReadableStream } = require("./lib/stream"); const { withAutomaticRetries, validateWebhook, @@ -291,7 +291,11 @@ class Replicate { if (prediction.urls && prediction.urls.stream) { const { signal } = options; - const stream = new Stream(prediction.urls.stream, { signal }); + const stream = createReadableStream({ + url: prediction.urls.stream, + fetch: this.fetch, + options: { signal }, + }); yield* stream; } else { throw new Error("Prediction does not support streaming"); diff --git a/index.test.ts b/index.test.ts index 97abc6fb..6624eb2f 100644 --- a/index.test.ts +++ b/index.test.ts @@ -7,7 +7,8 @@ import Replicate, { parseProgressFromLogs, } from "replicate"; import nock from "nock"; -import fetch from "cross-fetch"; +import { createReadableStream } from "./lib/stream"; +import { PassThrough } from "node:stream"; let client: Replicate; const BASE_URL = "https://api.replicate.com/v1"; @@ -21,7 +22,6 @@ describe("Replicate client", () => { beforeEach(() => { client = new Replicate({ auth: "test-token" }); - client.fetch = fetch; unmatched = []; nock.emitter.on("no match", handleNoMatch); @@ -251,7 +251,7 @@ describe("Replicate client", () => { let actual: Record | undefined; nock(BASE_URL) .post("/predictions") - .reply(201, (uri: string, body: Record) => { + .reply(201, (_uri: string, body: Record) => { actual = body; return body; }); @@ -1010,8 +1010,6 @@ describe("Replicate client", () => { }); test("Calls the correct API routes for a model", async () => { - const firstPollingRequest = true; - nock(BASE_URL) .post("/models/replicate/hello-world/predictions") .reply(201, { @@ -1187,4 +1185,314 @@ describe("Replicate client", () => { }); // Continue with tests for other methods + + describe("createReadableStream", () => { + function createStream(body: string | NodeJS.ReadableStream, status = 200) { + const streamEndpoint = "https://stream.replicate.com"; + nock(streamEndpoint) + .get("/fake_stream") + .matchHeader("Accept", "text/event-stream") + .reply(status, body); + + return createReadableStream({ + url: `${streamEndpoint}/fake_stream`, + fetch: fetch, + }); + } + + test("consumes a server sent event stream", async () => { + const stream = createStream( + ` + event: output + id: EVENT_1 + data: hello world + + event: done + id: EVENT_2 + data: {} + + `.replace(/^[ ]+/gm, "") + ); + + const iterator = stream[Symbol.asyncIterator](); + expect(await iterator.next()).toEqual({ + done: false, + value: { event: "output", id: "EVENT_1", data: "hello world" }, + }); + expect(await iterator.next()).toEqual({ + done: false, + value: { event: "done", id: "EVENT_2", data: "{}" }, + }); + expect(await iterator.next()).toEqual({ done: true }); + expect(await iterator.next()).toEqual({ done: true }); + }); + + test("consumes multiple events", async () => { + const stream = createStream( + ` + event: output + id: EVENT_1 + data: hello world + + event: output + id: EVENT_2 + data: hello dave + + event: done + id: EVENT_3 + data: {} + + `.replace(/^[ ]+/gm, "") + ); + + const iterator = stream[Symbol.asyncIterator](); + + expect(await iterator.next()).toEqual({ + done: false, + value: { event: "output", id: "EVENT_1", data: "hello world" }, + }); + expect(await iterator.next()).toEqual({ + done: false, + value: { event: "output", id: "EVENT_2", data: "hello dave" }, + }); + expect(await iterator.next()).toEqual({ + done: false, + value: { event: "done", id: "EVENT_3", data: "{}" }, + }); + expect(await iterator.next()).toEqual({ done: true }); + expect(await iterator.next()).toEqual({ done: true }); + }); + + test("ignores unexpected characters", async () => { + const stream = createStream( + ` + : hi + + event: output + id: EVENT_1 + data: hello world + + event: done + id: EVENT_2 + data: {} + + `.replace(/^[ ]+/gm, "") + ); + + const iterator = stream[Symbol.asyncIterator](); + + expect(await iterator.next()).toEqual({ + done: false, + value: { event: "output", id: "EVENT_1", data: "hello world" }, + }); + expect(await iterator.next()).toEqual({ + done: false, + value: { event: "done", id: "EVENT_2", data: "{}" }, + }); + expect(await iterator.next()).toEqual({ done: true }); + expect(await iterator.next()).toEqual({ done: true }); + }); + + test("supports multiple lines of output in a single event", async () => { + const stream = createStream( + ` + : hi + + event: output + id: EVENT_1 + data: hello, + data: this is a new line, + data: and this is a new line too + + event: done + id: EVENT_2 + data: {} + + `.replace(/^[ ]+/gm, "") + ); + + const iterator = stream[Symbol.asyncIterator](); + + expect(await iterator.next()).toEqual({ + done: false, + value: { + event: "output", + id: "EVENT_1", + data: "hello,\nthis is a new line,\nand this is a new line too", + }, + }); + expect(await iterator.next()).toEqual({ + done: false, + value: { event: "done", id: "EVENT_2", data: "{}" }, + }); + expect(await iterator.next()).toEqual({ done: true }); + expect(await iterator.next()).toEqual({ done: true }); + }); + + test("supports the server writing data lines in multiple chunks", async () => { + const body = new PassThrough(); + const stream = createStream(body); + + // Create a stream of data chunks split on the pipe character for readability. + const data = ` + event: output + id: EVENT_1 + data: hello,| + data: this is a new line,| + data: and this is a new line too + + event: done + id: EVENT_2 + data: {} + + `.replace(/^[ ]+/gm, ""); + + const chunks = data.split("|"); + + // Consume the iterator in parallel to writing it. + const reading = new Promise((resolve, reject) => { + (async () => { + const iterator = stream[Symbol.asyncIterator](); + expect(await iterator.next()).toEqual({ + done: false, + value: { + event: "output", + id: "EVENT_1", + data: "hello,\nthis is a new line,\nand this is a new line too", + }, + }); + expect(await iterator.next()).toEqual({ + done: false, + value: { event: "done", id: "EVENT_2", data: "{}" }, + }); + expect(await iterator.next()).toEqual({ done: true }); + })().then(resolve, reject); + }); + + // Write the chunks to the stream at an interval. + const writing = new Promise((resolve, reject) => { + (async () => { + for await (const chunk of chunks) { + body.write(chunk); + await new Promise((resolve) => setTimeout(resolve, 1)); + } + body.end(); + resolve(null); + })().then(resolve, reject); + }); + + // Wait for both promises to resolve. + await Promise.all([reading, writing]); + }); + + test("supports the server writing data in a complete mess", async () => { + const body = new PassThrough(); + const stream = createStream(body); + + // Create a stream of data chunks split on the pipe character for readability. + const data = ` + : hi + + ev|ent: output + id: EVENT_1 + data: hello, + data: this |is a new line,| + data: and this is |a new line too + + event: d|one + id: EVENT|_2 + data: {} + + `.replace(/^[ ]+/gm, ""); + + const chunks = data.split("|"); + + // Consume the iterator in parallel to writing it. + const reading = new Promise((resolve, reject) => { + (async () => { + const iterator = stream[Symbol.asyncIterator](); + expect(await iterator.next()).toEqual({ + done: false, + value: { + event: "output", + id: "EVENT_1", + data: "hello,\nthis is a new line,\nand this is a new line too", + }, + }); + expect(await iterator.next()).toEqual({ + done: false, + value: { event: "done", id: "EVENT_2", data: "{}" }, + }); + expect(await iterator.next()).toEqual({ done: true }); + })().then(resolve, reject); + }); + + // Write the chunks to the stream at an interval. + const writing = new Promise((resolve, reject) => { + (async () => { + for await (const chunk of chunks) { + body.write(chunk); + await new Promise((resolve) => setTimeout(resolve, 1)); + } + body.end(); + resolve(null); + })().then(resolve, reject); + }); + + // Wait for both promises to resolve. + await Promise.all([reading, writing]); + }); + + test("supports ending without a done", async () => { + const stream = createStream( + ` + event: output + id: EVENT_1 + data: hello world + + `.replace(/^[ ]+/gm, "") + ); + + const iterator = stream[Symbol.asyncIterator](); + expect(await iterator.next()).toEqual({ + done: false, + value: { event: "output", id: "EVENT_1", data: "hello world" }, + }); + expect(await iterator.next()).toEqual({ done: true }); + }); + + test("an error event in the stream raises an exception", async () => { + const stream = createStream( + ` + event: output + id: EVENT_1 + data: hello world + + event: error + id: EVENT_2 + data: An unexpected error occurred + + `.replace(/^[ ]+/gm, "") + ); + + const iterator = stream[Symbol.asyncIterator](); + expect(await iterator.next()).toEqual({ + done: false, + value: { event: "output", id: "EVENT_1", data: "hello world" }, + }); + await expect(iterator.next()).rejects.toThrowError( + "An unexpected error occurred" + ); + expect(await iterator.next()).toEqual({ done: true }); + }); + + test("an error when fetching the stream raises an exception", async () => { + const stream = createStream("{}", 500); + const iterator = stream[Symbol.asyncIterator](); + await expect(iterator.next()).rejects.toThrowError( + "Request to https://stream.replicate.com/fake_stream failed with status 500" + ); + expect(await iterator.next()).toEqual({ done: true }); + }); + }); }); diff --git a/lib/stream.js b/lib/stream.js index 012d6d03..a97642db 100644 --- a/lib/stream.js +++ b/lib/stream.js @@ -1,14 +1,9 @@ // Attempt to use readable-stream if available, attempt to use the built-in stream module. -let Readable; -try { - Readable = require("readable-stream").Readable; -} catch (e) { - try { - Readable = require("stream").Readable; - } catch (e) { - Readable = null; - } -} + +const ApiError = require("./error"); +const { + EventSourceParserStream, +} = require("../vendor/eventsource-parser/stream"); /** * A server-sent event. @@ -42,98 +37,56 @@ class ServerSentEvent { } /** - * A stream of server-sent events. + * Create a new stream of server-sent events. + * + * @param {object} config + * @param {string} config.url The URL to connect to. + * @param {typeof fetch} [config.fetch] The URL to connect to. + * @param {object} [config.options] The EventSource options. + * @returns {ReadableStream & AsyncIterable} */ -class Stream extends Readable { - /** - * Create a new stream of server-sent events. - * - * @param {string} url The URL to connect to. - * @param {object} options The fetch options. - */ - constructor(url, options) { - if (!Readable) { - throw new Error( - "Readable streams are not supported. Please use Node.js 18 or later, or install the readable-stream package." - ); - } - - super(); - this.url = url; - this.options = options; - - this.event = null; - this.data = []; - this.lastEventId = null; - this.retry = null; - } - - decode(line) { - if (!line) { - if (!this.event && !this.data.length && !this.lastEventId) { - return null; +function createReadableStream({ url, fetch, options = {} }) { + return new ReadableStream({ + async start(controller) { + const init = { + ...options, + headers: { + ...options.headers, + Accept: "text/event-stream", + }, + }; + const response = await fetch(url, init); + + if (!response.ok) { + const text = await response.text(); + const request = new Request(url, init); + controller.error( + new ApiError( + `Request to ${url} failed with status ${response.status}`, + request, + response + ) + ); } - const sse = new ServerSentEvent( - this.event, - this.data.join("\n"), - this.lastEventId - ); - - this.event = null; - this.data = []; - this.retry = null; - - return sse; - } - - if (line.startsWith(":")) { - return null; - } - - const [field, value] = line.split(": "); - if (field === "event") { - this.event = value; - } else if (field === "data") { - this.data.push(value); - } else if (field === "id") { - this.lastEventId = value; - } - - return null; - } - - async *[Symbol.asyncIterator]() { - const response = await fetch(this.url, { - ...this.options, - headers: { - Accept: "text/event-stream", - }, - }); - - for await (const chunk of response.body) { - const decoder = new TextDecoder("utf-8"); - const text = decoder.decode(chunk); - const lines = text.split("\n"); - for (const line of lines) { - const sse = this.decode(line); - if (sse) { - if (sse.event === "error") { - throw new Error(sse.data); - } - - yield sse; - - if (sse.event === "done") { - return; - } + const stream = response.body + .pipeThrough(new TextDecoderStream()) + .pipeThrough(new EventSourceParserStream()); + for await (const event of stream) { + if (event.event === "error") { + controller.error(new Error(event.data)); + } else { + controller.enqueue( + new ServerSentEvent(event.event, event.data, event.id) + ); } } - } - } + controller.close(); + }, + }); } module.exports = { - Stream, + createReadableStream, ServerSentEvent, }; diff --git a/lib/util.js b/lib/util.js index ac99e082..ff9daccc 100644 --- a/lib/util.js +++ b/lib/util.js @@ -26,7 +26,7 @@ const ApiError = require("./error"); /** * Validate a webhook signature * - * @returns {boolean} - True if the signature is valid + * @returns {Promise} - True if the signature is valid * @throws {Error} - If the request is missing required headers, body, or secret */ async function validateWebhook(requestData, secret) { diff --git a/package-lock.json b/package-lock.json index 8ba3c038..c3a18b42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@typescript-eslint/eslint-plugin": "^5.56.0", "cross-fetch": "^3.1.5", "jest": "^29.6.2", - "nock": "^13.3.0", + "nock": "^14.0.0-beta.4", "publint": "^0.2.7", "ts-jest": "^29.1.0", "typescript": "^5.0.2" @@ -3948,12 +3948,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -4083,18 +4077,17 @@ "dev": true }, "node_modules/nock": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.3.0.tgz", - "integrity": "sha512-HHqYQ6mBeiMc+N038w8LkMpDCRquCHWeNmN3v6645P3NhN2+qXOBqvPqo7Rt1VyCMzKhJ733wZqw5B7cQVFNPg==", + "version": "14.0.0-beta.4", + "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.0-beta.4.tgz", + "integrity": "sha512-N9GIOnNFas/TtdCQpavpi6A6SyVVInkD/vrUCF2u51vlE2wSnqfPifVli6xSX8l6Lz/3sdSwPusE9n3KPDDh0g==", "dev": true, "dependencies": { "debug": "^4.1.0", "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.21", "propagate": "^2.0.0" }, "engines": { - "node": ">= 10.13" + "node": ">= 18" } }, "node_modules/node-fetch": { diff --git a/package.json b/package.json index 098bb3ad..c2ed3c9d 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "index.d.ts", "index.js", "lib/**/*.js", + "vendor/**/*", "package.json" ], "engines": { @@ -41,7 +42,7 @@ "@typescript-eslint/eslint-plugin": "^5.56.0", "cross-fetch": "^3.1.5", "jest": "^29.6.2", - "nock": "^13.3.0", + "nock": "^14.0.0-beta.4", "publint": "^0.2.7", "ts-jest": "^29.1.0", "typescript": "^5.0.2" diff --git a/tsconfig.json b/tsconfig.json index 7a564ee1..b699d792 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,9 +2,10 @@ "compilerOptions": { "esModuleInterop": true, "noEmit": true, - "strict": true + "strict": true, + "allowJs": true }, "exclude": [ "**/node_modules" ] -} +} \ No newline at end of file diff --git a/vendor/eventsource-parser/stream.js b/vendor/eventsource-parser/stream.js new file mode 100644 index 00000000..88465daa --- /dev/null +++ b/vendor/eventsource-parser/stream.js @@ -0,0 +1,198 @@ +// Source: https://github.com/rexxars/eventsource-parser/tree/v1.1.2 +// +// MIT License +// +// Copyright (c) 2024 Espen Hovlandsdal +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if ((from && typeof from === "object") || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { + get: () => from[key], + enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable, + }); + } + return to; +}; +var __toCommonJS = (mod) => + __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// /input.ts +var input_exports = {}; +__export(input_exports, { + EventSourceParserStream: () => EventSourceParserStream, +}); +module.exports = __toCommonJS(input_exports); + +// http-url:https://unpkg.com/eventsource-parser@1.1.2/dist/index.js +function createParser(onParse) { + let isFirstChunk; + let buffer; + let startingPosition; + let startingFieldLength; + let eventId; + let eventName; + let data; + reset(); + return { + feed, + reset, + }; + function reset() { + isFirstChunk = true; + buffer = ""; + startingPosition = 0; + startingFieldLength = -1; + eventId = void 0; + eventName = void 0; + data = ""; + } + function feed(chunk) { + buffer = buffer ? buffer + chunk : chunk; + if (isFirstChunk && hasBom(buffer)) { + buffer = buffer.slice(BOM.length); + } + isFirstChunk = false; + const length = buffer.length; + let position = 0; + let discardTrailingNewline = false; + while (position < length) { + if (discardTrailingNewline) { + if (buffer[position] === "\n") { + ++position; + } + discardTrailingNewline = false; + } + let lineLength = -1; + let fieldLength = startingFieldLength; + let character; + for ( + let index = startingPosition; + lineLength < 0 && index < length; + ++index + ) { + character = buffer[index]; + if (character === ":" && fieldLength < 0) { + fieldLength = index - position; + } else if (character === "\r") { + discardTrailingNewline = true; + lineLength = index - position; + } else if (character === "\n") { + lineLength = index - position; + } + } + if (lineLength < 0) { + startingPosition = length - position; + startingFieldLength = fieldLength; + break; + } else { + startingPosition = 0; + startingFieldLength = -1; + } + parseEventStreamLine(buffer, position, fieldLength, lineLength); + position += lineLength + 1; + } + if (position === length) { + buffer = ""; + } else if (position > 0) { + buffer = buffer.slice(position); + } + } + function parseEventStreamLine(lineBuffer, index, fieldLength, lineLength) { + if (lineLength === 0) { + if (data.length > 0) { + onParse({ + type: "event", + id: eventId, + event: eventName || void 0, + data: data.slice(0, -1), + // remove trailing newline + }); + data = ""; + eventId = void 0; + } + eventName = void 0; + return; + } + const noValue = fieldLength < 0; + const field = lineBuffer.slice( + index, + index + (noValue ? lineLength : fieldLength) + ); + let step = 0; + if (noValue) { + step = lineLength; + } else if (lineBuffer[index + fieldLength + 1] === " ") { + step = fieldLength + 2; + } else { + step = fieldLength + 1; + } + const position = index + step; + const valueLength = lineLength - step; + const value = lineBuffer.slice(position, position + valueLength).toString(); + if (field === "data") { + data += value ? "".concat(value, "\n") : "\n"; + } else if (field === "event") { + eventName = value; + } else if (field === "id" && !value.includes("\0")) { + eventId = value; + } else if (field === "retry") { + const retry = parseInt(value, 10); + if (!Number.isNaN(retry)) { + onParse({ + type: "reconnect-interval", + value: retry, + }); + } + } + } +} +var BOM = [239, 187, 191]; +function hasBom(buffer) { + return BOM.every((charCode, index) => buffer.charCodeAt(index) === charCode); +} + +// http-url:https://unpkg.com/eventsource-parser@1.1.2/dist/stream.js +var EventSourceParserStream = class extends TransformStream { + constructor() { + let parser; + super({ + start(controller) { + parser = createParser((event) => { + if (event.type === "event") { + controller.enqueue(event); + } + }); + }, + transform(chunk) { + parser.feed(chunk); + }, + }); + } +}; From 3c62031ed6f24984b83517dc9bd2e26c731de627 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 12 Mar 2024 02:19:25 -0700 Subject: [PATCH 104/207] 0.28.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c3a18b42..0a727c60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.27.1", + "version": "0.28.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.27.1", + "version": "0.28.0", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index c2ed3c9d..48a0b455 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.27.1", + "version": "0.28.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 82d905651b43e725fbd7302412895447ed26087f Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Tue, 12 Mar 2024 13:36:58 +0000 Subject: [PATCH 105/207] Close the stream when receiving "done" event from the server (#219) This fixes a regression introduced with #214 where we were not exiting correctly when getting the `"done"` event from the server. This was picked up by the introduction of the CloudFlare integration tests added in #217 which uses the streaming API. Once the fix was added it turns out that the `nock()` tests were incorrectly passing due to some internal weirdness when using `respondWith` and a `Readable` object. I wasn't able to get this working without hitting a different error: TypeError: Invalid state: Controller is already closed It looks like nock is retaining some global state somewhere in it's implementation and streams are being retained across requests. No combination of resetting mocks seemed to fix it. In the end I just mocked out the fetch function passed into the `createReadableStream` library and returned a `Response`. I think we should probably do this everywhere rather than use `nock()` as the Request/Response APIs provided by fetch are much better now than the old node http lib. --- index.test.ts | 130 +++++++++----------- integration/cloudflare-worker/.npmrc | 3 +- integration/cloudflare-worker/index.test.js | 15 ++- integration/commonjs/.npmrc | 3 +- integration/esm/.npmrc | 3 +- integration/typescript/.npmrc | 3 +- lib/stream.js | 17 ++- 7 files changed, 85 insertions(+), 89 deletions(-) diff --git a/index.test.ts b/index.test.ts index 6624eb2f..7e0ae22e 100644 --- a/index.test.ts +++ b/index.test.ts @@ -7,8 +7,8 @@ import Replicate, { parseProgressFromLogs, } from "replicate"; import nock from "nock"; +import { Readable } from "node:stream"; import { createReadableStream } from "./lib/stream"; -import { PassThrough } from "node:stream"; let client: Replicate; const BASE_URL = "https://api.replicate.com/v1"; @@ -1187,16 +1187,17 @@ describe("Replicate client", () => { // Continue with tests for other methods describe("createReadableStream", () => { - function createStream(body: string | NodeJS.ReadableStream, status = 200) { - const streamEndpoint = "https://stream.replicate.com"; - nock(streamEndpoint) - .get("/fake_stream") - .matchHeader("Accept", "text/event-stream") - .reply(status, body); - + function createStream(body: string | ReadableStream, status = 200) { + const streamEndpoint = "https://stream.replicate.com/fake_stream"; + const fetch = jest.fn((url) => { + if (url !== streamEndpoint) { + throw new Error(`Unmocked call to fetch() with url: ${url}`); + } + return new Response(body, { status }); + }); return createReadableStream({ - url: `${streamEndpoint}/fake_stream`, - fetch: fetch, + url: streamEndpoint, + fetch: fetch as any, }); } @@ -1330,9 +1331,6 @@ describe("Replicate client", () => { }); test("supports the server writing data lines in multiple chunks", async () => { - const body = new PassThrough(); - const stream = createStream(body); - // Create a stream of data chunks split on the pipe character for readability. const data = ` event: output @@ -1348,45 +1346,47 @@ describe("Replicate client", () => { `.replace(/^[ ]+/gm, ""); const chunks = data.split("|"); + const body = new ReadableStream({ + async pull(controller) { + if (chunks.length) { + await new Promise((resolve) => setTimeout(resolve, 1)); + const chunk = chunks.shift(); + controller.enqueue(new TextEncoder().encode(chunk)); + } + }, + }); + + const stream = createStream(body); // Consume the iterator in parallel to writing it. - const reading = new Promise((resolve, reject) => { - (async () => { - const iterator = stream[Symbol.asyncIterator](); - expect(await iterator.next()).toEqual({ - done: false, - value: { - event: "output", - id: "EVENT_1", - data: "hello,\nthis is a new line,\nand this is a new line too", - }, - }); - expect(await iterator.next()).toEqual({ - done: false, - value: { event: "done", id: "EVENT_2", data: "{}" }, - }); - expect(await iterator.next()).toEqual({ done: true }); - })().then(resolve, reject); + const iterator = stream[Symbol.asyncIterator](); + expect(await iterator.next()).toEqual({ + done: false, + value: { + event: "output", + id: "EVENT_1", + data: "hello,\nthis is a new line,\nand this is a new line too", + }, }); - - // Write the chunks to the stream at an interval. - const writing = new Promise((resolve, reject) => { - (async () => { - for await (const chunk of chunks) { - body.write(chunk); - await new Promise((resolve) => setTimeout(resolve, 1)); - } - body.end(); - resolve(null); - })().then(resolve, reject); + expect(await iterator.next()).toEqual({ + done: false, + value: { event: "done", id: "EVENT_2", data: "{}" }, }); + expect(await iterator.next()).toEqual({ done: true }); // Wait for both promises to resolve. - await Promise.all([reading, writing]); }); test("supports the server writing data in a complete mess", async () => { - const body = new PassThrough(); + const body = new ReadableStream({ + async pull(controller) { + if (chunks.length) { + await new Promise((resolve) => setTimeout(resolve, 1)); + const chunk = chunks.shift(); + controller.enqueue(new TextEncoder().encode(chunk)); + } + }, + }); const stream = createStream(body); // Create a stream of data chunks split on the pipe character for readability. @@ -1407,40 +1407,20 @@ describe("Replicate client", () => { const chunks = data.split("|"); - // Consume the iterator in parallel to writing it. - const reading = new Promise((resolve, reject) => { - (async () => { - const iterator = stream[Symbol.asyncIterator](); - expect(await iterator.next()).toEqual({ - done: false, - value: { - event: "output", - id: "EVENT_1", - data: "hello,\nthis is a new line,\nand this is a new line too", - }, - }); - expect(await iterator.next()).toEqual({ - done: false, - value: { event: "done", id: "EVENT_2", data: "{}" }, - }); - expect(await iterator.next()).toEqual({ done: true }); - })().then(resolve, reject); + const iterator = stream[Symbol.asyncIterator](); + expect(await iterator.next()).toEqual({ + done: false, + value: { + event: "output", + id: "EVENT_1", + data: "hello,\nthis is a new line,\nand this is a new line too", + }, }); - - // Write the chunks to the stream at an interval. - const writing = new Promise((resolve, reject) => { - (async () => { - for await (const chunk of chunks) { - body.write(chunk); - await new Promise((resolve) => setTimeout(resolve, 1)); - } - body.end(); - resolve(null); - })().then(resolve, reject); + expect(await iterator.next()).toEqual({ + done: false, + value: { event: "done", id: "EVENT_2", data: "{}" }, }); - - // Wait for both promises to resolve. - await Promise.all([reading, writing]); + expect(await iterator.next()).toEqual({ done: true }); }); test("supports ending without a done", async () => { diff --git a/integration/cloudflare-worker/.npmrc b/integration/cloudflare-worker/.npmrc index b15cbc2c..77750405 100644 --- a/integration/cloudflare-worker/.npmrc +++ b/integration/cloudflare-worker/.npmrc @@ -1,2 +1,3 @@ package-lock=false - +audit=false +fund=false diff --git a/integration/cloudflare-worker/index.test.js b/integration/cloudflare-worker/index.test.js index 0c0fc5e2..932d8f5d 100644 --- a/integration/cloudflare-worker/index.test.js +++ b/integration/cloudflare-worker/index.test.js @@ -3,8 +3,8 @@ import { unstable_dev as dev } from "wrangler"; import { test, after, before, describe } from "node:test"; import assert from "node:assert"; -/** @type {import("wrangler").UnstableDevWorker} */ describe("CloudFlare Worker", () => { + /** @type {import("wrangler").UnstableDevWorker} */ let worker; before(async () => { @@ -22,15 +22,20 @@ describe("CloudFlare Worker", () => { await worker.stop(); }); - test("worker streams back a response", { timeout: 1000 }, async () => { + test("worker streams back a response", { timeout: 5000 }, async () => { const resp = await worker.fetch(); const text = await resp.text(); - assert.ok(resp.ok, "status is 2xx"); - assert(text.length > 0, "body.length is greater than 0"); + assert.ok(resp.ok, `expected status to be 2xx but got ${resp.status}`); + assert( + text.length > 0, + "expected body to have content but got body.length of 0" + ); assert( text.includes("Colin CloudFlare"), - "body includes stream characters" + `expected body to include "Colin CloudFlare" but got ${JSON.stringify( + text + )}` ); }); }); diff --git a/integration/commonjs/.npmrc b/integration/commonjs/.npmrc index b15cbc2c..77750405 100644 --- a/integration/commonjs/.npmrc +++ b/integration/commonjs/.npmrc @@ -1,2 +1,3 @@ package-lock=false - +audit=false +fund=false diff --git a/integration/esm/.npmrc b/integration/esm/.npmrc index b15cbc2c..77750405 100644 --- a/integration/esm/.npmrc +++ b/integration/esm/.npmrc @@ -1,2 +1,3 @@ package-lock=false - +audit=false +fund=false diff --git a/integration/typescript/.npmrc b/integration/typescript/.npmrc index b15cbc2c..77750405 100644 --- a/integration/typescript/.npmrc +++ b/integration/typescript/.npmrc @@ -1,2 +1,3 @@ package-lock=false - +audit=false +fund=false diff --git a/lib/stream.js b/lib/stream.js index a97642db..cd9274c2 100644 --- a/lib/stream.js +++ b/lib/stream.js @@ -62,7 +62,7 @@ function createReadableStream({ url, fetch, options = {} }) { const request = new Request(url, init); controller.error( new ApiError( - `Request to ${url} failed with status ${response.status}`, + `Request to ${url} failed with status ${response.status}: ${text}`, request, response ) @@ -72,15 +72,22 @@ function createReadableStream({ url, fetch, options = {} }) { const stream = response.body .pipeThrough(new TextDecoderStream()) .pipeThrough(new EventSourceParserStream()); + for await (const event of stream) { if (event.event === "error") { controller.error(new Error(event.data)); - } else { - controller.enqueue( - new ServerSentEvent(event.event, event.data, event.id) - ); + break; + } + + controller.enqueue( + new ServerSentEvent(event.event, event.data, event.id) + ); + + if (event.event === "done") { + break; } } + controller.close(); }, }); From 46bc3f2c6fe71a6a5bb44937e334369667537715 Mon Sep 17 00:00:00 2001 From: Mattt Date: Tue, 12 Mar 2024 07:03:48 -0700 Subject: [PATCH 106/207] Update CI configuration for integration test suite (#218) * Update configuration of integration test suite to run withour fail-fast behavior * Run Cloudflare Worker test suite only on latest Node version --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc4d90e1..8d4345e2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,9 +33,13 @@ jobs: strategy: matrix: + # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases node-version: [18.x, 20.x] suite: [commonjs, esm, typescript, cloudflare-worker] - # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases + exclude: + - suite: cloudflare-worker + node-version: 18.x # Only test Cloudflare suite with the latest Node version + fail-fast: false env: REPLICATE_API_TOKEN: ${{ secrets.REPLICATE_API_TOKEN }} From ba9b94e9ad4b245359e8933a794d319d718f46d2 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 12 Mar 2024 07:10:44 -0700 Subject: [PATCH 107/207] 0.28.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0a727c60..1d6c9cdd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.28.0", + "version": "0.28.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.28.0", + "version": "0.28.1", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 48a0b455..123574d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.28.0", + "version": "0.28.1", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 20983bc6524e08e84ace09f31401dfcfdf2af4e5 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Wed, 13 Mar 2024 11:11:06 +0000 Subject: [PATCH 108/207] Document webhooks in README --- README.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5341fd41..255c8f18 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ console.log(prediction.output); // ['https://replicate.delivery/pbxt/RoaxeXqhL0xaYyLm6w3bpGwF5RaNBjADukfFnMbhOyeoWBdhA/out-0.png'] ``` -To run a model that takes a file input, pass a URL to a publicly accessible file. Or, for smaller files (<10MB), you can pass the data directly. +To run a model that takes a file input you can pass the data directly or pass a URL to a publicly accessible file. ```js const fs = require("node:fs/promises"); @@ -89,6 +89,65 @@ const output = await replicate.run(model, { input }); // ['https://replicate.delivery/mgxm/e7b0e122-9daa-410e-8cde-006c7308ff4d/output.png'] ``` +### Webhooks + +Webhooks provide real-time updates about your prediction. Specify an endpoint when you create a prediction, and Replicate will send HTTP POST requests to that URL when the prediction is created, updated, and finished. + +It is possible to provide a URL to the predictions.create() function that will be requested by Replicate when the prediction status changes. This is an alternative to polling. + +To receive webhooks you’ll need a web server. The following example uses Hono, a web standards based server, but this pattern applies to most frameworks. + +
+ See example + +```js +import { serve } from '@hono/node-server'; +import { Hono } from 'hono'; + +const app = new Hono(); +app.get('/webhooks/replicate', async (c) => { + // Get the prediction from the request. + const prediction = await c.req.json(); + console.log(prediction); + //=> {"id": "xyz", "status": "successful", ... } + + // Acknowledge the webhook. + c.status(200); + c.json({ok: true}); +})); + +serve(app, (info) => { + console.log(`Listening on http://localhost:${info.port}`) + //=> Listening on http://localhost:3000 +}); +``` + +
+ +Create the prediction passing in the webhook URL to `webhook` and specify which events you want to receive in `webhook_events_filter` out of "start", "output", ”logs” and "completed": + +```js +const Replicate = require("replicate"); +const replicate = new Replicate(); + +const input = { + image: "https://replicate.delivery/pbxt/KWDkejqLfER3jrroDTUsSvBWFaHtapPxfg4xxZIqYmfh3zXm/Screenshot%202024-02-28%20at%2022.14.00.png", + denoising_strength: 0.5, + instant_id_strength: 0.8 +}; + +const callbackURL = `https://my.app/webhooks/replicate`; +await replicate.predictions.create({ + version: "19deaef633fd44776c82edf39fd60e95a7250b8ececf11a725229dc75a81f9ca", + input: input, + webhook: callbackURL, + webhook_events_filter: ["completed"], +}); + +// The server will now handle the event and log: +// => {"id": "xyz", "status": "successful", ... } +``` + ## TypeScript Currently in order to support the module format used by `replicate` you'll need to set `esModuleInterop` to `true` in your tsconfig.json. From ea009390f690e374a7615e56e7893f16fea03b97 Mon Sep 17 00:00:00 2001 From: Mattt Date: Mon, 18 Mar 2024 04:31:29 -0700 Subject: [PATCH 109/207] Add integration test for Bun (#220) * Add integration test for Bun * Remove comments in tsconfig.json * Remove typescript dependency from package.json * Run tests with bun test * Trim down .gitignore * Update bun.lockb * Remove test script from package.json altogether * Remove @types/node dependency * Build and install tarball for Bun integration test * Update tsconfig.json Co-authored-by: Aron Carroll --------- Co-authored-by: Aron Carroll --- .github/workflows/ci.yml | 66 ++++++++++++++++++++++++++++++++-- integration/bun/.gitignore | 1 + integration/bun/bun.lockb | Bin 0 -> 166030 bytes integration/bun/index.test.ts | 23 ++++++++++++ integration/bun/index.ts | 16 +++++++++ integration/bun/package.json | 14 ++++++++ integration/bun/tsconfig.json | 15 ++++++++ tsconfig.json | 6 ++-- 8 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 integration/bun/.gitignore create mode 100755 integration/bun/bun.lockb create mode 100644 integration/bun/index.test.ts create mode 100644 integration/bun/index.ts create mode 100644 integration/bun/package.json create mode 100644 integration/bun/tsconfig.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d4345e2..c6ca04e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,8 +12,8 @@ jobs: strategy: matrix: - node-version: [18.x, 20.x] # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases + node-version: [18.x, 20.x] steps: - uses: actions/checkout@v3 @@ -27,23 +27,51 @@ jobs: - run: npm run check - run: npm run lint - integration: + + integration-node: needs: test runs-on: ubuntu-latest + env: + REPLICATE_API_TOKEN: ${{ secrets.REPLICATE_API_TOKEN }} + strategy: matrix: # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases node-version: [18.x, 20.x] - suite: [commonjs, esm, typescript, cloudflare-worker] + suite: [commonjs, esm, typescript] exclude: - suite: cloudflare-worker node-version: 18.x # Only test Cloudflare suite with the latest Node version fail-fast: false + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" + # Build a production tarball and run the integration tests against it. + - run: | + PKG_TARBALL=$(npm --loglevel error pack) + npm --prefix integration/${{ matrix.suite }} install + npm --prefix integration/${{ matrix.suite }} install "file:/./$PKG_TARBALL" + npm --prefix integration/${{ matrix.suite }} test + + + integration-edge: + needs: test + runs-on: ubuntu-latest + env: REPLICATE_API_TOKEN: ${{ secrets.REPLICATE_API_TOKEN }} + strategy: + matrix: + node-version: [20.x] + suite: [cloudflare-worker] + steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} @@ -58,3 +86,35 @@ jobs: npm --prefix integration/${{ matrix.suite }} install npm --prefix integration/${{ matrix.suite }} install "file:/./$PKG_TARBALL" npm --prefix integration/${{ matrix.suite }} test + + + integration-bun: + needs: test + runs-on: ubuntu-latest + + env: + REPLICATE_API_TOKEN: ${{ secrets.REPLICATE_API_TOKEN }} + + strategy: + matrix: + bun-version: [1.0.11] + node-version: [20.x] + suite: [bun] + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" + - name: Use Bun ${{ matrix.bun-version }} + uses: oven-sh/setup-bun@v1 + with: + bun-version: ${{ matrix.bun-version }} + - run: | + PKG_TARBALL=$(npm --loglevel error pack) + cd integration/${{ matrix.suite }} + bun uninstall replicate + bun install "file:../../$PKG_TARBALL" + bun test diff --git a/integration/bun/.gitignore b/integration/bun/.gitignore new file mode 100644 index 00000000..3c3629e6 --- /dev/null +++ b/integration/bun/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/integration/bun/bun.lockb b/integration/bun/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..dd45eaf0f1831cc4099e22920a740384a6ecba30 GIT binary patch literal 166030 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 { + const output = await main(); + expect(output as any).toEqual("hello Brünnhilde Bun"); +}); diff --git a/integration/bun/index.ts b/integration/bun/index.ts new file mode 100644 index 00000000..263a86ec --- /dev/null +++ b/integration/bun/index.ts @@ -0,0 +1,16 @@ +import Replicate from "replicate"; + +const replicate = new Replicate({ + auth: process.env.REPLICATE_API_TOKEN, +}); + +export default async function main() { + return await replicate.run( + "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + { + input: { + text: "Brünnhilde Bun", + }, + } + ); +} diff --git a/integration/bun/package.json b/integration/bun/package.json new file mode 100644 index 00000000..f4ba2029 --- /dev/null +++ b/integration/bun/package.json @@ -0,0 +1,14 @@ +{ + "name": "replicate-app-bun", + "version": "0.0.0", + "private": true, + "description": "Bun integration tests", + "main": "index.js", + "type": "module", + "dependencies": { + "replicate": "file:../../" + }, + "devDependencies": { + "@types/bun": "latest" + } +} diff --git a/integration/bun/tsconfig.json b/integration/bun/tsconfig.json new file mode 100644 index 00000000..2ffa937f --- /dev/null +++ b/integration/bun/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "nodenext", + "inlineSourceMap": true, + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "skipDefaultLibCheck": true, + "skipLibCheck": true + } +} diff --git a/tsconfig.json b/tsconfig.json index b699d792..e6b4ed6c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,8 @@ "allowJs": true }, "exclude": [ - "**/node_modules" + "**/node_modules", + "integration/**" ] -} \ No newline at end of file +} + From b06a5f8268ed9b2aee3f2c6906b5c713d4a21079 Mon Sep 17 00:00:00 2001 From: Mattt Date: Mon, 18 Mar 2024 04:45:59 -0700 Subject: [PATCH 110/207] Extract CI tarball generation into separate `build` job (#226) * Extract tarball generation into separate build job * Replace use of deprecated ::set-output See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ --- .github/workflows/ci.yml | 54 ++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c6ca04e4..b3dbba42 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,8 +28,32 @@ jobs: - run: npm run lint + # Build a production tarball and use it to run the integration + build: + runs-on: ubuntu-latest + + outputs: + tarball-name: ${{ steps.pack.outputs.tarball-name }} + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: 20.x + cache: "npm" + - name: Build tarball + id: pack + run: | + echo "tarball-name=$(npm --loglevel error pack)" >> $GITHUB_OUTPUT + - uses: actions/upload-artifact@v3 + with: + name: package-tarball + path: ${{ steps.pack.outputs.tarball-name }} + + integration-node: - needs: test + needs: [test, build] runs-on: ubuntu-latest env: @@ -47,21 +71,22 @@ jobs: steps: - uses: actions/checkout@v3 + - uses: actions/download-artifact@v3 + with: + name: package-tarball - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} cache: "npm" - # Build a production tarball and run the integration tests against it. - run: | - PKG_TARBALL=$(npm --loglevel error pack) npm --prefix integration/${{ matrix.suite }} install - npm --prefix integration/${{ matrix.suite }} install "file:/./$PKG_TARBALL" + npm --prefix integration/${{ matrix.suite }} install "./${{ needs.build.outputs.tarball-name }}" npm --prefix integration/${{ matrix.suite }} test integration-edge: - needs: test + needs: [test, build] runs-on: ubuntu-latest env: @@ -74,22 +99,23 @@ jobs: steps: - uses: actions/checkout@v3 + - uses: actions/download-artifact@v3 + with: + name: package-tarball - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} cache: "npm" - # Build a production tarball and run the integration tests against it. - run: | test "${{ matrix.suite }}" = "cloudflare-worker" && echo "REPLICATE_API_TOKEN=${{ secrets.REPLICATE_API_TOKEN }}" > integration/${{ matrix.suite }}/.dev.vars - PKG_TARBALL=$(npm --loglevel error pack) npm --prefix integration/${{ matrix.suite }} install - npm --prefix integration/${{ matrix.suite }} install "file:/./$PKG_TARBALL" + npm --prefix integration/${{ matrix.suite }} install "./${{ needs.build.outputs.tarball-name }}" npm --prefix integration/${{ matrix.suite }} test integration-bun: - needs: test + needs: [test, build] runs-on: ubuntu-latest env: @@ -98,23 +124,19 @@ jobs: strategy: matrix: bun-version: [1.0.11] - node-version: [20.x] suite: [bun] steps: - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + - uses: actions/download-artifact@v3 with: - node-version: ${{ matrix.node-version }} - cache: "npm" + name: package-tarball - name: Use Bun ${{ matrix.bun-version }} uses: oven-sh/setup-bun@v1 with: bun-version: ${{ matrix.bun-version }} - run: | - PKG_TARBALL=$(npm --loglevel error pack) cd integration/${{ matrix.suite }} bun uninstall replicate - bun install "file:../../$PKG_TARBALL" + bun install "file:../../${{ needs.build.outputs.tarball-name }}" bun test From 9245a097948ffdabb472f1342a2aad0697db9260 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Tue, 19 Mar 2024 05:20:22 +0000 Subject: [PATCH 111/207] Add browser integration tests using Playwright (#222) Currently just tests the streaming API on Chromium but it caught a bug with `ReadableStream`. I know we don't officially support the browser environments yet, but I do think we should ensure that it works if possible. It's relatively simple to setup a CloudFlare worker, for example, that applies the API token + CORS headers to a browser request for example should someone want to do such a thing. --- .github/workflows/ci.yml | 33 ++++++++++++++++++-- index.js | 4 ++- integration/browser/.npmrc | 3 ++ integration/browser/README.md | 39 ++++++++++++++++++++++++ integration/browser/index.js | 22 +++++++++++++ integration/browser/index.test.js | 34 +++++++++++++++++++++ integration/browser/package.json | 19 ++++++++++++ integration/browser/playwright.config.ts | 3 ++ lib/stream.js | 3 +- lib/util.js | 24 +++++++++++++++ tsconfig.json | 6 +--- 11 files changed, 180 insertions(+), 10 deletions(-) create mode 100644 integration/browser/.npmrc create mode 100644 integration/browser/README.md create mode 100644 integration/browser/index.js create mode 100644 integration/browser/index.test.js create mode 100644 integration/browser/package.json create mode 100644 integration/browser/playwright.config.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3dbba42..5fe3c622 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,9 +64,6 @@ jobs: # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases node-version: [18.x, 20.x] suite: [commonjs, esm, typescript] - exclude: - - suite: cloudflare-worker - node-version: 18.x # Only test Cloudflare suite with the latest Node version fail-fast: false steps: @@ -84,6 +81,36 @@ jobs: npm --prefix integration/${{ matrix.suite }} install "./${{ needs.build.outputs.tarball-name }}" npm --prefix integration/${{ matrix.suite }} test + integration-browser: + needs: [test, build] + runs-on: ubuntu-latest + + env: + REPLICATE_API_TOKEN: ${{ secrets.REPLICATE_API_TOKEN }} + + strategy: + matrix: + browser: ["chromium", "firefox", "webkit"] + suite: ["browser"] + fail-fast: false + + steps: + - uses: actions/checkout@v3 + - uses: actions/download-artifact@v3 + with: + name: package-tarball + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" + - run: | + cd integration/${{ matrix.suite }} + npm install + npm install "../../${{ needs.build.outputs.tarball-name }}" + npm exec -- playwright install ${{ matrix.browser }} + npm exec -- playwright install-deps ${{ matrix.browser }} + npm exec -- playwright test --browser ${{ matrix.browser }} integration-edge: needs: [test, build] diff --git a/index.js b/index.js index cd299f4b..6c6590b5 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,7 @@ const { withAutomaticRetries, validateWebhook, parseProgressFromLogs, + streamAsyncIterator, } = require("./lib/util"); const accounts = require("./lib/accounts"); @@ -296,7 +297,8 @@ class Replicate { fetch: this.fetch, options: { signal }, }); - yield* stream; + + yield* streamAsyncIterator(stream); } else { throw new Error("Prediction does not support streaming"); } diff --git a/integration/browser/.npmrc b/integration/browser/.npmrc new file mode 100644 index 00000000..77750405 --- /dev/null +++ b/integration/browser/.npmrc @@ -0,0 +1,3 @@ +package-lock=false +audit=false +fund=false diff --git a/integration/browser/README.md b/integration/browser/README.md new file mode 100644 index 00000000..575d779a --- /dev/null +++ b/integration/browser/README.md @@ -0,0 +1,39 @@ +# Browser integration tests + +Uses [`playwright`](https://playwright.dev/docs) to run a basic integration test against the three most common browser engines, Firefox, Chromium and WebKit. + +It uses the `replicate/canary` model for the moment, which requires a Replicate API token available in the environment under `REPLICATE_API_TOKEN`. + +The entire suite is a single `main()` function that calls a single model exercising the streaming API. + +The test uses `esbuild` within the test generate a browser friendly version of the `index.js` file which is loaded into the given browser and calls the `main()` function asserting the response content. + +## CORS + +The Replicate API doesn't support Cross Origin Resource Sharing at this time. We work around this in Playwright by intercepting the request in a `page.route` handler. We don't modify the request/response, but this seems to work around the restriction. + +## Setup + + npm install + +## Local + +The following command will run the tests across all browsers. + + npm test + +To run against the default browser (chromium) run: + + npm exec playwright test + +Or, specify a browser with: + + npm exec playwright test --browser firefox + +## Debugging + +Running `playwright test` with the `--debug` flag opens a browser window with a debugging interface, and a breakpoint set at the start of the test. It can also be connected directly to VSCode. + + npm exec playwright test --debug + +The browser.js file is injected into the page via a script tag, to be able to set breakpoints in this file you'll need to use a `debugger` statement and open the devtools in the spawned browser window before continuing the test suite. diff --git a/integration/browser/index.js b/integration/browser/index.js new file mode 100644 index 00000000..9c0ae654 --- /dev/null +++ b/integration/browser/index.js @@ -0,0 +1,22 @@ +import Replicate from "replicate"; + +/** + * @param {string} - token the REPLICATE_API_TOKEN + */ +window.main = async (token) => { + const replicate = new Replicate({ auth: token }); + const stream = replicate.stream( + "replicate/canary:30e22229542eb3f79d4f945dacb58d32001b02cc313ae6f54eef27904edf3272", + { + input: { + text: "Betty Browser", + }, + } + ); + + const output = []; + for await (const event of stream) { + output.push(String(event)); + } + return output.join(""); +}; diff --git a/integration/browser/index.test.js b/integration/browser/index.test.js new file mode 100644 index 00000000..380d8138 --- /dev/null +++ b/integration/browser/index.test.js @@ -0,0 +1,34 @@ +import { test, expect } from "@playwright/test"; +import { build } from "esbuild"; + +// Convert the source file from commonjs to a browser script. +const result = await build({ + entryPoints: ["index.js"], + bundle: true, + platform: "browser", + external: ["node:crypto"], + write: false, +}); +const source = new TextDecoder().decode(result.outputFiles[0].contents); + +// https://playwright.dev/docs/network#modify-requests + +test("browser", async ({ page }) => { + // Patch the API endpoint to work around CORS for now. + await page.route( + "https://api.replicate.com/v1/predictions", + async (route) => { + // Fetch original response. + const response = await route.fetch(); + // Add a prefix to the title. + return route.fulfill({ response }); + } + ); + + await page.addScriptTag({ content: source }); + const result = await page.evaluate( + (token) => window.main(token), + [process.env.REPLICATE_API_TOKEN] + ); + expect(result).toBe("hello there, Betty Browser"); +}); diff --git a/integration/browser/package.json b/integration/browser/package.json new file mode 100644 index 00000000..91ba1791 --- /dev/null +++ b/integration/browser/package.json @@ -0,0 +1,19 @@ +{ + "name": "replicate-app-browser", + "private": true, + "version": "0.0.0", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "test": "playwright test --browser all" + }, + "license": "ISC", + "dependencies": { + "replicate": "../../" + }, + "devDependencies": { + "@playwright/test": "^1.42.1", + "esbuild": "^0.20.1" + } +} diff --git a/integration/browser/playwright.config.ts b/integration/browser/playwright.config.ts new file mode 100644 index 00000000..142a1777 --- /dev/null +++ b/integration/browser/playwright.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({}); diff --git a/lib/stream.js b/lib/stream.js index cd9274c2..a6e5f845 100644 --- a/lib/stream.js +++ b/lib/stream.js @@ -1,6 +1,7 @@ // Attempt to use readable-stream if available, attempt to use the built-in stream module. const ApiError = require("./error"); +const { streamAsyncIterator } = require("./util"); const { EventSourceParserStream, } = require("../vendor/eventsource-parser/stream"); @@ -73,7 +74,7 @@ function createReadableStream({ url, fetch, options = {} }) { .pipeThrough(new TextDecoderStream()) .pipeThrough(new EventSourceParserStream()); - for await (const event of stream) { + for await (const event of streamAsyncIterator(stream)) { if (event.event === "error") { controller.error(new Error(event.data)); break; diff --git a/lib/util.js b/lib/util.js index ff9daccc..22a14c89 100644 --- a/lib/util.js +++ b/lib/util.js @@ -354,9 +354,33 @@ function parseProgressFromLogs(input) { return null; } +/** + * Helper to make any `ReadableStream` iterable, this is supported + * by most server runtimes but browsers still haven't implemented + * it yet. + * See: https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream#browser_compatibility + * + * @template T + * @param {ReadableStream} stream an instance of a `ReadableStream` + * @yields {T} a chunk/event from the stream + */ +async function* streamAsyncIterator(stream) { + const reader = stream.getReader(); + try { + while (true) { + const { done, value } = await reader.read(); + if (done) return; + yield value; + } + } finally { + reader.releaseLock(); + } +} + module.exports = { transformFileInputs, validateWebhook, withAutomaticRetries, parseProgressFromLogs, + streamAsyncIterator, }; diff --git a/tsconfig.json b/tsconfig.json index e6b4ed6c..8a2eb357 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,9 +5,5 @@ "strict": true, "allowJs": true }, - "exclude": [ - "**/node_modules", - "integration/**" - ] + "exclude": ["integration/**", "**/node_modules"] } - From f280b0b4c33fe2601d60ac4b2b993488b0358c56 Mon Sep 17 00:00:00 2001 From: Mattt Date: Tue, 19 Mar 2024 01:41:51 -0700 Subject: [PATCH 112/207] Switch to replicate.stream in Bun integration test (#227) * Switch to replicate.stream in Bun integration test * Vendor @stardazed/streams-text-encoding * Modify vendored source of @stardazed/streams-text-encoding * Use polyfill when TextDecoderStream isn't available * Use globalThis instead of global * Set 30s test timeout * Add information about vendoring and patching TextDecoderStream polyfill * Use replicate/canary for streaming test * Fix README --- .github/workflows/ci.yml | 2 +- README.md | 39 +++++--- integration/bun/index.test.ts | 2 +- integration/bun/index.ts | 22 +++-- lib/stream.js | 4 + .../text-decoder-stream.js | 95 +++++++++++++++++++ 6 files changed, 143 insertions(+), 21 deletions(-) create mode 100644 vendor/streams-text-encoding/text-decoder-stream.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5fe3c622..f42a2916 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -166,4 +166,4 @@ jobs: cd integration/${{ matrix.suite }} bun uninstall replicate bun install "file:../../${{ needs.build.outputs.tarball-name }}" - bun test + bun test --timeout 30000 diff --git a/README.md b/README.md index 255c8f18..70a1a9c5 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ app.get('/webhooks/replicate', async (c) => { const prediction = await c.req.json(); console.log(prediction); //=> {"id": "xyz", "status": "successful", ... } - + // Acknowledge the webhook. c.status(200); c.json({ok: true}); @@ -217,15 +217,15 @@ Run a model and await the result. Unlike [`replicate.prediction.create`](#replic const output = await replicate.run(identifier, options, progress); ``` -| name | type | description | -| ------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `identifier` | string | **Required**. The model version identifier in the format `{owner}/{name}:{version}`, for example `stability-ai/sdxl:8beff3369e81422112d93b89ca01426147de542cd4684c244b673b105188fe5f` | -| `options.input` | object | **Required**. An object with the model inputs. | -| `options.wait` | object | Options for waiting for the prediction to finish | -| `options.wait.interval` | number | Polling interval in milliseconds. Defaults to 500 | -| `options.webhook` | string | An HTTPS URL for receiving a webhook when the prediction has new output | -| `options.webhook_events_filter` | string[] | An array of events which should trigger [webhooks](https://replicate.com/docs/webhooks). Allowable values are `start`, `output`, `logs`, and `completed` | -| `options.signal` | object | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the prediction | +| name | type | description | +| ------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `identifier` | string | **Required**. The model version identifier in the format `{owner}/{name}:{version}`, for example `stability-ai/sdxl:8beff3369e81422112d93b89ca01426147de542cd4684c244b673b105188fe5f` | +| `options.input` | object | **Required**. An object with the model inputs. | +| `options.wait` | object | Options for waiting for the prediction to finish | +| `options.wait.interval` | number | Polling interval in milliseconds. Defaults to 500 | +| `options.webhook` | string | An HTTPS URL for receiving a webhook when the prediction has new output | +| `options.webhook_events_filter` | string[] | An array of events which should trigger [webhooks](https://replicate.com/docs/webhooks). Allowable values are `start`, `output`, `logs`, and `completed` | +| `options.signal` | object | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the prediction | | `progress` | function | Callback function that receives the prediction object as it's updated. The function is called when the prediction is created, each time it's updated while polling for completion, and when it's completed. | Throws `Error` if the prediction failed. @@ -246,7 +246,7 @@ Example that logs progress as the model is running: const model = "stability-ai/sdxl:8beff3369e81422112d93b89ca01426147de542cd4684c244b673b105188fe5f"; const input = { prompt: "a 19th century portrait of a raccoon gentleman wearing a suit" }; const onProgress = (prediction) => { - const last_log_line = prediction.logs.split("\n").pop() + const last_log_line = prediction.logs.split("\n").pop() console.log({id: prediction.id, log: last_log_line}) } const output = await replicate.run(model, { input }, onProgress) @@ -875,6 +875,21 @@ The `Replicate` constructor and all `replicate.*` methods are fully typed. We have a few dependencies that have been bundled into the vendor directory rather than adding external npm dependencies. -These have been generated using bundlejs.com and copied into the appropriate directory along with the license and repository information. +These have been generated using bundlejs.com and copied into the appropriate directory along with the license and repository information. * [eventsource-parser/stream](https://bundlejs.com/?bundle&q=eventsource-parser%40latest%2Fstream&config=%7B%22esbuild%22%3A%7B%22format%22%3A%22cjs%22%2C%22minify%22%3Afalse%2C%22platform%22%3A%22neutral%22%7D%7D) +* [streams-text-encoding/text-decoder-stream](https://bundlejs.com/?q=%40stardazed%2Fstreams-text-encoding&treeshake=%5B%7B+TextDecoderStream+%7D%5D&config=%7B%22esbuild%22%3A%7B%22format%22%3A%22cjs%22%2C%22minify%22%3Afalse%7D%7D) + +> [!NOTE] +> The vendored implementation of `TextDecoderStream` requires +> the following patch to be applied to the output of bundlejs.com: +> +> ```diff +> constructor(label, options) { +> - this[decDecoder] = new TextDecoder(label, options); +> - this[decTransform] = new TransformStream(new TextDecodeTransformer(this[decDecoder])); +> + const decoder = new TextDecoder(label || "utf-8", options || {}); +> + this[decDecoder] = decoder; +> + this[decTransform] = new TransformStream(new TextDecodeTransformer(decoder)); +> } +> ``` diff --git a/integration/bun/index.test.ts b/integration/bun/index.test.ts index e9fbaab0..1357e5ba 100644 --- a/integration/bun/index.test.ts +++ b/integration/bun/index.test.ts @@ -19,5 +19,5 @@ import type { test("main", async () => { const output = await main(); - expect(output as any).toEqual("hello Brünnhilde Bun"); + expect(output).toContain("Brünnhilde Bun"); }); diff --git a/integration/bun/index.ts b/integration/bun/index.ts index 263a86ec..fc95399c 100644 --- a/integration/bun/index.ts +++ b/integration/bun/index.ts @@ -5,12 +5,20 @@ const replicate = new Replicate({ }); export default async function main() { - return await replicate.run( - "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", - { - input: { - text: "Brünnhilde Bun", - }, + const model = + "replicate/canary:30e22229542eb3f79d4f945dacb58d32001b02cc313ae6f54eef27904edf3272"; + const options = { + input: { + text: "Brünnhilde Bun", + }, + }; + const output = []; + + for await (const { event, data } of replicate.stream(model, options)) { + if (event === "output") { + output.push(data); } - ); + } + + return output.join("").trim(); } diff --git a/lib/stream.js b/lib/stream.js index a6e5f845..2e0bbde3 100644 --- a/lib/stream.js +++ b/lib/stream.js @@ -5,6 +5,10 @@ const { streamAsyncIterator } = require("./util"); const { EventSourceParserStream, } = require("../vendor/eventsource-parser/stream"); +const { TextDecoderStream } = + typeof globalThis.TextDecoderStream === "undefined" + ? require("../vendor/streams-text-encoding/text-decoder-stream") + : globalThis; /** * A server-sent event. diff --git a/vendor/streams-text-encoding/text-decoder-stream.js b/vendor/streams-text-encoding/text-decoder-stream.js new file mode 100644 index 00000000..f400709d --- /dev/null +++ b/vendor/streams-text-encoding/text-decoder-stream.js @@ -0,0 +1,95 @@ +// Adapted from https://github.com/stardazed/sd-streams +// +// MIT License +// +// Copyright (c) 2018-Present @zenmumbler +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// /input.ts +var input_exports = {}; +__export(input_exports, { + TextDecoderStream: () => TextDecoderStream +}); +module.exports = __toCommonJS(input_exports); + +// http-url:https://unpkg.com/@stardazed/streams-text-encoding@1.0.2/dist/sd-streams-text-encoding.esm.js +var decDecoder = Symbol("decDecoder"); +var decTransform = Symbol("decTransform"); +var TextDecodeTransformer = class { + constructor(decoder) { + this.decoder_ = decoder; + } + transform(chunk, controller) { + if (!(chunk instanceof ArrayBuffer || ArrayBuffer.isView(chunk))) { + throw new TypeError("Input data must be a BufferSource"); + } + const text = this.decoder_.decode(chunk, { stream: true }); + if (text.length !== 0) { + controller.enqueue(text); + } + } + flush(controller) { + const text = this.decoder_.decode(); + if (text.length !== 0) { + controller.enqueue(text); + } + } +}; +var TextDecoderStream = class { + constructor(label, options) { + const decoder = new TextDecoder(label || "utf-8", options || {}); + this[decDecoder] = decoder; + this[decTransform] = new TransformStream(new TextDecodeTransformer(decoder)); + } + get encoding() { + return this[decDecoder].encoding; + } + get fatal() { + return this[decDecoder].fatal; + } + get ignoreBOM() { + return this[decDecoder].ignoreBOM; + } + get readable() { + return this[decTransform].readable; + } + get writable() { + return this[decTransform].writable; + } +}; +var encEncoder = Symbol("encEncoder"); +var encTransform = Symbol("encTransform"); From 1c9838226369eecb38092fcceab3330e8202325a Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Tue, 19 Mar 2024 08:51:16 +0000 Subject: [PATCH 113/207] Add support for new deployment endpoints (#223) * Add support for new deployment endpoints * Align definition for Deployment type to OpenAPI specification * Add test coverage for deployment endpoints --------- Co-authored-by: Mattt Zmuda --- index.d.ts | 30 ++++++++++- index.js | 3 ++ index.test.ts | 129 +++++++++++++++++++++++++++++++++++++++++++++ lib/deployments.js | 73 +++++++++++++++++++++++++ tsconfig.json | 2 +- 5 files changed, 234 insertions(+), 3 deletions(-) diff --git a/index.d.ts b/index.d.ts index abf68dce..d1602adf 100644 --- a/index.d.ts +++ b/index.d.ts @@ -33,8 +33,10 @@ declare module "replicate" { created_by: Account; configuration: { hardware: string; - min_instances: number; - max_instances: number; + scaling: { + min_instances: number; + max_instances: number; + }; }; }; } @@ -194,6 +196,30 @@ declare module "replicate" { deployment_owner: string, deployment_name: string ): Promise; + create(deployment_config: { + name: string; + model: string; + version: string; + hardware: string; + min_instances: number; + max_instances: number; + }): Promise; + update( + deployment_owner: string, + deployment_name: string, + deployment_config: { + version?: string; + hardware?: string; + min_instances?: number; + max_instances?: number; + } & ( + | { version: string } + | { hardware: string } + | { min_instances: number } + | { max_instances: number } + ) + ): Promise; + list(): Promise>; }; hardware: { diff --git a/index.js b/index.js index 6c6590b5..042af91c 100644 --- a/index.js +++ b/index.js @@ -67,6 +67,9 @@ class Replicate { this.deployments = { get: deployments.get.bind(this), + create: deployments.create.bind(this), + update: deployments.update.bind(this), + list: deployments.list.bind(this), predictions: { create: deployments.predictions.create.bind(this), }, diff --git a/index.test.ts b/index.test.ts index 7e0ae22e..55adee59 100644 --- a/index.test.ts +++ b/index.test.ts @@ -811,6 +811,135 @@ describe("Replicate client", () => { // Add more tests for error handling, edge cases, etc. }); + describe("deployments.create", () => { + test("Calls the correct API route with the correct payload", async () => { + nock(BASE_URL) + .post("/deployments") + .reply(200, { + owner: "acme", + name: "my-app-image-generator", + current_release: { + number: 1, + model: "stability-ai/sdxl", + version: + "da77bc59ee60423279fd632efb4795ab731d9e3ca9705ef3341091fb989b7eaf", + created_at: "2024-02-15T16:32:57.018467Z", + created_by: { + type: "organization", + username: "acme", + name: "Acme Corp, Inc.", + github_url: "https://github.com/acme", + }, + configuration: { + hardware: "gpu-t4", + scaling: { + min_instances: 1, + max_instances: 5, + }, + }, + }, + }); + + const deployment = await client.deployments.create({ + name: "my-app-image-generator", + model: "stability-ai/sdxl", + version: + "da77bc59ee60423279fd632efb4795ab731d9e3ca9705ef3341091fb989b7eaf", + hardware: "gpu-t4", + min_instances: 1, + max_instances: 5, + }); + + expect(deployment.owner).toBe("acme"); + expect(deployment.name).toBe("my-app-image-generator"); + expect(deployment.current_release.model).toBe("stability-ai/sdxl"); + }); + // Add more tests for error handling, edge cases, etc. + }); + + describe("deployments.update", () => { + test("Calls the correct API route with the correct payload", async () => { + nock(BASE_URL) + .patch("/deployments/acme/my-app-image-generator") + .reply(200, { + owner: "acme", + name: "my-app-image-generator", + current_release: { + number: 2, + model: "stability-ai/sdxl", + version: + "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", + created_at: "2024-02-16T08:14:22.345678Z", + created_by: { + type: "organization", + username: "acme", + name: "Acme Corp, Inc.", + github_url: "https://github.com/acme", + }, + configuration: { + hardware: "gpu-a40-large", + scaling: { + min_instances: 3, + max_instances: 10, + }, + }, + }, + }); + + const deployment = await client.deployments.update( + "acme", + "my-app-image-generator", + { + version: + "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", + hardware: "gpu-a40-large", + min_instances: 3, + max_instances: 10, + } + ); + + expect(deployment.current_release.number).toBe(2); + expect(deployment.current_release.version).toBe( + "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532" + ); + expect(deployment.current_release.configuration.hardware).toBe( + "gpu-a40-large" + ); + expect( + deployment.current_release.configuration.scaling?.min_instances + ).toBe(3); + expect( + deployment.current_release.configuration.scaling?.max_instances + ).toBe(10); + }); + // Add more tests for error handling, edge cases, etc. + }); + + describe("deployments.list", () => { + test("Calls the correct API route", async () => { + nock(BASE_URL) + .get("/deployments") + .reply(200, { + next: null, + previous: null, + results: [ + { + owner: "acme", + name: "my-app-image-generator", + current_release: { + // ... + }, + }, + // ... + ], + }); + + const deployments = await client.deployments.list(); + expect(deployments.results.length).toBe(1) + }); + // Add more tests for pagination, error handling, edge cases, etc. + }); + describe("predictions.create with model", () => { test("Calls the correct API route with the correct payload", async () => { nock(BASE_URL) diff --git a/lib/deployments.js b/lib/deployments.js index 3e1ceebd..4f6f3c6b 100644 --- a/lib/deployments.js +++ b/lib/deployments.js @@ -57,9 +57,82 @@ async function getDeployment(deployment_owner, deployment_name) { return response.json(); } +/** + * @typedef {Object} DeploymentCreateRequest - Request body for `deployments.create` + * @property {string} name - the name of the deployment + * @property {string} model - the full name of the model that you want to deploy e.g. stability-ai/sdxl + * @property {string} version - the 64-character string ID of the model version that you want to deploy + * @property {string} hardware - the SKU for the hardware used to run the model, via `replicate.hardware.list()` + * @property {number} min_instances - the minimum number of instances for scaling + * @property {number} max_instances - the maximum number of instances for scaling + */ + +/** + * Create a deployment + * + * @param {DeploymentCreateRequest} config - Required. The deployment config. + * @returns {Promise} Resolves with the deployment data + */ +async function createDeployment(deployment_config) { + const response = await this.request("/deployments", { + method: "POST", + data: deployment_config, + }); + + return response.json(); +} + +/** + * @typedef {Object} DeploymentUpdateRequest - Request body for `deployments.update` + * @property {string} version - the 64-character string ID of the model version that you want to deploy + * @property {string} hardware - the SKU for the hardware used to run the model, via `replicate.hardware.list()` + * @property {number} min_instances - the minimum number of instances for scaling + * @property {number} max_instances - the maximum number of instances for scaling + */ + +/** + * Update an existing deployment + * + * @param {string} deployment_owner - Required. The username of the user or organization who owns the deployment + * @param {string} deployment_name - Required. The name of the deployment + * @param {DeploymentUpdateRequest} deployment_config - Required. The deployment changes. + * @returns {Promise} Resolves with the deployment data + */ +async function updateDeployment( + deployment_owner, + deployment_name, + deployment_config +) { + const response = await this.request( + `/deployments/${deployment_owner}/${deployment_name}`, + { + method: "PATCH", + data: deployment_config, + } + ); + + return response.json(); +} + +/** + * List all deployments + * + * @returns {Promise} - Resolves with a page of deployments + */ +async function listDeployments() { + const response = await this.request("/deployments", { + method: "GET", + }); + + return response.json(); +} + module.exports = { predictions: { create: createPrediction, }, get: getDeployment, + create: createDeployment, + update: updateDeployment, + list: listDeployments, }; diff --git a/tsconfig.json b/tsconfig.json index 8a2eb357..d77efdc5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,5 +5,5 @@ "strict": true, "allowJs": true }, - "exclude": ["integration/**", "**/node_modules"] + "exclude": ["**/node_modules", "integration"] } From be0fceeeae60aa49a28a76cfa22a7408d275d39f Mon Sep 17 00:00:00 2001 From: Mattt Date: Tue, 19 Mar 2024 02:33:43 -0700 Subject: [PATCH 114/207] Update README with deployments methods (#228) --- README.md | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 70a1a9c5..e8ecd578 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ const app = new Hono(); app.get('/webhooks/replicate', async (c) => { // Get the prediction from the request. const prediction = await c.req.json(); - console.log(prediction); + console.log(prediction); //=> {"id": "xyz", "status": "successful", ... } // Acknowledge the webhook. @@ -828,6 +828,112 @@ const response = await replicate.deployments.predictions.create(deployment_owner Use `replicate.wait` to wait for a prediction to finish, or `replicate.predictions.cancel` to cancel a prediction before it finishes. +### `replicate.deployments.list` + +List your deployments. + +```js +const response = await replicate.deployments.list(); +``` + +```jsonc +{ + "next": null, + "previous": null, + "results": [ + { + "owner": "acme", + "name": "my-app-image-generator", + "current_release": { /* ... */ } + } + /* ... */ + ] +} +``` + +### `replicate.deployments.create` + +Create a new deployment. + +```js +const response = await replicate.deployments.create(options); +``` + +| name | type | description | +| ----------------------- | ------ | -------------------------------------------------------------------------------- | +| `options.name` | string | Required. Name of the new deployment | +| `options.model` | string | Required. Name of the model in the format `{username}/{model_name}` | +| `options.version` | string | Required. ID of the model version | +| `options.hardware` | string | Required. SKU of the hardware to run the deployment on (`cpu`, `gpu-a100`, etc.) | +| `options.min_instances` | number | Minimum number of instances to run. Defaults to 0 | +| `options.max_instances` | number | Maximum number of instances to scale up to based on traffic. Defaults to 1 | + +```jsonc +{ + "owner": "acme", + "name": "my-app-image-generator", + "current_release": { + "number": 1, + "model": "stability-ai/sdxl", + "version": "da77bc59ee60423279fd632efb4795ab731d9e3ca9705ef3341091fb989b7eaf", + "created_at": "2024-03-14T11:43:32.049157Z", + "created_by": { + "type": "organization", + "username": "acme", + "name": "Acme, Inc.", + "github_url": "https://github.com/replicate" + }, + "configuration": { + "hardware": "gpu-a100", + "min_instances": 1, + "max_instances": 0 + } + } +} +``` + +### `replicate.deployments.update` + +Update an existing deployment. + +```js +const response = await replicate.deployments.update(deploymentOwner, deploymentName, options); +``` + +| name | type | description | +| ----------------------- | ------ | -------------------------------------------------------------------------------- | +| `deploymentOwner` | string | Required. Owner of the deployment | +| `deploymentName` | string | Required. Name of the deployment to update | +| `options.model` | string | Name of the model in the format `{username}/{model_name}` | +| `options.version` | string | ID of the model version | +| `options.hardware` | string | Required. SKU of the hardware to run the deployment on (`cpu`, `gpu-a100`, etc.) | +| `options.min_instances` | number | Minimum number of instances to run | +| `options.max_instances` | number | Maximum number of instances to scale up to | + +```jsonc +{ + "owner": "acme", + "name": "my-app-image-generator", + "current_release": { + "number": 2, + "model": "stability-ai/sdxl", + "version": "39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b", + "created_at": "2024-03-14T11:43:32.049157Z", + "created_by": { + "type": "organization", + "username": "acme", + "name": "Acme, Inc.", + "github_url": "https://github.com/replicate" + }, + "configuration": { + "hardware": "gpu-a100", + "min_instances": 1, + "max_instances": 0 + } + } +} +``` + ### `replicate.paginate` Pass another method as an argument to iterate over results From f7e561cecec6a919b2d49e04d74c7a9e0b6b2a60 Mon Sep 17 00:00:00 2001 From: Mattt Date: Tue, 19 Mar 2024 02:43:48 -0700 Subject: [PATCH 115/207] Unset Bun integration test timeout and automatically retry 3 times (#230) * Unset Bun integration test timeout * Automatically retry bun test * Add log statements for received events --- .github/workflows/ci.yml | 5 ++++- integration/bun/index.ts | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f42a2916..74995fc8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -166,4 +166,7 @@ jobs: cd integration/${{ matrix.suite }} bun uninstall replicate bun install "file:../../${{ needs.build.outputs.tarball-name }}" - bun test --timeout 30000 + retries=3 + for ((i=0; i Date: Tue, 19 Mar 2024 02:47:17 -0700 Subject: [PATCH 116/207] 0.29.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1d6c9cdd..9a3a3f3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.28.1", + "version": "0.29.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.28.1", + "version": "0.29.0", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 123574d7..26f546e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.28.1", + "version": "0.29.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From e42aa29f26494316e47d6ea9812f1fc760bd1021 Mon Sep 17 00:00:00 2001 From: Mattt Date: Thu, 21 Mar 2024 03:53:38 -0700 Subject: [PATCH 117/207] Move min and max instances properties to deployment configuration (#232) --- index.d.ts | 6 ++---- index.test.ts | 28 +++++++++------------------- 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/index.d.ts b/index.d.ts index d1602adf..31a23251 100644 --- a/index.d.ts +++ b/index.d.ts @@ -33,10 +33,8 @@ declare module "replicate" { created_by: Account; configuration: { hardware: string; - scaling: { - min_instances: number; - max_instances: number; - }; + min_instances: number; + max_instances: number; }; }; } diff --git a/index.test.ts b/index.test.ts index 55adee59..d2f064cb 100644 --- a/index.test.ts +++ b/index.test.ts @@ -791,10 +791,8 @@ describe("Replicate client", () => { }, configuration: { hardware: "gpu-t4", - scaling: { - min_instances: 1, - max_instances: 5, - }, + min_instances: 1, + max_instances: 5, }, }, }); @@ -832,10 +830,8 @@ describe("Replicate client", () => { }, configuration: { hardware: "gpu-t4", - scaling: { - min_instances: 1, - max_instances: 5, - }, + min_instances: 1, + max_instances: 5, }, }, }); @@ -878,10 +874,8 @@ describe("Replicate client", () => { }, configuration: { hardware: "gpu-a40-large", - scaling: { - min_instances: 3, - max_instances: 10, - }, + min_instances: 3, + max_instances: 10, }, }, }); @@ -905,12 +899,8 @@ describe("Replicate client", () => { expect(deployment.current_release.configuration.hardware).toBe( "gpu-a40-large" ); - expect( - deployment.current_release.configuration.scaling?.min_instances - ).toBe(3); - expect( - deployment.current_release.configuration.scaling?.max_instances - ).toBe(10); + expect(deployment.current_release.configuration.min_instances).toBe(3); + expect(deployment.current_release.configuration.max_instances).toBe(10); }); // Add more tests for error handling, edge cases, etc. }); @@ -935,7 +925,7 @@ describe("Replicate client", () => { }); const deployments = await client.deployments.list(); - expect(deployments.results.length).toBe(1) + expect(deployments.results.length).toBe(1); }); // Add more tests for pagination, error handling, edge cases, etc. }); From 19a5ca92454ffd48d9aac7ed6a65950babef635c Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Thu, 21 Mar 2024 03:54:00 -0700 Subject: [PATCH 118/207] 0.29.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9a3a3f3c..1eb7db51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.29.0", + "version": "0.29.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.29.0", + "version": "0.29.1", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 26f546e4..ba884be8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.29.0", + "version": "0.29.1", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From c77125a8af9c15b810a037ff638b5733524b6c0b Mon Sep 17 00:00:00 2001 From: Mattt Date: Wed, 10 Apr 2024 04:22:07 -0700 Subject: [PATCH 119/207] Fix CI for Node 20.x (#239) * Update to actions/checkout@v4 * Use actions/setup-node@v4 * Add missing node-version value to integration-browser * npm i --save-dev nock@beta * Pin to Node.js 20.11.1 --- .github/workflows/ci.yml | 26 ++++++++++++++------------ package-lock.json | 9 ++++----- package.json | 2 +- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 74995fc8..966cb77e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,12 +13,13 @@ jobs: strategy: matrix: # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases - node-version: [18.x, 20.x] + # FIXME: Tests are failing on Node.js v20.12.1 (https://github.com/replicate/replicate-javascript/issues/237) + node-version: [18.x, 20.11.1] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: "npm" @@ -36,9 +37,9 @@ jobs: tarball-name: ${{ steps.pack.outputs.tarball-name }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 20.x cache: "npm" @@ -67,12 +68,12 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/download-artifact@v3 with: name: package-tarball - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: "npm" @@ -90,17 +91,18 @@ jobs: strategy: matrix: + node-version: [20.x] browser: ["chromium", "firefox", "webkit"] suite: ["browser"] fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/download-artifact@v3 with: name: package-tarball - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: "npm" @@ -125,12 +127,12 @@ jobs: suite: [cloudflare-worker] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/download-artifact@v3 with: name: package-tarball - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: "npm" @@ -154,7 +156,7 @@ jobs: suite: [bun] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/download-artifact@v3 with: name: package-tarball diff --git a/package-lock.json b/package-lock.json index 1eb7db51..caab4f7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@typescript-eslint/eslint-plugin": "^5.56.0", "cross-fetch": "^3.1.5", "jest": "^29.6.2", - "nock": "^14.0.0-beta.4", + "nock": "^14.0.0-beta.5", "publint": "^0.2.7", "ts-jest": "^29.1.0", "typescript": "^5.0.2" @@ -4077,12 +4077,11 @@ "dev": true }, "node_modules/nock": { - "version": "14.0.0-beta.4", - "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.0-beta.4.tgz", - "integrity": "sha512-N9GIOnNFas/TtdCQpavpi6A6SyVVInkD/vrUCF2u51vlE2wSnqfPifVli6xSX8l6Lz/3sdSwPusE9n3KPDDh0g==", + "version": "14.0.0-beta.5", + "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.0-beta.5.tgz", + "integrity": "sha512-u255tf4DYvyErTlPZA9uTfXghiZZy+NflUOFONPVKZ5tP0yaHwKig28zyFOLhu8y5YcCRC+V5vDk4HHileh2iw==", "dev": true, "dependencies": { - "debug": "^4.1.0", "json-stringify-safe": "^5.0.1", "propagate": "^2.0.0" }, diff --git a/package.json b/package.json index ba884be8..1d838dff 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@typescript-eslint/eslint-plugin": "^5.56.0", "cross-fetch": "^3.1.5", "jest": "^29.6.2", - "nock": "^14.0.0-beta.4", + "nock": "^14.0.0-beta.5", "publint": "^0.2.7", "ts-jest": "^29.1.0", "typescript": "^5.0.2" From 8605cb0556f2b9605b2678b3b99db594d57fc86f Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Wed, 10 Apr 2024 04:25:08 -0700 Subject: [PATCH 120/207] fix broken link in README (#236) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e8ecd578..f6d798c8 100644 --- a/README.md +++ b/README.md @@ -385,7 +385,7 @@ const response = await replicate.models.create(model_owner, model_name, options) | `model_owner` | string | **Required**. The name of the user or organization that will own the model. This must be the same as the user or organization that is making the API request. In other words, the API token used in the request must belong to this user or organization. | | `model_name` | string | **Required**. The name of the model. This must be unique among all models owned by the user or organization. | | `options.visibility` | string | **Required**. Whether the model should be public or private. A public model can be viewed and run by anyone, whereas a private model can be viewed and run only by the user or organization members that own the model. | -| `options.hardware` | string | **Required**. The SKU for the hardware used to run the model. Possible values can be found by calling [`replicate.hardware.list()](#replicatehardwarelist)`. | +| `options.hardware` | string | **Required**. The SKU for the hardware used to run the model. Possible values can be found by calling [`replicate.hardware.list()`](#replicatehardwarelist). | | `options.description` | string | A description of the model. | | `options.github_url` | string | A URL for the model's source code on GitHub. | | `options.paper_url` | string | A URL for the model's paper. | From d555cb13ee8f28a37de567b734a4165dc4b480fb Mon Sep 17 00:00:00 2001 From: Mattt Date: Thu, 25 Apr 2024 04:26:43 -0700 Subject: [PATCH 121/207] Use Bearer authorization scheme (#246) --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 042af91c..ce5d502c 100644 --- a/index.js +++ b/index.js @@ -218,7 +218,7 @@ class Replicate { const headers = {}; if (auth) { - headers["Authorization"] = `Token ${auth}`; + headers["Authorization"] = `Bearer ${auth}`; } headers["Content-Type"] = "application/json"; headers["User-Agent"] = userAgent; From 9f1a89cb20f5fc07ca0c328d5fc48d9016e08488 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Thu, 2 May 2024 12:53:05 +0100 Subject: [PATCH 122/207] Extract `signal` property from the `run()` options This prevents it being passed to the backend as part of the body. The backend has recently started validating the body payload so this is now resulting as an API error. Fixes #249 --- index.js | 4 +--- index.test.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index ce5d502c..74f667b1 100644 --- a/index.js +++ b/index.js @@ -129,7 +129,7 @@ class Replicate { * @returns {Promise} - Resolves with the output of running the model */ async run(ref, options, progress) { - const { wait, ...data } = options; + const { wait, signal, ...data } = options; const identifier = ModelVersionIdentifier.parse(ref); @@ -153,8 +153,6 @@ class Replicate { progress(prediction); } - const { signal } = options; - prediction = await this.wait( prediction, wait || {}, diff --git a/index.test.ts b/index.test.ts index d2f064cb..f62c7bc5 100644 --- a/index.test.ts +++ b/index.test.ts @@ -1233,11 +1233,14 @@ describe("Replicate client", () => { test("Aborts the operation when abort signal is invoked", async () => { const controller = new AbortController(); const { signal } = controller; + let body: Record | undefined; const scope = nock(BASE_URL) - .post("/predictions", (body) => { + .post("/predictions", (_body) => { + // Should not pass the signal object in the body. + body = _body; controller.abort(); - return body; + return _body; }) .reply(201, { id: "ufawqhfynnddngldkgtslldrkq", @@ -1263,7 +1266,10 @@ describe("Replicate client", () => { } ); + expect(body).toBeDefined(); + expect(body?.["signal"]).toBeUndefined(); expect(signal.aborted).toBe(true); + expect(output).toBeUndefined(); scope.done(); }); From 9c54b7ebd7d9f0965fcfcc4f5211a0b9012846c6 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Thu, 2 May 2024 12:54:41 +0100 Subject: [PATCH 123/207] Call the `onProgress` handler with the canceled prediction Previously when aborting a `run()` request we were dropping the final canceled prediction object and calling the `onProgress` callback with a stale "processing" object. --- index.js | 8 ++++++-- index.test.ts | 25 +++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 74f667b1..79b8f4dc 100644 --- a/index.js +++ b/index.js @@ -162,8 +162,8 @@ class Replicate { progress(updatedPrediction); } - if (signal && signal.aborted) { - await this.predictions.cancel(updatedPrediction.id); + // We handle the cancel later in the function. + if (signal?.aborted) { return true; // stop polling } @@ -171,6 +171,10 @@ class Replicate { } ); + if (signal?.aborted) { + prediction = await this.predictions.cancel(prediction.id); + } + // Call progress callback with the completed prediction object if (progress) { progress(prediction); diff --git a/index.test.ts b/index.test.ts index f62c7bc5..53737e05 100644 --- a/index.test.ts +++ b/index.test.ts @@ -1258,12 +1258,14 @@ describe("Replicate client", () => { status: "canceled", }); - await client.run( + const onProgress = jest.fn(); + const output = await client.run( "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", { input: { text: "Hello, world!" }, signal, - } + }, + onProgress ); expect(body).toBeDefined(); @@ -1271,6 +1273,25 @@ describe("Replicate client", () => { expect(signal.aborted).toBe(true); expect(output).toBeUndefined(); + expect(onProgress).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + status: "processing", + }) + ); + expect(onProgress).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + status: "processing", + }) + ); + expect(onProgress).toHaveBeenNthCalledWith( + 3, + expect.objectContaining({ + status: "canceled", + }) + ); + scope.done(); }); }); From 397e0830fe10b9c10b81fc12b27332f28ed558e3 Mon Sep 17 00:00:00 2001 From: Mattt Date: Thu, 2 May 2024 06:03:35 -0700 Subject: [PATCH 124/207] Unpin Node 20 version in CI (#240) * Unpin Node 20 version in CI * Update dev dependencies --- .github/workflows/ci.yml | 3 +- package-lock.json | 1206 ++++++++++++++++++-------------------- package.json | 4 +- 3 files changed, 589 insertions(+), 624 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 966cb77e..2144cbf3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,8 +13,7 @@ jobs: strategy: matrix: # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases - # FIXME: Tests are failing on Node.js v20.12.1 (https://github.com/replicate/replicate-javascript/issues/237) - node-version: [18.x, 20.11.1] + node-version: [18.x, 20.x] steps: - uses: actions/checkout@v4 diff --git a/package-lock.json b/package-lock.json index caab4f7b..776133d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,8 +13,8 @@ "@types/jest": "^29.5.3", "@typescript-eslint/eslint-plugin": "^5.56.0", "cross-fetch": "^3.1.5", - "jest": "^29.6.2", - "nock": "^14.0.0-beta.5", + "jest": "^29.7.0", + "nock": "^14.0.0-beta.6", "publint": "^0.2.7", "ts-jest": "^29.1.0", "typescript": "^5.0.2" @@ -43,119 +43,48 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz", - "integrity": "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", + "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.3.tgz", - "integrity": "sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", + "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.3", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-module-transforms": "^7.21.2", - "@babel/helpers": "^7.21.0", - "@babel/parser": "^7.21.3", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.3", - "@babel/types": "^7.21.3", - "convert-source-map": "^1.7.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.24.5", + "@babel/helpers": "^7.24.5", + "@babel/parser": "^7.24.5", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.5", + "@babel/types": "^7.24.5", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -165,30 +94,24 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", + "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", "dev": true, "dependencies": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.5", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -196,42 +119,39 @@ } }, "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", - "semver": "^6.3.0" + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -272,119 +192,120 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", - "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", + "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.2", - "@babel/types": "^7.21.2" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.24.3", + "@babel/helper-simple-access": "^7.24.5", + "@babel/helper-split-export-declaration": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.5" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", + "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", - "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", + "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", "dev": true, "dependencies": { - "@babel/types": "^7.20.2" + "@babel/types": "^7.24.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", + "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", + "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", - "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", - "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz", + "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==", "dev": true, "dependencies": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.0", - "@babel/types": "^7.21.0" + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.5", + "@babel/types": "^7.24.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", + "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.5", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -462,9 +383,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", + "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -534,12 +455,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", - "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", + "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -636,12 +557,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", - "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", + "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -651,34 +572,34 @@ } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", + "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.5", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", + "@babel/helper-split-export-declaration": "^7.24.5", + "@babel/parser": "^7.24.5", + "@babel/types": "^7.24.5", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -695,13 +616,13 @@ } }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", + "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.1", + "@babel/helper-validator-identifier": "^7.24.5", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1038,16 +959,16 @@ } }, "node_modules/@jest/console": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.2.tgz", - "integrity": "sha512-0N0yZof5hi44HAR2pPS+ikJ3nzKNoZdVu8FffRf3wy47I7Dm7etk/3KetMdRUqzVd16V4O2m2ISpNTbnIuqy1w==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "dependencies": { - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^29.6.2", - "jest-util": "^29.6.2", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0" }, "engines": { @@ -1055,37 +976,37 @@ } }, "node_modules/@jest/core": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.2.tgz", - "integrity": "sha512-Oj+5B+sDMiMWLhPFF+4/DvHOf+U10rgvCLGPHP8Xlsy/7QxS51aU/eBngudHlJXnaWD5EohAgJ4js+T6pa+zOg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, "dependencies": { - "@jest/console": "^29.6.2", - "@jest/reporters": "^29.6.2", - "@jest/test-result": "^29.6.2", - "@jest/transform": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.5.0", - "jest-config": "^29.6.2", - "jest-haste-map": "^29.6.2", - "jest-message-util": "^29.6.2", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.6.2", - "jest-resolve-dependencies": "^29.6.2", - "jest-runner": "^29.6.2", - "jest-runtime": "^29.6.2", - "jest-snapshot": "^29.6.2", - "jest-util": "^29.6.2", - "jest-validate": "^29.6.2", - "jest-watcher": "^29.6.2", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", - "pretty-format": "^29.6.2", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, @@ -1102,88 +1023,88 @@ } }, "node_modules/@jest/environment": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.2.tgz", - "integrity": "sha512-AEcW43C7huGd/vogTddNNTDRpO6vQ2zaQNrttvWV18ArBx9Z56h7BIsXkNFJVOO4/kblWEQz30ckw0+L3izc+Q==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, "dependencies": { - "@jest/fake-timers": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.6.2" + "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.2.tgz", - "integrity": "sha512-m6DrEJxVKjkELTVAztTLyS/7C92Y2b0VYqmDROYKLLALHn8T/04yPs70NADUYPrV3ruI+H3J0iUIuhkjp7vkfg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, "dependencies": { - "expect": "^29.6.2", - "jest-snapshot": "^29.6.2" + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.2.tgz", - "integrity": "sha512-6zIhM8go3RV2IG4aIZaZbxwpOzz3ZiM23oxAlkquOIole+G6TrbeXnykxWYlqF7kz2HlBjdKtca20x9atkEQYg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, "dependencies": { - "jest-get-type": "^29.4.3" + "jest-get-type": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.2.tgz", - "integrity": "sha512-euZDmIlWjm1Z0lJ1D0f7a0/y5Kh/koLFMUBE5SUYWrmy8oNhJpbTBDAP6CxKnadcMLDoDf4waRYCe35cH6G6PA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, "dependencies": { - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "jest-message-util": "^29.6.2", - "jest-mock": "^29.6.2", - "jest-util": "^29.6.2" + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/globals": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.2.tgz", - "integrity": "sha512-cjuJmNDjs6aMijCmSa1g2TNG4Lby/AeU7/02VtpW+SLcZXzOLK2GpN2nLqcFjmhy3B3AoPeQVx7BnyOf681bAw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.2", - "@jest/expect": "^29.6.2", - "@jest/types": "^29.6.1", - "jest-mock": "^29.6.2" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/reporters": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.2.tgz", - "integrity": "sha512-sWtijrvIav8LgfJZlrGCdN0nP2EWbakglJY49J1Y5QihcQLfy7ovyxxjJBRXMNltgt4uPtEcFmIMbVshEDfFWw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.6.2", - "@jest/test-result": "^29.6.2", - "@jest/transform": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", @@ -1192,13 +1113,13 @@ "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.6.2", - "jest-util": "^29.6.2", - "jest-worker": "^29.6.2", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", @@ -1216,10 +1137,26 @@ } } }, + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", + "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@jest/schemas": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz", - "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "dependencies": { "@sinclair/typebox": "^0.27.8" @@ -1229,9 +1166,9 @@ } }, "node_modules/@jest/source-map": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.0.tgz", - "integrity": "sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", @@ -1243,13 +1180,13 @@ } }, "node_modules/@jest/test-result": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.2.tgz", - "integrity": "sha512-3VKFXzcV42EYhMCsJQURptSqnyjqCGbtLuX5Xxb6Pm6gUf1wIRIl+mandIRGJyWKgNKYF9cnstti6Ls5ekduqw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, "dependencies": { - "@jest/console": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, @@ -1258,14 +1195,14 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.2.tgz", - "integrity": "sha512-GVYi6PfPwVejO7slw6IDO0qKVum5jtrJ3KoLGbgBWyr2qr4GaxFV6su+ZAjdTX75Sr1DkMFRk09r2ZVa+wtCGw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, "dependencies": { - "@jest/test-result": "^29.6.2", + "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.2", + "jest-haste-map": "^29.7.0", "slash": "^3.0.0" }, "engines": { @@ -1273,22 +1210,22 @@ } }, "node_modules/@jest/transform": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.2.tgz", - "integrity": "sha512-ZqCqEISr58Ce3U+buNFJYUktLJZOggfyvR+bZMaiV1e8B1SIvJbwZMrYz3gx/KAPn9EXmOmN+uB08yLCjWkQQg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.2", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.6.2", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", @@ -1299,12 +1236,12 @@ } }, "node_modules/@jest/types": { - "version": "29.6.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", - "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "dependencies": { - "@jest/schemas": "^29.6.0", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -1338,9 +1275,9 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "engines": { "node": ">=6.0.0" @@ -1353,13 +1290,13 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@nodelib/fs.scandir": { @@ -1404,9 +1341,9 @@ "dev": true }, "node_modules/@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, "dependencies": { "type-detect": "4.0.8" @@ -1422,9 +1359,9 @@ } }, "node_modules/@types/babel__core": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", - "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "dependencies": { "@babel/parser": "^7.20.7", @@ -1435,18 +1372,18 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", "dev": true, "dependencies": { "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, "dependencies": { "@babel/parser": "^7.1.0", @@ -1454,18 +1391,18 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", - "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", "dev": true, "dependencies": { - "@babel/types": "^7.3.0" + "@babel/types": "^7.20.7" } }, "node_modules/@types/graceful-fs": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", - "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", "dev": true, "dependencies": { "@types/node": "*" @@ -1866,15 +1803,15 @@ } }, "node_modules/babel-jest": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.2.tgz", - "integrity": "sha512-BYCzImLos6J3BH/+HvUCHG1dTf2MzmAB4jaVxHV+29RZLjR29XuYTmsf2sdDwkrb+FczkGo3kOhE7ga6sI0P4A==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, "dependencies": { - "@jest/transform": "^29.6.2", + "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.5.0", + "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" @@ -1903,9 +1840,9 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", - "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, "dependencies": { "@babel/template": "^7.3.3", @@ -1941,12 +1878,12 @@ } }, "node_modules/babel-preset-jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", - "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, "dependencies": { - "babel-plugin-jest-hoist": "^29.5.0", + "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "engines": { @@ -2005,9 +1942,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "dev": true, "funding": [ { @@ -2017,13 +1954,17 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" }, "bin": { "browserslist": "cli.js" @@ -2102,9 +2043,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001469", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001469.tgz", - "integrity": "sha512-Rcp7221ScNqQPP3W+lVOYDyjdR6dC+neEQCttoNr5bAyz54AboB4iwpnWgyi8P4YUsPybVzT4LgWiBbI3drL4g==", + "version": "1.0.30001615", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001615.tgz", + "integrity": "sha512-1IpazM5G3r38meiae0bHRnPhz+CBQ3ZLqbQMtrg+AsTPKAXgW38JNsXkyZ+v8waCsDmPq87lmfun5Q2AGysNEQ==", "dev": true, "funding": [ { @@ -2114,6 +2055,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ] }, @@ -2158,9 +2103,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", - "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz", + "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==", "dev": true }, "node_modules/cliui": { @@ -2223,6 +2168,27 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/cross-fetch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", @@ -2264,9 +2230,9 @@ } }, "node_modules/dedent": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", - "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", "dev": true, "peerDependencies": { "babel-plugin-macros": "^3.1.0" @@ -2303,9 +2269,9 @@ } }, "node_modules/diff-sequences": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -2337,9 +2303,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.337", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.337.tgz", - "integrity": "sha512-W8gdzXG86mVPoc56eM8YA+QiLxaAxJ8cmDjxZgfhLLWVvZQxyA918w5tX2JEWApZta45T1/sYcmFHTsTOUE3nw==", + "version": "1.4.754", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.754.tgz", + "integrity": "sha512-7Kr5jUdns5rL/M9wFFmMZAgFDuL2YOnanFH4OI4iFzUqyh3XOL7nAGbSlSMZdzKMIyyTpNSbqZsWG9odwLeKvA==", "dev": true }, "node_modules/emittery": { @@ -2370,9 +2336,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -2640,17 +2606,16 @@ } }, "node_modules/expect": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.2.tgz", - "integrity": "sha512-iAErsLxJ8C+S02QbLAwgSGSezLQK+XXRDt8IuFXFpwCNw2ECmzZSmjKcCaFVp5VRMk+WAvz6h6jokzEzBFZEuA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, "dependencies": { - "@jest/expect-utils": "^29.6.2", - "@types/node": "*", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.6.2", - "jest-message-util": "^29.6.2", - "jest-util": "^29.6.2" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -2792,9 +2757,9 @@ "dev": true }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, "optional": true, @@ -2806,10 +2771,13 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/gensync": { "version": "1.0.0-beta.2", @@ -2931,18 +2899,6 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2952,6 +2908,18 @@ "node": ">=8" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -3097,12 +3065,12 @@ "dev": true }, "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dev": true, "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3247,9 +3215,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, "dependencies": { "html-escaper": "^2.0.0", @@ -3260,15 +3228,15 @@ } }, "node_modules/jest": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.2.tgz", - "integrity": "sha512-8eQg2mqFbaP7CwfsTpCxQ+sHzw1WuNWL5UUvjnWP4hx2riGz9fPSzYOaU5q8/GqWn1TfgZIVTqYJygbGbWAANg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "dependencies": { - "@jest/core": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", "import-local": "^3.0.2", - "jest-cli": "^29.6.2" + "jest-cli": "^29.7.0" }, "bin": { "jest": "bin/jest.js" @@ -3286,12 +3254,13 @@ } }, "node_modules/jest-changed-files": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", - "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, "dependencies": { "execa": "^5.0.0", + "jest-util": "^29.7.0", "p-limit": "^3.1.0" }, "engines": { @@ -3299,28 +3268,28 @@ } }, "node_modules/jest-circus": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.2.tgz", - "integrity": "sha512-G9mN+KOYIUe2sB9kpJkO9Bk18J4dTDArNFPwoZ7WKHKel55eKIS/u2bLthxgojwlf9NLCVQfgzM/WsOVvoC6Fw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.2", - "@jest/expect": "^29.6.2", - "@jest/test-result": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", - "jest-each": "^29.6.2", - "jest-matcher-utils": "^29.6.2", - "jest-message-util": "^29.6.2", - "jest-runtime": "^29.6.2", - "jest-snapshot": "^29.6.2", - "jest-util": "^29.6.2", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "p-limit": "^3.1.0", - "pretty-format": "^29.6.2", + "pretty-format": "^29.7.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" @@ -3330,22 +3299,21 @@ } }, "node_modules/jest-cli": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.2.tgz", - "integrity": "sha512-TT6O247v6dCEX2UGHGyflMpxhnrL0DNqP2fRTKYm3nJJpCTfXX3GCMQPGFjXDoj0i5/Blp3jriKXFgdfmbYB6Q==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, "dependencies": { - "@jest/core": "^29.6.2", - "@jest/test-result": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", + "create-jest": "^29.7.0", "exit": "^0.1.2", - "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^29.6.2", - "jest-util": "^29.6.2", - "jest-validate": "^29.6.2", - "prompts": "^2.0.1", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "yargs": "^17.3.1" }, "bin": { @@ -3364,31 +3332,31 @@ } }, "node_modules/jest-config": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.2.tgz", - "integrity": "sha512-VxwFOC8gkiJbuodG9CPtMRjBUNZEHxwfQXmIudSTzFWxaci3Qub1ddTRbFNQlD/zUeaifLndh/eDccFX4wCMQw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.6.2", - "@jest/types": "^29.6.1", - "babel-jest": "^29.6.2", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^29.6.2", - "jest-environment-node": "^29.6.2", - "jest-get-type": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.6.2", - "jest-runner": "^29.6.2", - "jest-util": "^29.6.2", - "jest-validate": "^29.6.2", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^29.6.2", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -3409,24 +3377,24 @@ } }, "node_modules/jest-diff": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.2.tgz", - "integrity": "sha512-t+ST7CB9GX5F2xKwhwCf0TAR17uNDiaPTZnVymP9lw0lssa9vG+AFyDZoeIHStU3WowFFwT+ky+er0WVl2yGhA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.6.2" + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-docblock": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", - "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, "dependencies": { "detect-newline": "^3.0.0" @@ -3436,62 +3404,62 @@ } }, "node_modules/jest-each": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.2.tgz", - "integrity": "sha512-MsrsqA0Ia99cIpABBc3izS1ZYoYfhIy0NNWqPSE0YXbQjwchyt6B1HD2khzyPe1WiJA7hbxXy77ZoUQxn8UlSw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, "dependencies": { - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", - "jest-util": "^29.6.2", - "pretty-format": "^29.6.2" + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-environment-node": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.2.tgz", - "integrity": "sha512-YGdFeZ3T9a+/612c5mTQIllvWkddPbYcN2v95ZH24oWMbGA4GGS2XdIF92QMhUhvrjjuQWYgUGW2zawOyH63MQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.2", - "@jest/fake-timers": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.6.2", - "jest-util": "^29.6.2" + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-get-type": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", - "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-haste-map": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.2.tgz", - "integrity": "sha512-+51XleTDAAysvU8rT6AnS1ZJ+WHVNqhj1k6nTvN2PYP+HjU3kqlaKQ1Lnw3NYW3bm2r8vq82X0Z1nDDHZMzHVA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, "dependencies": { - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.6.2", - "jest-worker": "^29.6.2", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, @@ -3503,46 +3471,46 @@ } }, "node_modules/jest-leak-detector": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.2.tgz", - "integrity": "sha512-aNqYhfp5uYEO3tdWMb2bfWv6f0b4I0LOxVRpnRLAeque2uqOVVMLh6khnTcE2qJ5wAKop0HcreM1btoysD6bPQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, "dependencies": { - "jest-get-type": "^29.4.3", - "pretty-format": "^29.6.2" + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.2.tgz", - "integrity": "sha512-4LiAk3hSSobtomeIAzFTe+N8kL6z0JtF3n6I4fg29iIW7tt99R7ZcIFW34QkX+DuVrf+CUe6wuVOpm7ZKFJzZQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^29.6.2", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.6.2" + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.2.tgz", - "integrity": "sha512-vnIGYEjoPSuRqV8W9t+Wow95SDp6KPX2Uf7EoeG9G99J2OVh7OSwpS4B6J0NfpEIpfkBNHlBZpA2rblEuEFhZQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.6.2", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -3551,14 +3519,14 @@ } }, "node_modules/jest-mock": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.2.tgz", - "integrity": "sha512-hoSv3lb3byzdKfwqCuT6uTscan471GUECqgNYykg6ob0yiAw3zYc7OrPnI9Qv8Wwoa4lC7AZ9hyS4AiIx5U2zg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, "dependencies": { - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-util": "^29.6.2" + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -3582,26 +3550,26 @@ } }, "node_modules/jest-regex-util": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-resolve": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.2.tgz", - "integrity": "sha512-G/iQUvZWI5e3SMFssc4ug4dH0aZiZpsDq9o1PtXTV1210Ztyb2+w+ZgQkB3iOiC5SmAEzJBOHWz6Hvrd+QnNPw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.2", + "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.6.2", - "jest-validate": "^29.6.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" @@ -3611,43 +3579,43 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.2.tgz", - "integrity": "sha512-LGqjDWxg2fuQQm7ypDxduLu/m4+4Lb4gczc13v51VMZbVP5tSBILqVx8qfWcsdP8f0G7aIqByIALDB0R93yL+w==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, "dependencies": { - "jest-regex-util": "^29.4.3", - "jest-snapshot": "^29.6.2" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-runner": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.2.tgz", - "integrity": "sha512-wXOT/a0EspYgfMiYHxwGLPCZfC0c38MivAlb2lMEAlwHINKemrttu1uSbcGbfDV31sFaPWnWJPmb2qXM8pqZ4w==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, "dependencies": { - "@jest/console": "^29.6.2", - "@jest/environment": "^29.6.2", - "@jest/test-result": "^29.6.2", - "@jest/transform": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", - "jest-docblock": "^29.4.3", - "jest-environment-node": "^29.6.2", - "jest-haste-map": "^29.6.2", - "jest-leak-detector": "^29.6.2", - "jest-message-util": "^29.6.2", - "jest-resolve": "^29.6.2", - "jest-runtime": "^29.6.2", - "jest-util": "^29.6.2", - "jest-watcher": "^29.6.2", - "jest-worker": "^29.6.2", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -3656,31 +3624,31 @@ } }, "node_modules/jest-runtime": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.2.tgz", - "integrity": "sha512-2X9dqK768KufGJyIeLmIzToDmsN0m7Iek8QNxRSI/2+iPFYHF0jTwlO3ftn7gdKd98G/VQw9XJCk77rbTGZnJg==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.6.2", - "@jest/fake-timers": "^29.6.2", - "@jest/globals": "^29.6.2", - "@jest/source-map": "^29.6.0", - "@jest/test-result": "^29.6.2", - "@jest/transform": "^29.6.2", - "@jest/types": "^29.6.1", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.2", - "jest-message-util": "^29.6.2", - "jest-mock": "^29.6.2", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.6.2", - "jest-snapshot": "^29.6.2", - "jest-util": "^29.6.2", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -3689,9 +3657,9 @@ } }, "node_modules/jest-snapshot": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.2.tgz", - "integrity": "sha512-1OdjqvqmRdGNvWXr/YZHuyhh5DeaLp1p/F8Tht/MrMw4Kr1Uu/j4lRG+iKl1DAqUJDWxtQBMk41Lnf/JETYBRA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", @@ -3699,20 +3667,20 @@ "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.6.2", - "@jest/transform": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^29.6.2", + "expect": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-diff": "^29.6.2", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.6.2", - "jest-message-util": "^29.6.2", - "jest-util": "^29.6.2", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "natural-compare": "^1.4.0", - "pretty-format": "^29.6.2", + "pretty-format": "^29.7.0", "semver": "^7.5.3" }, "engines": { @@ -3720,12 +3688,12 @@ } }, "node_modules/jest-util": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.2.tgz", - "integrity": "sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "dependencies": { - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -3737,17 +3705,17 @@ } }, "node_modules/jest-validate": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.2.tgz", - "integrity": "sha512-vGz0yMN5fUFRRbpJDPwxMpgSXW1LDKROHfBopAvDcmD6s+B/s8WJrwi+4bfH4SdInBA5C3P3BI19dBtKzx1Arg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, "dependencies": { - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", + "jest-get-type": "^29.6.3", "leven": "^3.1.0", - "pretty-format": "^29.6.2" + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -3766,18 +3734,18 @@ } }, "node_modules/jest-watcher": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.2.tgz", - "integrity": "sha512-GZitlqkMkhkefjfN/p3SJjrDaxPflqxEAv3/ik10OirZqJGYH5rPiIsgVcfof0Tdqg3shQGdEIxDBx+B4tuLzA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, "dependencies": { - "@jest/test-result": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", - "jest-util": "^29.6.2", + "jest-util": "^29.7.0", "string-length": "^4.0.1" }, "engines": { @@ -3785,13 +3753,13 @@ } }, "node_modules/jest-worker": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.2.tgz", - "integrity": "sha512-l3ccBOabTdkng8I/ORCkADz4eSMKejTYv1vB/Z83UiubqhC1oQ5Li6dWCyqOIvSifGjUBxuvxvlm6KGK2DtuAQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, "dependencies": { "@types/node": "*", - "jest-util": "^29.6.2", + "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -4077,9 +4045,9 @@ "dev": true }, "node_modules/nock": { - "version": "14.0.0-beta.5", - "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.0-beta.5.tgz", - "integrity": "sha512-u255tf4DYvyErTlPZA9uTfXghiZZy+NflUOFONPVKZ5tP0yaHwKig28zyFOLhu8y5YcCRC+V5vDk4HHileh2iw==", + "version": "14.0.0-beta.6", + "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.0-beta.6.tgz", + "integrity": "sha512-b7lc7qvj1dQzxtbU7TqyTMnKbNKwGQd585xsRtcCZOv3I/yOK9Vwv4nOgnLFxFtX9m1yjhQDRbgqFCqNh9HuEw==", "dev": true, "dependencies": { "json-stringify-safe": "^5.0.1", @@ -4116,9 +4084,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, "node_modules/normalize-path": { @@ -4478,12 +4446,12 @@ } }, "node_modules/pretty-format": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.2.tgz", - "integrity": "sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "dependencies": { - "@jest/schemas": "^29.6.0", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -4565,9 +4533,9 @@ } }, "node_modules/pure-rand": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", - "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", "dev": true, "funding": [ { @@ -4632,12 +4600,12 @@ } }, "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -5159,9 +5127,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.14.tgz", + "integrity": "sha512-JixKH8GR2pWYshIPUg/NujK3JO7JiqEEUiNArE86NQyrgUuZeTlZQN3xuS/yiV5Kb48ev9K6RqNkaJjXsdg7Jw==", "dev": true, "funding": [ { @@ -5171,14 +5139,18 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "escalade": "^3.1.1", + "escalade": "^3.1.2", "picocolors": "^1.0.0" }, "bin": { - "browserslist-lint": "cli.js" + "update-browserslist-db": "cli.js" }, "peerDependencies": { "browserslist": ">= 4.21.0" @@ -5195,25 +5167,19 @@ } }, "node_modules/v8-to-istanbul": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" + "convert-source-map": "^2.0.0" }, "engines": { "node": ">=10.12.0" } }, - "node_modules/v8-to-istanbul/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", diff --git a/package.json b/package.json index 1d838dff..b61d4b94 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,8 @@ "@types/jest": "^29.5.3", "@typescript-eslint/eslint-plugin": "^5.56.0", "cross-fetch": "^3.1.5", - "jest": "^29.6.2", - "nock": "^14.0.0-beta.5", + "jest": "^29.7.0", + "nock": "^14.0.0-beta.6", "publint": "^0.2.7", "ts-jest": "^29.1.0", "typescript": "^5.0.2" From c1fd92018d26e266640190166258b9fe8a6ee324 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Thu, 2 May 2024 06:03:57 -0700 Subject: [PATCH 125/207] 0.29.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 776133d6..07d2bda8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.29.1", + "version": "0.29.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.29.1", + "version": "0.29.2", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index b61d4b94..d4cf9b2f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.29.1", + "version": "0.29.2", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 082a0f85110b919c19f2dba2f56dd38b64137347 Mon Sep 17 00:00:00 2001 From: Mattt Date: Thu, 2 May 2024 14:27:41 -0700 Subject: [PATCH 126/207] Remove use of optional chaining in client (#253) --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 79b8f4dc..f4c0e2cf 100644 --- a/index.js +++ b/index.js @@ -163,7 +163,7 @@ class Replicate { } // We handle the cancel later in the function. - if (signal?.aborted) { + if (signal && signal.aborted) { return true; // stop polling } @@ -171,7 +171,7 @@ class Replicate { } ); - if (signal?.aborted) { + if (signal && signal.aborted) { prediction = await this.predictions.cancel(prediction.id); } From a8c4927ad143b5cca36c58c0af39ddd32911f384 Mon Sep 17 00:00:00 2001 From: Mattt Date: Thu, 2 May 2024 14:27:51 -0700 Subject: [PATCH 127/207] Add Node 22 to CI test matrix (#252) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2144cbf3..ed2cab8e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases - node-version: [18.x, 20.x] + node-version: [18.x, 20.x, 22.x] steps: - uses: actions/checkout@v4 From 587d44545a2494811b2abbfb5309702c48065da5 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Thu, 2 May 2024 22:32:56 +0100 Subject: [PATCH 128/207] Remove the node protocol from the require statement (#250) * Remove the node protocol from the require statement This should improve the situation for certain JavaScript bundlers in the ecosystem which do not like the `node:crypto` require statement. * Update browser tests to mark "crypto" module as external * Make indirect request to `require("node:crypto")` * Add nextjs integration test --- .github/workflows/ci.yml | 22 ++++++++++++++++++++++ integration/next/.npmrc | 3 +++ integration/next/middleware.ts | 17 +++++++++++++++++ integration/next/package.json | 14 ++++++++++++++ integration/next/pages/index.js | 5 +++++ lib/util.js | 13 ++++++++++++- 6 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 integration/next/.npmrc create mode 100644 integration/next/middleware.ts create mode 100644 integration/next/package.json create mode 100644 integration/next/pages/index.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed2cab8e..e0a90e49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -171,3 +171,25 @@ jobs: for ((i=0; i ( +
+

Welcome to Next.js

+
+) diff --git a/lib/util.js b/lib/util.js index 22a14c89..68b1d9de 100644 --- a/lib/util.js +++ b/lib/util.js @@ -90,7 +90,18 @@ async function createHMACSHA256(secret, data) { // In Node 18 the `crypto` global is behind a --no-experimental-global-webcrypto flag if (typeof crypto === "undefined" && typeof require === "function") { - crypto = require("node:crypto").webcrypto; + // NOTE: Webpack (primarily as it's used by Next.js) and perhaps some + // other bundlers do not currently support the `node` protocol and will + // error if it's found in the source. Other platforms like CloudFlare + // will only support requires when using the node protocol. + // + // As this line is purely to support Node 18.x we make an indirect request + // to the require function which fools Webpack... + // + // We may be able to remove this in future as it looks like Webpack is getting + // support for requiring using the `node:` protocol. + // See: https://github.com/webpack/webpack/issues/18277 + crypto = require.call(null, "node:crypto").webcrypto; } const key = await crypto.subtle.importKey( From bb5ddaf583b0a1f4cac776cbbcb71c86e7e33e16 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Thu, 2 May 2024 14:33:58 -0700 Subject: [PATCH 129/207] 0.29.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 07d2bda8..0a2d156c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.29.2", + "version": "0.29.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.29.2", + "version": "0.29.3", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index d4cf9b2f..79082df1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.29.2", + "version": "0.29.3", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 469f8da0c1311f82e66d39725349cfcd9e2d6a60 Mon Sep 17 00:00:00 2001 From: Mattt Date: Fri, 3 May 2024 13:01:50 -0700 Subject: [PATCH 130/207] Prevent signal parameter from being passed alongside input when calling `replicate.stream` (#254) * Prevent signal parameter from being passed alongside input when calling replicate.stream * Add test coverage for passing signal to replicate.stream --- index.js | 5 ++--- integration/cloudflare-worker/index.js | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index f4c0e2cf..dd60ceca 100644 --- a/index.js +++ b/index.js @@ -274,7 +274,7 @@ class Replicate { * @yields {ServerSentEvent} Each streamed event from the prediction */ async *stream(ref, options) { - const { wait, ...data } = options; + const { wait, signal, ...data } = options; const identifier = ModelVersionIdentifier.parse(ref); @@ -296,11 +296,10 @@ class Replicate { } if (prediction.urls && prediction.urls.stream) { - const { signal } = options; const stream = createReadableStream({ url: prediction.urls.stream, fetch: this.fetch, - options: { signal }, + ...(signal ? { options: { signal } } : {}), }); yield* streamAsyncIterator(stream); diff --git a/integration/cloudflare-worker/index.js b/integration/cloudflare-worker/index.js index be18d53c..32ec9fc8 100644 --- a/integration/cloudflare-worker/index.js +++ b/integration/cloudflare-worker/index.js @@ -5,12 +5,14 @@ export default { const replicate = new Replicate({ auth: env.REPLICATE_API_TOKEN }); try { + const controller = new AbortController(); const output = replicate.stream( "replicate/canary:30e22229542eb3f79d4f945dacb58d32001b02cc313ae6f54eef27904edf3272", { input: { text: "Colin CloudFlare", }, + signal: controller.signal, } ); const stream = new ReadableStream({ From 5ba8d17528a154fcdaf7ab38a28e4ac37c4282c6 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 3 May 2024 13:02:18 -0700 Subject: [PATCH 131/207] 0.29.4 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0a2d156c..955a42d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.29.3", + "version": "0.29.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.29.3", + "version": "0.29.4", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 79082df1..35504498 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.29.3", + "version": "0.29.4", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 70ca64a472fd729c7a8688050c2b4e97a3228bed Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Tue, 14 May 2024 16:20:59 -0700 Subject: [PATCH 132/207] docs: document `validateWebhook` (#259) * document validateWebhook * Update README.md --- README.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/README.md b/README.md index f6d798c8..e88bb6a1 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,49 @@ await replicate.predictions.create({ // => {"id": "xyz", "status": "successful", ... } ``` +## Verifying webhooks + +To prevent unauthorized requests, Replicate signs every webhook and its metadata with a unique key for each user or organization. You can use this signature to verify the webhook indeed comes from Replicate before you process it. + +This client includes a `validateWebhook` convenience function that you can use to validate webhooks. + +To validate webhooks: + +1. Check out the [webhooks guide](https://replicate.com/docs/webhooks) to get started. +1. [Retrieve your webhook signing secret](https://replicate.com/docs/webhooks#retrieving-the-webhook-signing-key) and store it in your enviroment. +1. Update your webhook handler to call `validateWebhook(request, secret)`, where `request` is an instance of a [web-standard `Request` object](https://developer.mozilla.org/en-US/docs/Web/API/object, and `secret` is the signing secret for your environment. + +Here's an example of how to validate webhooks using Next.js: + +```js +import { NextResponse } from 'next/server'; +import { validateWebhook } from 'replicate'; + +export async function POST(request) { + const secret = process.env.REPLICATE_WEBHOOK_SIGNING_SECRET; + + if (!secret) { + console.log("Skipping webhook validation. To validate webhooks, set REPLICATE_WEBHOOK_SIGNING_SECRET") + const body = await request.json(); + console.log(body); + return NextResponse.json({ detail: "Webhook received (but not validated)" }, { status: 200 }); + } + + const webhookIsValid = await validateWebhook(request.clone(), secret); + + if (!webhookIsValid) { + return NextResponse.json({ detail: "Webhook is invalid" }, { status: 401 }); + } + + // process validated webhook here... + console.log("Webhook is valid!"); + const body = await request.json(); + console.log(body); + + return NextResponse.json({ detail: "Webhook is valid" }, { status: 200 }); +} +``` + ## TypeScript Currently in order to support the module format used by `replicate` you'll need to set `esModuleInterop` to `true` in your tsconfig.json. From cfafee4fb5e0ce59fe348810ffb2f7cf7a6e6b1a Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Tue, 14 May 2024 16:11:44 -0700 Subject: [PATCH 133/207] document node 18 requirement --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e88bb6a1..270c6d2e 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ and everything else you can do with ## Installation +This library requires Node.js >= 18. + Install it from npm: ```bash @@ -20,7 +22,7 @@ npm install replicate ## Usage -Create the client: +Import or require the package: ```js // CommonJS (default or using .cjs extension) @@ -30,9 +32,11 @@ const Replicate = require("replicate"); import Replicate from "replicate"; ``` -``` +Instantiate the client: + +```js const replicate = new Replicate({ - // get your token from https://replicate.com/account + // get your token from https://replicate.com/account/api-tokens auth: "my api token", // defaults to process.env.REPLICATE_API_TOKEN }); ``` From 87bd3ab3e9df312a193a18787c35401956035bdd Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Tue, 21 May 2024 17:47:53 -0700 Subject: [PATCH 134/207] add PR template (#263) --- .github/pull_request_template.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..58baaeca --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,4 @@ + + +- [ ] Tests +- [ ] Docs \ No newline at end of file From cc3d281afb39834822b2be5911b147c2b8a269e0 Mon Sep 17 00:00:00 2001 From: Mattt Date: Wed, 22 May 2024 14:04:30 -0700 Subject: [PATCH 135/207] Add support for files API endpoints (#184) * Add support for files API endpoints * Apply suggestions from code review Co-authored-by: Aron Carroll * Remove prepareInputs in favor of FileEncodingStrategy This allows the user to determine if file uploads, falling back to base64 should be used or just sticking to one approach. The tests have been updated to validate the file upload payload and to ensure the url is correctly passed to the prediction create method. * Remove replicate.files API * Tell Biome to ignore .wrangler directory --------- Co-authored-by: Aron Carroll --- biome.json | 6 +++- index.d.ts | 19 ++++++++++ index.js | 11 +++++- index.test.ts | 62 +++++++++++++++++++++++++++++++-- lib/deployments.js | 6 +++- lib/files.js | 86 ++++++++++++++++++++++++++++++++++++++++++++++ lib/predictions.js | 12 +++++-- lib/util.js | 55 ++++++++++++++++++++++++++--- 8 files changed, 244 insertions(+), 13 deletions(-) create mode 100644 lib/files.js diff --git a/biome.json b/biome.json index ecb665f5..094cf0ec 100644 --- a/biome.json +++ b/biome.json @@ -1,7 +1,11 @@ { "$schema": "https://biomejs.dev/schemas/1.0.0/schema.json", "files": { - "ignore": [".wrangler", "vendor/*"] + "ignore": [ + ".wrangler", + "node_modules", + "vendor/*" + ] }, "formatter": { "indentStyle": "space", diff --git a/index.d.ts b/index.d.ts index 31a23251..1ef9e89c 100644 --- a/index.d.ts +++ b/index.d.ts @@ -39,6 +39,21 @@ declare module "replicate" { }; } + export interface FileObject { + id: string; + name: string; + content_type: string; + size: number; + etag: string; + checksum: string; + metadata: Record; + created_at: string; + expires_at: string | null; + urls: { + get: string; + }; + } + export interface Hardware { sku: string; name: string; @@ -93,6 +108,8 @@ declare module "replicate" { export type Training = Prediction; + export type FileEncodingStrategy = "default" | "upload" | "data-uri"; + export interface Page { previous?: string; next?: string; @@ -119,12 +136,14 @@ declare module "replicate" { input: Request | string, init?: RequestInit ) => Promise; + fileEncodingStrategy?: FileEncodingStrategy; }); auth: string; userAgent?: string; baseUrl?: string; fetch: (input: Request | string, init?: RequestInit) => Promise; + fileEncodingStrategy: FileEncodingStrategy; run( identifier: `${string}/${string}` | `${string}/${string}:${string}`, diff --git a/index.js b/index.js index dd60ceca..70cbe862 100644 --- a/index.js +++ b/index.js @@ -46,6 +46,7 @@ class Replicate { * @param {string} options.userAgent - Identifier of your app * @param {string} [options.baseUrl] - Defaults to https://api.replicate.com/v1 * @param {Function} [options.fetch] - Fetch function to use. Defaults to `globalThis.fetch` + * @param {"default" | "upload" | "data-uri"} [options.fileEncodingStrategy] - Determines the file encoding strategy to use */ constructor(options = {}) { this.auth = @@ -55,6 +56,7 @@ class Replicate { options.userAgent || `replicate-javascript/${packageJSON.version}`; this.baseUrl = options.baseUrl || "https://api.replicate.com/v1"; this.fetch = options.fetch || globalThis.fetch; + this.fileEncodingStrategy = options.fileEncodingStrategy ?? "default"; this.accounts = { current: accounts.current.bind(this), @@ -230,10 +232,17 @@ class Replicate { } } + let body = undefined; + if (data instanceof FormData) { + body = data; + } else if (data) { + body = JSON.stringify(data); + } + const init = { method, headers, - body: data ? JSON.stringify(data) : undefined, + body, }; const shouldRetry = diff --git a/index.test.ts b/index.test.ts index 53737e05..7502969d 100644 --- a/index.test.ts +++ b/index.test.ts @@ -222,13 +222,13 @@ describe("Replicate client", () => { expect(prediction.id).toBe("ufawqhfynnddngldkgtslldrkq"); }); - test.each([ + const fileTestCases = [ // Skip test case if File type is not available ...(typeof File !== "undefined" ? [ { type: "file", - value: new File(["hello world"], "hello.txt", { + value: new File(["hello world"], "file_hello.txt", { type: "text/plain", }), expected: "data:text/plain;base64,aGVsbG8gd29ybGQ=", @@ -245,11 +245,67 @@ describe("Replicate client", () => { value: Buffer.from("hello world"), expected: "data:application/octet-stream;base64,aGVsbG8gd29ybGQ=", }, - ])( + ]; + + test.each(fileTestCases)( + "converts a $type input into a Replicate file URL", + async ({ value: data, type }) => { + const mockedFetch = jest.spyOn(client, "fetch"); + + nock(BASE_URL) + .post("/files") + .matchHeader("Content-Type", "multipart/form-data") + .reply(201, { + urls: { + get: "https://replicate.com/api/files/123", + }, + }) + .post( + "/predictions", + (body) => body.input.data === "https://replicate.com/api/files/123" + ) + .reply(201, (_uri: string, body: Record) => { + return body; + }); + + const prediction = await client.predictions.create({ + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + input: { + prompt: "Tell me a story", + data, + }, + stream: true, + }); + + expect(client.fetch).toHaveBeenCalledWith( + new URL("https://api.replicate.com/v1/files"), + { + method: "POST", + body: expect.any(FormData), + headers: expect.objectContaining({ + "Content-Type": "multipart/form-data", + }), + } + ); + const form = mockedFetch.mock.calls[0][1]?.body as FormData; + // @ts-ignore + expect(form?.get("content")?.name).toMatch(new RegExp(`^${type}_`)); + + expect(prediction.input).toEqual({ + prompt: "Tell me a story", + data: "https://replicate.com/api/files/123", + }); + } + ); + + test.each(fileTestCases)( "converts a $type input into a base64 encoded string", async ({ value: data, expected }) => { let actual: Record | undefined; nock(BASE_URL) + .post("/files") + .reply(503, "Service Unavailable") .post("/predictions") .reply(201, (_uri: string, body: Record) => { actual = body; diff --git a/lib/deployments.js b/lib/deployments.js index 4f6f3c6b..27a2f6a2 100644 --- a/lib/deployments.js +++ b/lib/deployments.js @@ -30,7 +30,11 @@ async function createPrediction(deployment_owner, deployment_name, options) { method: "POST", data: { ...data, - input: await transformFileInputs(input), + input: await transformFileInputs( + this, + input, + this.fileEncodingStrategy + ), stream, }, } diff --git a/lib/files.js b/lib/files.js new file mode 100644 index 00000000..f6620e92 --- /dev/null +++ b/lib/files.js @@ -0,0 +1,86 @@ +/** + * Create a file + * + * @param {object} file - Required. The file object. + * @param {object} metadata - Optional. User-provided metadata associated with the file. + * @returns {Promise} - Resolves with the file data + */ +async function createFile(file, metadata = {}) { + const form = new FormData(); + + let filename; + let blob; + if (file instanceof Blob) { + filename = file.name || `blob_${Date.now()}`; + blob = file; + } else if (Buffer.isBuffer(file)) { + filename = `buffer_${Date.now()}`; + blob = new Blob(file, { type: "application/octet-stream" }); + } else { + throw new Error("Invalid file argument, must be a Blob, File or Buffer"); + } + + form.append("content", blob, filename); + form.append( + "metadata", + new Blob([JSON.stringify(metadata)], { type: "application/json" }) + ); + + const response = await this.request("/files", { + method: "POST", + data: form, + headers: { + "Content-Type": "multipart/form-data", + }, + }); + + return response.json(); +} + +/** + * List all files + * + * @returns {Promise} - Resolves with the files data + */ +async function listFiles() { + const response = await this.request("/files", { + method: "GET", + }); + + return response.json(); +} + +/** + * Get a file + * + * @param {string} file_id - Required. The ID of the file. + * @returns {Promise} - Resolves with the file data + */ +async function getFile(file_id) { + const response = await this.request(`/files/${file_id}`, { + method: "GET", + }); + + return response.json(); +} + +/** + * Delete a file + * + * @param {string} file_id - Required. The ID of the file. + * @returns {Promise} - Resolves with the deletion confirmation + */ +async function deleteFile(file_id) { + const response = await this.request(`/files/${file_id}`, { + method: "DELETE", + }); + + return response.json(); +} + +module.exports = { + create: createFile, + list: listFiles, + get: getFile, + delete: deleteFile, +}; diff --git a/lib/predictions.js b/lib/predictions.js index 5b0370e5..c290d40f 100644 --- a/lib/predictions.js +++ b/lib/predictions.js @@ -30,7 +30,11 @@ async function createPrediction(options) { method: "POST", data: { ...data, - input: await transformFileInputs(input), + input: await transformFileInputs( + this, + input, + this.fileEncodingStrategy + ), version, stream, }, @@ -40,7 +44,11 @@ async function createPrediction(options) { method: "POST", data: { ...data, - input: await transformFileInputs(input), + input: await transformFileInputs( + this, + input, + this.fileEncodingStrategy + ), stream, }, }); diff --git a/lib/util.js b/lib/util.js index 68b1d9de..e164899e 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,4 +1,5 @@ const ApiError = require("./error"); +const { create: createFile } = require("./files"); /** * @see {@link validateWebhook} @@ -209,12 +210,58 @@ async function withAutomaticRetries(request, options = {}) { } attempts += 1; } - /* eslint-enable no-await-in-loop */ } while (attempts < maxRetries); return request(); } +/** + * Walks the inputs and, for any File or Blob, tries to upload it to Replicate + * and replaces the input with the URL of the uploaded file. + * + * @param {Replicate} client - The client used to upload the file + * @param {object} inputs - The inputs to transform + * @param {"default" | "upload" | "data-uri"} strategy - Whether to upload files to Replicate, encode as dataURIs or try both. + * @returns {object} - The transformed inputs + * @throws {ApiError} If the request to upload the file fails + */ +async function transformFileInputs(client, inputs, strategy) { + switch (strategy) { + case "data-uri": + return await transformFileInputsToBase64EncodedDataURIs(client, inputs); + case "upload": + return await transformFileInputsToReplicateFileURLs(client, inputs); + case "default": + try { + return await transformFileInputsToReplicateFileURLs(client, inputs); + } catch (error) { + return await transformFileInputsToBase64EncodedDataURIs(inputs); + } + default: + throw new Error(`Unexpected file upload strategy: ${strategy}`); + } +} + +/** + * Walks the inputs and, for any File or Blob, tries to upload it to Replicate + * and replaces the input with the URL of the uploaded file. + * + * @param {Replicate} client - The client used to upload the file + * @param {object} inputs - The inputs to transform + * @returns {object} - The transformed inputs + * @throws {ApiError} If the request to upload the file fails + */ +async function transformFileInputsToReplicateFileURLs(client, inputs) { + return await transform(inputs, async (value) => { + if (value instanceof Blob || value instanceof Buffer) { + const file = await createFile.call(client, value); + return file.urls.get; + } + + return value; + }); +} + const MAX_DATA_URI_SIZE = 10_000_000; /** @@ -225,9 +272,9 @@ const MAX_DATA_URI_SIZE = 10_000_000; * @returns {object} - The transformed inputs * @throws {Error} If the size of inputs exceeds a given threshould set by MAX_DATA_URI_SIZE */ -async function transformFileInputs(inputs) { +async function transformFileInputsToBase64EncodedDataURIs(inputs) { let totalBytes = 0; - const result = await transform(inputs, async (value) => { + return await transform(inputs, async (value) => { let buffer; let mime; @@ -258,8 +305,6 @@ async function transformFileInputs(inputs) { return `data:${mime};base64,${data}`; }); - - return result; } // Walk a JavaScript object and transform the leaf values. From 3db8800b05912c14074ee68f2c69fb3ace982ba2 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Wed, 22 May 2024 14:08:23 -0700 Subject: [PATCH 136/207] 0.30.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 955a42d9..6303b3b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.29.4", + "version": "0.30.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.29.4", + "version": "0.30.0", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 35504498..23d7472c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.29.4", + "version": "0.30.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From e0592866a3e213fa7772a518d99fd63f11b77e85 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Tue, 28 May 2024 15:27:37 -0700 Subject: [PATCH 137/207] Update README with troubleshooting section (#264) * merge two typescript headings into one * move note about vendored deps to CONTRIBUTING docs * add a troubleshooting section to README first item: Next.js noStore --- CONTRIBUTING.md | 23 +++++++++++++++++++++++ README.md | 34 ++++++++++++---------------------- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ab1d859d..0b5e8942 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,3 +32,26 @@ This will: - Push the commit and tag to GitHub - Publish the package to npm - Create a GitHub release + +## Vendored Dependencies + +We have a few dependencies that have been bundled into the vendor directory rather than adding external npm dependencies. + +These have been generated using bundlejs.com and copied into the appropriate directory along with the license and repository information. + +* [eventsource-parser/stream](https://bundlejs.com/?bundle&q=eventsource-parser%40latest%2Fstream&config=%7B%22esbuild%22%3A%7B%22format%22%3A%22cjs%22%2C%22minify%22%3Afalse%2C%22platform%22%3A%22neutral%22%7D%7D) +* [streams-text-encoding/text-decoder-stream](https://bundlejs.com/?q=%40stardazed%2Fstreams-text-encoding&treeshake=%5B%7B+TextDecoderStream+%7D%5D&config=%7B%22esbuild%22%3A%7B%22format%22%3A%22cjs%22%2C%22minify%22%3Afalse%7D%7D) + +> [!NOTE] +> The vendored implementation of `TextDecoderStream` requires +> the following patch to be applied to the output of bundlejs.com: +> +> ```diff +> constructor(label, options) { +> - this[decDecoder] = new TextDecoder(label, options); +> - this[decTransform] = new TransformStream(new TextDecodeTransformer(this[decDecoder])); +> + const decoder = new TextDecoder(label || "utf-8", options || {}); +> + this[decDecoder] = decoder; +> + this[decTransform] = new TransformStream(new TextDecodeTransformer(decoder)); +> } +> ``` diff --git a/README.md b/README.md index 270c6d2e..29c31c6f 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,8 @@ export async function POST(request) { ## TypeScript +The `Replicate` constructor and all `replicate.*` methods are fully typed. + Currently in order to support the module format used by `replicate` you'll need to set `esModuleInterop` to `true` in your tsconfig.json. ## API @@ -1020,29 +1022,17 @@ The `replicate.request()` method is used by the other methods to interact with the Replicate API. You can call this method directly to make other requests to the API. -## TypeScript - -The `Replicate` constructor and all `replicate.*` methods are fully typed. +## Troubleshooting -## Vendored Dependencies +### Predictions hanging in Next.js -We have a few dependencies that have been bundled into the vendor directory rather than adding external npm dependencies. +Next.js App Router adds some extensions to `fetch` to make it cache responses. To disable this behavior, set the `cache` option to `"no-store"` on the Replicate client's fetch object: -These have been generated using bundlejs.com and copied into the appropriate directory along with the license and repository information. - -* [eventsource-parser/stream](https://bundlejs.com/?bundle&q=eventsource-parser%40latest%2Fstream&config=%7B%22esbuild%22%3A%7B%22format%22%3A%22cjs%22%2C%22minify%22%3Afalse%2C%22platform%22%3A%22neutral%22%7D%7D) -* [streams-text-encoding/text-decoder-stream](https://bundlejs.com/?q=%40stardazed%2Fstreams-text-encoding&treeshake=%5B%7B+TextDecoderStream+%7D%5D&config=%7B%22esbuild%22%3A%7B%22format%22%3A%22cjs%22%2C%22minify%22%3Afalse%7D%7D) +```js +replicate = new Replicate({/*...*/}) +replicate.fetch = (url, options) => { + return fetch(url, { ...options, cache: "no-store" }); +}; +``` -> [!NOTE] -> The vendored implementation of `TextDecoderStream` requires -> the following patch to be applied to the output of bundlejs.com: -> -> ```diff -> constructor(label, options) { -> - this[decDecoder] = new TextDecoder(label, options); -> - this[decTransform] = new TransformStream(new TextDecodeTransformer(this[decDecoder])); -> + const decoder = new TextDecoder(label || "utf-8", options || {}); -> + this[decDecoder] = decoder; -> + this[decTransform] = new TransformStream(new TextDecodeTransformer(decoder)); -> } -> ``` +Alternatively you can use Next.js [`noStore`](https://github.com/replicate/replicate-javascript/issues/136#issuecomment-1847442879) to opt out of caching for your component. From d981fc14fbb45748ecf032d735cb8e4f55b1aa15 Mon Sep 17 00:00:00 2001 From: Mattt Date: Wed, 29 May 2024 11:04:09 -0700 Subject: [PATCH 138/207] Fix regression in how array input values are transformed (#266) * Fix regression in how array input values are transformed * Refactor tests --- index.test.ts | 106 +++++++++++++++++++++++++++++++++++--------------- lib/util.js | 5 ++- 2 files changed, 77 insertions(+), 34 deletions(-) diff --git a/index.test.ts b/index.test.ts index 7502969d..834b786f 100644 --- a/index.test.ts +++ b/index.test.ts @@ -185,42 +185,84 @@ describe("Replicate client", () => { }); describe("predictions.create", () => { - test("Calls the correct API route with the correct payload", async () => { - nock(BASE_URL) - .post("/predictions") - .reply(200, { - id: "ufawqhfynnddngldkgtslldrkq", - model: "replicate/hello-world", - version: - "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", - urls: { - get: "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", - cancel: - "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel", - }, - created_at: "2022-04-26T22:13:06.224088Z", - started_at: null, - completed_at: null, - status: "starting", - input: { - text: "Alice", + const predictionTestCases = [ + { + description: "String input", + input: { + text: "Alice", + }, + }, + { + description: "Number input", + input: { + text: 123, + }, + }, + { + description: "Boolean input", + input: { + text: true, + }, + }, + { + description: "Array input", + input: { + text: ["Alice", "Bob", "Charlie"], + }, + }, + { + description: "Object input", + input: { + text: { + name: "Alice", }, - output: null, - error: null, - logs: null, - metrics: {}, - }); - const prediction = await client.predictions.create({ + }, + }, + ].map((testCase) => ({ + ...testCase, + expectedResponse: { + id: "ufawqhfynnddngldkgtslldrkq", + model: "replicate/hello-world", version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", - input: { - text: "Alice", + urls: { + get: "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", + cancel: + "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel", }, - webhook: "http://test.host/webhook", - webhook_events_filter: ["output", "completed"], - }); - expect(prediction.id).toBe("ufawqhfynnddngldkgtslldrkq"); - }); + input: testCase.input, + created_at: "2022-04-26T22:13:06.224088Z", + started_at: null, + completed_at: null, + status: "starting", + }, + })); + + test.each(predictionTestCases)( + "$description", + async ({ input, expectedResponse }) => { + nock(BASE_URL) + .post("/predictions", { + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + input: input as Record, + webhook: "http://test.host/webhook", + webhook_events_filter: ["output", "completed"], + }) + .reply(200, expectedResponse); + + const response = await client.predictions.create({ + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + input: input as Record, + webhook: "http://test.host/webhook", + webhook_events_filter: ["output", "completed"], + }); + + expect(response.input).toEqual(input); + expect(response.status).toBe(expectedResponse.status); + } + ); const fileTestCases = [ // Skip test case if File type is not available diff --git a/lib/util.js b/lib/util.js index e164899e..3745d9f0 100644 --- a/lib/util.js +++ b/lib/util.js @@ -310,9 +310,10 @@ async function transformFileInputsToBase64EncodedDataURIs(inputs) { // Walk a JavaScript object and transform the leaf values. async function transform(value, mapper) { if (Array.isArray(value)) { - let copy = []; + const copy = []; for (const val of value) { - copy = await transform(val, mapper); + const transformed = await transform(val, mapper); + copy.push(transformed); } return copy; } From 0d7a60b6e4e10528d2805761559fc487e0a718e2 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Wed, 29 May 2024 11:04:29 -0700 Subject: [PATCH 139/207] 0.30.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6303b3b7..f268ac61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.30.0", + "version": "0.30.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.30.0", + "version": "0.30.1", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 23d7472c..502ee210 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.30.0", + "version": "0.30.1", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 9b4bb64227633c5d8aaa864714229987694cea61 Mon Sep 17 00:00:00 2001 From: Mattt Date: Mon, 10 Jun 2024 03:55:14 -0700 Subject: [PATCH 140/207] Delete explicit `Content-Type` for `FormData` input (#268) * Delete explicit Content-Type for FormData input * Update tests --- index.js | 9 ++++++--- index.test.ts | 6 +----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index 70cbe862..bf53449a 100644 --- a/index.js +++ b/index.js @@ -220,12 +220,13 @@ class Replicate { url.searchParams.append(key, value); } - const headers = {}; + const headers = { + "Content-Type": "application/json", + "User-Agent": userAgent, + }; if (auth) { headers["Authorization"] = `Bearer ${auth}`; } - headers["Content-Type"] = "application/json"; - headers["User-Agent"] = userAgent; if (options.headers) { for (const [key, value] of Object.entries(options.headers)) { headers[key] = value; @@ -235,6 +236,8 @@ class Replicate { let body = undefined; if (data instanceof FormData) { body = data; + // biome-ignore lint/performance/noDelete: + delete headers["Content-Type"]; // Use automatic content type header } else if (data) { body = JSON.stringify(data); } diff --git a/index.test.ts b/index.test.ts index 834b786f..8ecb5ae2 100644 --- a/index.test.ts +++ b/index.test.ts @@ -296,7 +296,6 @@ describe("Replicate client", () => { nock(BASE_URL) .post("/files") - .matchHeader("Content-Type", "multipart/form-data") .reply(201, { urls: { get: "https://replicate.com/api/files/123", @@ -317,7 +316,6 @@ describe("Replicate client", () => { prompt: "Tell me a story", data, }, - stream: true, }); expect(client.fetch).toHaveBeenCalledWith( @@ -325,9 +323,7 @@ describe("Replicate client", () => { { method: "POST", body: expect.any(FormData), - headers: expect.objectContaining({ - "Content-Type": "multipart/form-data", - }), + headers: expect.any(Object), } ); const form = mockedFetch.mock.calls[0][1]?.body as FormData; From a345d0d721e654e180dfe898fc95e094d324d3f5 Mon Sep 17 00:00:00 2001 From: Mattt Date: Mon, 10 Jun 2024 04:05:17 -0700 Subject: [PATCH 141/207] Update blob creation to handle Buffer inputs correctly in createFile (#269) --- lib/files.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/files.js b/lib/files.js index f6620e92..520f2aa2 100644 --- a/lib/files.js +++ b/lib/files.js @@ -15,7 +15,11 @@ async function createFile(file, metadata = {}) { blob = file; } else if (Buffer.isBuffer(file)) { filename = `buffer_${Date.now()}`; - blob = new Blob(file, { type: "application/octet-stream" }); + const bytes = new Uint8Array(file); + blob = new Blob([bytes], { + type: "application/octet-stream", + name: filename, + }); } else { throw new Error("Invalid file argument, must be a Blob, File or Buffer"); } From 4689e2bed0e44b1a099cd82f0c392a355b6c2fae Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Mon, 10 Jun 2024 04:05:37 -0700 Subject: [PATCH 142/207] 0.30.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f268ac61..710a1e9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.30.1", + "version": "0.30.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.30.1", + "version": "0.30.2", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 502ee210..f31fb229 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.30.1", + "version": "0.30.2", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From fa38a54e08220ab1e4f835c318c9e142a4cd9439 Mon Sep 17 00:00:00 2001 From: Mattt Date: Tue, 11 Jun 2024 05:44:06 -0700 Subject: [PATCH 143/207] Revert "add PR template (#263)" (#271) This reverts commit 87bd3ab3e9df312a193a18787c35401956035bdd. --- .github/pull_request_template.md | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index 58baaeca..00000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,4 +0,0 @@ - - -- [ ] Tests -- [ ] Docs \ No newline at end of file From 9b5df12c0ad71efaeaacd40f1387f204e70cefb3 Mon Sep 17 00:00:00 2001 From: Mattt Date: Tue, 2 Jul 2024 09:32:31 -0700 Subject: [PATCH 144/207] Document file operations (#276) * Reword file input recommendations * Document file methods * Document fileEncodingStrategy client constructor option --- README.md | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 109 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 29c31c6f..797af77c 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,9 @@ console.log(prediction.output); // ['https://replicate.delivery/pbxt/RoaxeXqhL0xaYyLm6w3bpGwF5RaNBjADukfFnMbhOyeoWBdhA/out-0.png'] ``` -To run a model that takes a file input you can pass the data directly or pass a URL to a publicly accessible file. +To run a model that takes a file input you can pass either +a URL to a publicly accessible file on the Internet +or a handle to a file on your local device. ```js const fs = require("node:fs/promises"); @@ -93,6 +95,10 @@ const output = await replicate.run(model, { input }); // ['https://replicate.delivery/mgxm/e7b0e122-9daa-410e-8cde-006c7308ff4d/output.png'] ``` +> [!NOTE] +> File handle inputs are automatically uploaded to Replicate. +> See [`replicate.files.create`](#replicatefilescreate) for more information. + ### Webhooks Webhooks provide real-time updates about your prediction. Specify an endpoint when you create a prediction, and Replicate will send HTTP POST requests to that URL when the prediction is created, updated, and finished. @@ -179,7 +185,7 @@ export async function POST(request) { console.log(body); return NextResponse.json({ detail: "Webhook received (but not validated)" }, { status: 200 }); } - + const webhookIsValid = await validateWebhook(request.clone(), secret); if (!webhookIsValid) { @@ -209,12 +215,14 @@ Currently in order to support the module format used by `replicate` you'll need const replicate = new Replicate(options); ``` -| name | type | description | -| ------------------- | -------- | --------------------------------------------------------------------------------- | -| `options.auth` | string | **Required**. API access token | -| `options.userAgent` | string | Identifier of your app. Defaults to `replicate-javascript/${packageJSON.version}` | -| `options.baseUrl` | string | Defaults to https://api.replicate.com/v1 | -| `options.fetch` | function | Fetch function to use. Defaults to `globalThis.fetch` | +| name | type | description | +| ------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `options.auth` | string | **Required**. API access token | +| `options.userAgent` | string | Identifier of your app. Defaults to `replicate-javascript/${packageJSON.version}` | +| `options.baseUrl` | string | Defaults to https://api.replicate.com/v1 | +| `options.fetch` | function | Fetch function to use. Defaults to `globalThis.fetch` | +| `options.fileEncodingStrategy` | string | Determines the file encoding strategy to use. Possible values: `"default"`, `"upload"`, or `"data-uri"`. Defaults to `"default"` | + The client makes requests to Replicate's API using [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch). @@ -983,6 +991,99 @@ const response = await replicate.deployments.update(deploymentOwner, deploymentN } ``` +### `replicate.files.create` + +Upload a file to Replicate. + +> [!TIP] +> The client library calls this endpoint automatically to upload the contents of +> file handles provided as prediction and training inputs. +> You don't need to call this method directly unless you want more control. +> For example, you might want to reuse a file across multiple predictions +> without re-uploading it each time, +> or you may want to set custom metadata on the file resource. +> +> You can configure how a client handles file handle inputs +> by setting the `fileEncodingStrategy` option in the +> [client constructor](#constructor). + +```js +const response = await replicate.files.create(file, metadata); +``` + +| name | type | description | +| ---------- | --------------------- | ---------------------------------------------------------- | +| `file` | Blob, File, or Buffer | **Required**. The file to upload. | +| `metadata` | object | Optional. User-provided metadata associated with the file. | + +```jsonc +{ + "id": "MTQzODcyMDct0YjZkLWE1ZGYtMmRjZTViNWIwOGEyNjNhNS0", + "name": "photo.webp", + "content_type": "image/webp", + "size": 96936, + "etag": "f211779ff7502705bbf42e9874a17ab3", + "checksums": { + "sha256": "7282eb6991fa4f38d80c312dc207d938c156d714c94681623aedac846488e7d3", + "md5": "f211779ff7502705bbf42e9874a17ab3" + }, + "metadata": { + "customer_reference_id": "123" + }, + "created_at": "2024-06-28T10:16:04.062Z", + "expires_at": "2024-06-29T10:16:04.062Z", + "urls": { + "get": "https://api.replicate.com/v1/files/MTQzODcyMDct0YjZkLWE1ZGYtMmRjZTViNWIwOGEyNjNhNS0" + } +} +``` + +Files uploaded to Replicate using this endpoint expire after 24 hours. + +Pass the `urls.get` property of a file resource +to use it as an input when running a model on Replicate. +The value of `urls.get` is opaque, +and shouldn't be inferred from other attributes. + +The contents of a file are only made accessible to a model running on Replicate, +and only when passed as a prediction or training input +by the user or organization who created the file. + +### `replicate.files.list` + +List all files you've uploaded. + +```js +const response = await replicate.files.list(); +``` + +### `replicate.files.get` + +Get metadata for a specific file. + +```js +const response = await replicate.files.get(file_id); +``` + +| name | type | description | +| --------- | ------ | --------------------------------- | +| `file_id` | string | **Required**. The ID of the file. | + +### `replicate.files.delete` + +Delete a file. + +Files uploaded using the `replicate.files.create` method expire after 24 hours. +You can use this method to delete them sooner. + +```js +const response = await replicate.files.delete(file_id); +``` + +| name | type | description | +| --------- | ------ | --------------------------------- | +| `file_id` | string | **Required**. The ID of the file. | + ### `replicate.paginate` Pass another method as an argument to iterate over results From d83117da4d338772ba16e97a5a7ba43cb127b44d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 09:33:04 -0700 Subject: [PATCH 145/207] Bump braces from 3.0.2 to 3.0.3 (#272) Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3. - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: braces dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 710a1e9c..6af93034 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1930,12 +1930,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -2701,9 +2701,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" From a3adcb3fb53a892a8b225333da10533be0e464e1 Mon Sep 17 00:00:00 2001 From: Mattt Date: Wed, 3 Jul 2024 14:49:25 -0700 Subject: [PATCH 146/207] Add support for deployments.delete endpoint (#279) --- index.d.ts | 4 ++++ index.js | 1 + index.test.ts | 14 ++++++++++++++ lib/deployments.js | 19 +++++++++++++++++++ 4 files changed, 38 insertions(+) diff --git a/index.d.ts b/index.d.ts index 1ef9e89c..e6fd2d9c 100644 --- a/index.d.ts +++ b/index.d.ts @@ -236,6 +236,10 @@ declare module "replicate" { | { max_instances: number } ) ): Promise; + delete( + deployment_owner: string, + deployment_name: string + ): Promise; list(): Promise>; }; diff --git a/index.js b/index.js index bf53449a..b92503ec 100644 --- a/index.js +++ b/index.js @@ -71,6 +71,7 @@ class Replicate { get: deployments.get.bind(this), create: deployments.create.bind(this), update: deployments.update.bind(this), + delete: deployments.delete.bind(this), list: deployments.list.bind(this), predictions: { create: deployments.predictions.create.bind(this), diff --git a/index.test.ts b/index.test.ts index 8ecb5ae2..9e22cbe0 100644 --- a/index.test.ts +++ b/index.test.ts @@ -999,6 +999,20 @@ describe("Replicate client", () => { // Add more tests for error handling, edge cases, etc. }); + describe("deployments.delete", () => { + test("Calls the correct API route with the correct payload", async () => { + nock(BASE_URL) + .delete("/deployments/acme/my-app-image-generator") + .reply(204); + + const success = await client.deployments.delete( + "acme", + "my-app-image-generator" + ); + expect(success).toBe(true); + }); + }); + describe("deployments.list", () => { test("Calls the correct API route", async () => { nock(BASE_URL) diff --git a/lib/deployments.js b/lib/deployments.js index 27a2f6a2..56ed2406 100644 --- a/lib/deployments.js +++ b/lib/deployments.js @@ -118,6 +118,24 @@ async function updateDeployment( return response.json(); } +/** + * Delete a deployment + * + * @param {string} deployment_owner - Required. The username of the user or organization who owns the deployment + * @param {string} deployment_name - Required. The name of the deployment + * @returns {Promise} Resolves with true if the deployment was deleted + */ +async function deleteDeployment(deployment_owner, deployment_name) { + const response = await this.request( + `/deployments/${deployment_owner}/${deployment_name}`, + { + method: "DELETE", + } + ); + + return response.status === 204; +} + /** * List all deployments * @@ -139,4 +157,5 @@ module.exports = { create: createDeployment, update: updateDeployment, list: listDeployments, + delete: deleteDeployment, }; From 7f2bd81f83ff5b1ac217289684e524d2d0a999d6 Mon Sep 17 00:00:00 2001 From: Mattt Date: Wed, 3 Jul 2024 15:44:01 -0700 Subject: [PATCH 147/207] Export files methods to client (#278) * Export files methods to client * Add type definition for file operations * Add test coverage for file methods --- index.d.ts | 10 ++++ index.js | 8 +++ index.test.ts | 140 +++++++++++++++++++++++++++++++++++++++++--------- lib/files.js | 4 +- 4 files changed, 135 insertions(+), 27 deletions(-) diff --git a/index.d.ts b/index.d.ts index e6fd2d9c..7d4ef0a9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -243,6 +243,16 @@ declare module "replicate" { list(): Promise>; }; + files: { + create( + file: Blob | Buffer, + metadata?: Record + ): Promise; + list(): Promise>; + get(file_id: string): Promise; + delete(file_id: string): Promise; + }; + hardware: { list(): Promise; }; diff --git a/index.js b/index.js index b92503ec..21e83f95 100644 --- a/index.js +++ b/index.js @@ -11,6 +11,7 @@ const { const accounts = require("./lib/accounts"); const collections = require("./lib/collections"); const deployments = require("./lib/deployments"); +const files = require("./lib/files"); const hardware = require("./lib/hardware"); const models = require("./lib/models"); const predictions = require("./lib/predictions"); @@ -78,6 +79,13 @@ class Replicate { }, }; + this.files = { + create: files.create.bind(this), + get: files.get.bind(this), + list: files.list.bind(this), + delete: files.delete.bind(this), + }; + this.hardware = { list: hardware.list.bind(this), }; diff --git a/index.test.ts b/index.test.ts index 9e22cbe0..2645ca4d 100644 --- a/index.test.ts +++ b/index.test.ts @@ -15,6 +15,31 @@ const BASE_URL = "https://api.replicate.com/v1"; nock.disableNetConnect(); +const fileTestCases = [ + // Skip test case if File type is not available + ...(typeof File !== "undefined" + ? [ + { + type: "file", + value: new File(["hello world"], "file_hello.txt", { + type: "text/plain", + }), + expected: "data:text/plain;base64,aGVsbG8gd29ybGQ=", + }, + ] + : []), + { + type: "blob", + value: new Blob(["hello world"], { type: "text/plain" }), + expected: "data:text/plain;base64,aGVsbG8gd29ybGQ=", + }, + { + type: "buffer", + value: Buffer.from("hello world"), + expected: "data:application/octet-stream;base64,aGVsbG8gd29ybGQ=", + }, +]; + describe("Replicate client", () => { let unmatched: any[] = []; const handleNoMatch = (req: unknown, options: any, body: string) => @@ -264,31 +289,6 @@ describe("Replicate client", () => { } ); - const fileTestCases = [ - // Skip test case if File type is not available - ...(typeof File !== "undefined" - ? [ - { - type: "file", - value: new File(["hello world"], "file_hello.txt", { - type: "text/plain", - }), - expected: "data:text/plain;base64,aGVsbG8gd29ybGQ=", - }, - ] - : []), - { - type: "blob", - value: new Blob(["hello world"], { type: "text/plain" }), - expected: "data:text/plain;base64,aGVsbG8gd29ybGQ=", - }, - { - type: "buffer", - value: Buffer.from("hello world"), - expected: "data:application/octet-stream;base64,aGVsbG8gd29ybGQ=", - }, - ]; - test.each(fileTestCases)( "converts a $type input into a Replicate file URL", async ({ value: data, type }) => { @@ -1072,6 +1072,96 @@ describe("Replicate client", () => { // Add more tests for error handling, edge cases, etc. }); + describe("files.create", () => { + test("Calls the correct API route with the correct payload", async () => { + for (const testCase of fileTestCases) { + nock(BASE_URL) + .post("/files") + .reply(200, { + id: "123", + name: "test-file", + content_type: "application/octet-stream", + size: 1024, + etag: "abc123", + checksum: "sha256:1234567890abcdef", + metadata: {}, + created_at: "2023-01-01T00:00:00Z", + expires_at: null, + urls: { + get: "https://api.replicate.com/v1/files/123", + }, + }); + const file = await client.files.create(testCase.value); + expect(file.id).toBe("123"); + expect(file.name).toBe("test-file"); + } + }); + }); + + describe("files.get", () => { + test("Calls the correct API route", async () => { + nock(BASE_URL) + .get("/files/123") + .reply(200, { + id: "123", + name: "test-file", + content_type: "application/octet-stream", + size: 1024, + etag: "abc123", + checksum: "sha256:1234567890abcdef", + metadata: {}, + created_at: "2023-01-01T00:00:00Z", + expires_at: null, + urls: { + get: "https://api.replicate.com/v1/files/123", + }, + }); + + const file = await client.files.get("123"); + expect(file.id).toBe("123"); + expect(file.name).toBe("test-file"); + }); + }); + + describe("files.list", () => { + test("Calls the correct API route", async () => { + nock(BASE_URL) + .get("/files") + .reply(200, { + next: null, + previous: null, + results: [ + { + id: "123", + name: "test-file", + content_type: "application/octet-stream", + size: 1024, + etag: "abc123", + checksum: "sha256:1234567890abcdef", + metadata: {}, + created_at: "2023-01-01T00:00:00Z", + expires_at: null, + urls: { + get: "https://api.replicate.com/v1/files/123", + }, + }, + ], + }); + + const files = await client.files.list(); + expect(files.results.length).toBe(1); + expect(files.results[0].id).toBe("123"); + }); + }); + + describe("files.delete", () => { + test("Calls the correct API route", async () => { + nock(BASE_URL).delete("/files/123").reply(204); + const success = await client.files.delete("123"); + expect(success).toBe(true); + }); + }); + describe("hardware.list", () => { test("Calls the correct API route", async () => { nock(BASE_URL) diff --git a/lib/files.js b/lib/files.js index 520f2aa2..de49c580 100644 --- a/lib/files.js +++ b/lib/files.js @@ -72,14 +72,14 @@ async function getFile(file_id) { * Delete a file * * @param {string} file_id - Required. The ID of the file. - * @returns {Promise} - Resolves with the deletion confirmation + * @returns {Promise} - Resolves with true if the file was deleted */ async function deleteFile(file_id) { const response = await this.request(`/files/${file_id}`, { method: "DELETE", }); - return response.json(); + return response.status === 204; } module.exports = { From 91218056871de60f3bcc3c6d9a7a0b664d180102 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Wed, 3 Jul 2024 15:49:15 -0700 Subject: [PATCH 148/207] 0.31.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6af93034..c6906dc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.30.2", + "version": "0.31.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.30.2", + "version": "0.31.0", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index f31fb229..9d9b60bf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.30.2", + "version": "0.31.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 7fec3af05e70a503abd440f72f066e1a6368b33a Mon Sep 17 00:00:00 2001 From: Mattt Date: Fri, 5 Jul 2024 04:35:02 -0700 Subject: [PATCH 149/207] Add integration test for Deno (#281) * Add integration test for Deno * Test local build of replicate --- .github/workflows/ci.yml | 28 ++++++++++- integration/deno/deno.json | 5 ++ integration/deno/deno.lock | 88 ++++++++++++++++++++++++++++++++++ integration/deno/index.test.ts | 26 ++++++++++ integration/deno/index.ts | 16 +++++++ 5 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 integration/deno/deno.json create mode 100644 integration/deno/deno.lock create mode 100644 integration/deno/index.test.ts create mode 100644 integration/deno/index.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0a90e49..87b1f75e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -141,7 +141,6 @@ jobs: npm --prefix integration/${{ matrix.suite }} install "./${{ needs.build.outputs.tarball-name }}" npm --prefix integration/${{ matrix.suite }} test - integration-bun: needs: [test, build] runs-on: ubuntu-latest @@ -172,6 +171,33 @@ jobs: bun test && break || echo "Test failed, retrying..." done + integration-deno: + needs: [test, build] + runs-on: ubuntu-latest + + env: + REPLICATE_API_TOKEN: ${{ secrets.REPLICATE_API_TOKEN }} + + strategy: + matrix: + deno-version: [v1.x] + suite: [deno] + + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v3 + with: + name: package-tarball + - name: Use Deno ${{ matrix.deno-version }} + uses: denoland/setup-deno@v1 + with: + deno-version: ${{ matrix.deno-version }} + - run: | + cd integration/deno + deno cache --node-modules-dir index.ts + tar -xzf ../../${{ needs.build.outputs.tarball-name }} --strip-components=1 -C node_modules/replicate + deno test --allow-env --allow-net --node-modules-dir index.test.ts + integration-nextjs: needs: [test, build] runs-on: ubuntu-latest diff --git a/integration/deno/deno.json b/integration/deno/deno.json new file mode 100644 index 00000000..6b3e0a05 --- /dev/null +++ b/integration/deno/deno.json @@ -0,0 +1,5 @@ +{ + "imports": { + "replicate": "npm:replicate" + } +} diff --git a/integration/deno/deno.lock b/integration/deno/deno.lock new file mode 100644 index 00000000..057d6122 --- /dev/null +++ b/integration/deno/deno.lock @@ -0,0 +1,88 @@ +{ + "version": "3", + "packages": { + "specifiers": { + "jsr:@std/assert": "jsr:@std/assert@0.226.0", + "jsr:@std/internal@^1.0.0": "jsr:@std/internal@1.0.1", + "npm:replicate": "npm:replicate@0.31.0" + }, + "jsr": { + "@std/assert@0.226.0": { + "integrity": "0dfb5f7c7723c18cec118e080fec76ce15b4c31154b15ad2bd74822603ef75b3", + "dependencies": [ + "jsr:@std/internal@^1.0.0" + ] + }, + "@std/internal@1.0.1": { + "integrity": "6f8c7544d06a11dd256c8d6ba54b11ed870aac6c5aeafff499892662c57673e6" + } + }, + "npm": { + "abort-controller@3.0.0": { + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "event-target-shim@5.0.1" + } + }, + "base64-js@1.5.1": { + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dependencies": {} + }, + "buffer@6.0.3": { + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dependencies": { + "base64-js": "base64-js@1.5.1", + "ieee754": "ieee754@1.2.1" + } + }, + "event-target-shim@5.0.1": { + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dependencies": {} + }, + "events@3.3.0": { + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dependencies": {} + }, + "ieee754@1.2.1": { + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dependencies": {} + }, + "process@0.11.10": { + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dependencies": {} + }, + "readable-stream@4.5.2": { + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "abort-controller@3.0.0", + "buffer": "buffer@6.0.3", + "events": "events@3.3.0", + "process": "process@0.11.10", + "string_decoder": "string_decoder@1.3.0" + } + }, + "replicate@0.31.0": { + "integrity": "sha512-BQl52LqndfY2sLQ384jyspaJI5ia301+IN1zOBbKZa2dB5EnayUxS0ynFueOdwo/4qRfQTR0GKJwpKFK/mb3zw==", + "dependencies": { + "readable-stream": "readable-stream@4.5.2" + } + }, + "safe-buffer@5.2.1": { + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dependencies": {} + }, + "string_decoder@1.3.0": { + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "safe-buffer@5.2.1" + } + } + } + }, + "remote": {}, + "workspace": { + "dependencies": [ + "npm:replicate" + ] + } +} diff --git a/integration/deno/index.test.ts b/integration/deno/index.test.ts new file mode 100644 index 00000000..28965d57 --- /dev/null +++ b/integration/deno/index.test.ts @@ -0,0 +1,26 @@ +import { assertEquals } from "jsr:@std/assert"; +import main from "./index.ts"; + +// Verify exported types. +import type { + Status, + Visibility, + WebhookEventType, + ApiError, + Collection, + Hardware, + Model, + ModelVersion, + Prediction, + Training, + Page, + ServerSentEvent, +} from "replicate"; + +Deno.test({ + name: "main", + async fn() { + const output = await main(); + assertEquals({ output }, { output: "hello Deno the dinosaur" }); + }, +}); diff --git a/integration/deno/index.ts b/integration/deno/index.ts new file mode 100644 index 00000000..069e28a0 --- /dev/null +++ b/integration/deno/index.ts @@ -0,0 +1,16 @@ +import Replicate from "replicate"; + +const replicate = new Replicate({ + auth: Deno.env.get("REPLICATE_API_TOKEN"), +}); + +export default async function main() { + return await replicate.run( + "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + { + input: { + text: "Deno the dinosaur", + }, + } + ); +} From 90de8f866f2f476a86e4585a18e18bebd3ce4a2a Mon Sep 17 00:00:00 2001 From: Mattt Date: Fri, 5 Jul 2024 04:40:35 -0700 Subject: [PATCH 150/207] Fix Deno integration test (#282) Add missing package.json file --- integration/deno/package.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 integration/deno/package.json diff --git a/integration/deno/package.json b/integration/deno/package.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/integration/deno/package.json @@ -0,0 +1 @@ +{} From f70b3d967a6664f9b1681bfa06599a6407c2d233 Mon Sep 17 00:00:00 2001 From: Mattt Date: Fri, 5 Jul 2024 05:07:22 -0700 Subject: [PATCH 151/207] Document supported platforms (#280) * Document supported platforms * Document support for Deno >= 1.28 --- README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 797af77c..0f26281d 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,18 @@ and everything else you can do with > For more information about how to build a web application > check out our ["Build a website with Next.js"](https://replicate.com/docs/get-started/nextjs) guide. -## Installation +## Supported platforms + +- [Node.js](https://nodejs.org) >= 18 +- [Bun](https://bun.sh) >= 1.0 +- [Deno](https://deno.com) >= 1.28 -This library requires Node.js >= 18. +You can also use this client library on most serverless platforms, including +[Cloudflare Workers](https://developers.cloudflare.com/workers/), +[Vercel functions](https://vercel.com/docs/functions), and +[AWS Lambda](https://aws.amazon.com/lambda/). + +## Installation Install it from npm: @@ -230,7 +239,7 @@ By default, the `globalThis.fetch` function is used, which is available on [Node.js 18](https://nodejs.org/en/blog/announcements/v18-release-announce#fetch-experimental) and later, as well as [Cloudflare Workers](https://developers.cloudflare.com/workers/runtime-apis/fetch/), -[Vercel Edge Functions](https://vercel.com/docs/concepts/functions/edge-functions), +[Vercel Functions](https://vercel.com/docs/functions), and other environments. On earlier versions of Node.js From 8d8e59914b59410c39d82483968d1e3d806e7a08 Mon Sep 17 00:00:00 2001 From: Mattt Date: Fri, 5 Jul 2024 05:57:56 -0700 Subject: [PATCH 152/207] Run integration tests with `replicate/canary` model (#283) * Run integration tests with replicate/canary model * Update expected output * Add test matrix for Next.js * Reorder test matrix configurations * Add test matrix for build step --- .github/workflows/ci.yml | 29 +++++++++++++++++++--------- integration/bun/index.test.ts | 2 +- integration/commonjs/index.js | 5 +++-- integration/commonjs/index.test.js | 2 +- integration/deno/index.test.ts | 2 +- integration/deno/index.ts | 7 ++++--- integration/esm/index.js | 5 +++-- integration/esm/index.test.js | 2 +- integration/typescript/index.test.ts | 2 +- integration/typescript/index.ts | 7 ++++--- 10 files changed, 39 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87b1f75e..27a982cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,7 @@ jobs: strategy: matrix: + suite: [node] # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases node-version: [18.x, 20.x, 22.x] @@ -32,15 +33,20 @@ jobs: build: runs-on: ubuntu-latest + strategy: + matrix: + # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases + node-version: [20.x] + outputs: tarball-name: ${{ steps.pack.outputs.tarball-name }} steps: - uses: actions/checkout@v4 - - name: Use Node.js + - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: ${{ matrix.node-version }} cache: "npm" - name: Build tarball id: pack @@ -61,9 +67,9 @@ jobs: strategy: matrix: + suite: [commonjs, esm, typescript] # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases node-version: [18.x, 20.x] - suite: [commonjs, esm, typescript] fail-fast: false steps: @@ -90,9 +96,9 @@ jobs: strategy: matrix: - node-version: [20.x] - browser: ["chromium", "firefox", "webkit"] suite: ["browser"] + browser: ["chromium", "firefox", "webkit"] + node-version: [20.x] fail-fast: false steps: @@ -122,8 +128,8 @@ jobs: strategy: matrix: - node-version: [20.x] suite: [cloudflare-worker] + node-version: [20.x] steps: - uses: actions/checkout@v4 @@ -150,8 +156,8 @@ jobs: strategy: matrix: - bun-version: [1.0.11] suite: [bun] + bun-version: [1.0.11] steps: - uses: actions/checkout@v4 @@ -180,8 +186,8 @@ jobs: strategy: matrix: - deno-version: [v1.x] suite: [deno] + deno-version: [v1.x] steps: - uses: actions/checkout@v4 @@ -202,6 +208,11 @@ jobs: needs: [test, build] runs-on: ubuntu-latest + strategy: + matrix: + suite: [nextjs] + node-version: [20.x] + env: REPLICATE_API_TOKEN: ${{ secrets.REPLICATE_API_TOKEN }} @@ -213,7 +224,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: ${{ matrix.node-version }} cache: "npm" - run: | npm --prefix integration/next install diff --git a/integration/bun/index.test.ts b/integration/bun/index.test.ts index 1357e5ba..f0665a6d 100644 --- a/integration/bun/index.test.ts +++ b/integration/bun/index.test.ts @@ -19,5 +19,5 @@ import type { test("main", async () => { const output = await main(); - expect(output).toContain("Brünnhilde Bun"); + expect(output).toEqual("hello there, Brünnhilde Bun"); }); diff --git a/integration/commonjs/index.js b/integration/commonjs/index.js index 40f00cd0..6d8c436b 100644 --- a/integration/commonjs/index.js +++ b/integration/commonjs/index.js @@ -5,12 +5,13 @@ const replicate = new Replicate({ }); module.exports = async function main() { - return await replicate.run( - "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + const output = await replicate.run( + "replicate/canary:30e22229542eb3f79d4f945dacb58d32001b02cc313ae6f54eef27904edf3272", { input: { text: "Claire CommonJS", }, } ); + return output.join("").trim(); }; diff --git a/integration/commonjs/index.test.js b/integration/commonjs/index.test.js index 6e0f8b2a..afc3c014 100644 --- a/integration/commonjs/index.test.js +++ b/integration/commonjs/index.test.js @@ -4,5 +4,5 @@ const main = require("./index"); test("main", async () => { const output = await main(); - assert.equal(output, "hello Claire CommonJS"); + assert.equal(output, "hello there, Claire CommonJS"); }); diff --git a/integration/deno/index.test.ts b/integration/deno/index.test.ts index 28965d57..e944d770 100644 --- a/integration/deno/index.test.ts +++ b/integration/deno/index.test.ts @@ -21,6 +21,6 @@ Deno.test({ name: "main", async fn() { const output = await main(); - assertEquals({ output }, { output: "hello Deno the dinosaur" }); + assertEquals({ output }, { output: "hello there, Deno the dinosaur" }); }, }); diff --git a/integration/deno/index.ts b/integration/deno/index.ts index 069e28a0..a235699b 100644 --- a/integration/deno/index.ts +++ b/integration/deno/index.ts @@ -5,12 +5,13 @@ const replicate = new Replicate({ }); export default async function main() { - return await replicate.run( - "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + const output = (await replicate.run( + "replicate/canary:30e22229542eb3f79d4f945dacb58d32001b02cc313ae6f54eef27904edf3272", { input: { text: "Deno the dinosaur", }, } - ); + )) as string[]; + return output.join("").trim(); } diff --git a/integration/esm/index.js b/integration/esm/index.js index 9264f3c0..48feccb7 100644 --- a/integration/esm/index.js +++ b/integration/esm/index.js @@ -5,12 +5,13 @@ const replicate = new Replicate({ }); export default async function main() { - return await replicate.run( - "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + const output = await replicate.run( + "replicate/canary:30e22229542eb3f79d4f945dacb58d32001b02cc313ae6f54eef27904edf3272", { input: { text: "Evelyn ESM", }, } ); + return Array.isArray(output) ? output.join("").trim() : String(output).trim(); } diff --git a/integration/esm/index.test.js b/integration/esm/index.test.js index 7549e909..c95ab8b0 100644 --- a/integration/esm/index.test.js +++ b/integration/esm/index.test.js @@ -4,5 +4,5 @@ import main from "./index.js"; test("main", async () => { const output = await main(); - assert.equal(output, "hello Evelyn ESM"); + assert.equal(output, "hello there, Evelyn ESM"); }); diff --git a/integration/typescript/index.test.ts b/integration/typescript/index.test.ts index b7928c5d..0f0f356b 100644 --- a/integration/typescript/index.test.ts +++ b/integration/typescript/index.test.ts @@ -20,5 +20,5 @@ import type { test("main", async () => { const output = await main(); - assert.equal(output, "hello Tracy TypeScript"); + assert.equal(output, "hello there, Tracy TypeScript"); }); diff --git a/integration/typescript/index.ts b/integration/typescript/index.ts index e49f75c2..1b276bc9 100644 --- a/integration/typescript/index.ts +++ b/integration/typescript/index.ts @@ -5,12 +5,13 @@ const replicate = new Replicate({ }); export default async function main() { - return await replicate.run( - "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + const output = (await replicate.run( + "replicate/canary:30e22229542eb3f79d4f945dacb58d32001b02cc313ae6f54eef27904edf3272", { input: { text: "Tracy TypeScript", }, } - ); + )) as string[]; + return output.join("").trim(); } From ba5614ed263808046db9101d7e150cc9388a92d4 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Fri, 5 Jul 2024 16:45:38 +0100 Subject: [PATCH 153/207] Document alternate interface to `validateWebhook()` Fixes #261 --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 0f26281d..401bfadb 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,19 @@ export async function POST(request) { } ``` +If your environment doesn't support `Request` objects, you can pass the required information to `validateWebhook` directly: + +```js +const requestData = { + id: "123", // the `Webhook-Id` header + timestamp: "0123456", // the `Webhook-Timestamp` header + signature: "xyz", // the `Webhook-Signature` header + body: "{...}", // the request body as a string, ArrayBuffer or ReadableStream + secret: "shhh", // the webhook secret, obtained from the `replicate.webhooks.defaul.secret` endpoint +}; +const webhookIsValid = await validateWebhook(requestData); +``` + ## TypeScript The `Replicate` constructor and all `replicate.*` methods are fully typed. From 7d4c04cd8b394112a99297b260be16196b9934f0 Mon Sep 17 00:00:00 2001 From: Mattt Date: Fri, 5 Jul 2024 11:12:27 -0700 Subject: [PATCH 154/207] Document 100MiB file upload limit (#287) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 401bfadb..8f4350b1 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,10 @@ const output = await replicate.run(model, { input }); > [!NOTE] > File handle inputs are automatically uploaded to Replicate. > See [`replicate.files.create`](#replicatefilescreate) for more information. +> The maximum size for uploaded files is 100MiB. +> To run a model with a larger file as an input, +> upload the file to your own storage provider +> and pass a publicly accessible URL. ### Webhooks From 997da157836be911de09c4bf7cdf93f7bc58688e Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Fri, 5 Jul 2024 19:13:40 +0100 Subject: [PATCH 155/207] Re-raise 4xx responses from server when uploading files (#284) Fixes #270 We were falling back to base64 encoding the file data when requests to upload the file failed. However if the client makes an invalid request such as failing to include Authorization headers by forgetting to auth then we should surface these errors. This commit now re-raises any 4xx errors returned while attempting to upload a file and adds tests to verify the behavior. --- index.test.ts | 427 +++++++++++++++++++++----------------------------- lib/util.js | 35 ++--- 2 files changed, 189 insertions(+), 273 deletions(-) diff --git a/index.test.ts b/index.test.ts index 2645ca4d..c4d7e067 100644 --- a/index.test.ts +++ b/index.test.ts @@ -1,11 +1,5 @@ import { expect, jest, test } from "@jest/globals"; -import Replicate, { - ApiError, - Model, - Prediction, - validateWebhook, - parseProgressFromLogs, -} from "replicate"; +import Replicate, { ApiError, Model, Prediction, validateWebhook, parseProgressFromLogs } from "replicate"; import nock from "nock"; import { Readable } from "node:stream"; import { createReadableStream } from "./lib/stream"; @@ -42,8 +36,7 @@ const fileTestCases = [ describe("Replicate client", () => { let unmatched: any[] = []; - const handleNoMatch = (req: unknown, options: any, body: string) => - unmatched.push({ req, options, body }); + const handleNoMatch = (req: unknown, options: any, body: string) => unmatched.push({ req, options, body }); beforeEach(() => { client = new Replicate({ auth: "test-token" }); @@ -123,8 +116,7 @@ describe("Replicate client", () => { { name: "Super resolution", slug: "super-resolution", - description: - "Upscaling models that create high-quality images from low-quality images.", + description: "Upscaling models that create high-quality images from low-quality images.", }, { name: "Image classification", @@ -147,8 +139,7 @@ describe("Replicate client", () => { nock(BASE_URL).get("/collections/super-resolution").reply(200, { name: "Super resolution", slug: "super-resolution", - description: - "Upscaling models that create high-quality images from low-quality images.", + description: "Upscaling models that create high-quality images from low-quality images.", models: [], }); @@ -188,9 +179,7 @@ describe("Replicate client", () => { results: [{ url: "https://replicate.com/some-user/model-1" }], next: "https://api.replicate.com/v1/models?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw", }) - .get( - "/models?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw" - ) + .get("/models?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw") .reply(200, { results: [{ url: "https://replicate.com/some-user/model-2" }], next: null, @@ -248,12 +237,10 @@ describe("Replicate client", () => { expectedResponse: { id: "ufawqhfynnddngldkgtslldrkq", model: "replicate/hello-world", - version: - "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", urls: { get: "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", - cancel: - "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel", + cancel: "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel", }, input: testCase.input, created_at: "2022-04-26T22:13:06.224088Z", @@ -263,79 +250,64 @@ describe("Replicate client", () => { }, })); - test.each(predictionTestCases)( - "$description", - async ({ input, expectedResponse }) => { - nock(BASE_URL) - .post("/predictions", { - version: - "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", - input: input as Record, - webhook: "http://test.host/webhook", - webhook_events_filter: ["output", "completed"], - }) - .reply(200, expectedResponse); - - const response = await client.predictions.create({ - version: - "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + test.each(predictionTestCases)("$description", async ({ input, expectedResponse }) => { + nock(BASE_URL) + .post("/predictions", { + version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: input as Record, webhook: "http://test.host/webhook", webhook_events_filter: ["output", "completed"], - }); + }) + .reply(200, expectedResponse); - expect(response.input).toEqual(input); - expect(response.status).toBe(expectedResponse.status); - } - ); + const response = await client.predictions.create({ + version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + input: input as Record, + webhook: "http://test.host/webhook", + webhook_events_filter: ["output", "completed"], + }); - test.each(fileTestCases)( - "converts a $type input into a Replicate file URL", - async ({ value: data, type }) => { - const mockedFetch = jest.spyOn(client, "fetch"); + expect(response.input).toEqual(input); + expect(response.status).toBe(expectedResponse.status); + }); - nock(BASE_URL) - .post("/files") - .reply(201, { - urls: { - get: "https://replicate.com/api/files/123", - }, - }) - .post( - "/predictions", - (body) => body.input.data === "https://replicate.com/api/files/123" - ) - .reply(201, (_uri: string, body: Record) => { - return body; - }); + test.each(fileTestCases)("converts a $type input into a Replicate file URL", async ({ value: data, type }) => { + const mockedFetch = jest.spyOn(client, "fetch"); - const prediction = await client.predictions.create({ - version: - "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", - input: { - prompt: "Tell me a story", - data, + nock(BASE_URL) + .post("/files") + .reply(201, { + urls: { + get: "https://replicate.com/api/files/123", }, + }) + .post("/predictions", (body) => body.input.data === "https://replicate.com/api/files/123") + .reply(201, (_uri: string, body: Record) => { + return body; }); - expect(client.fetch).toHaveBeenCalledWith( - new URL("https://api.replicate.com/v1/files"), - { - method: "POST", - body: expect.any(FormData), - headers: expect.any(Object), - } - ); - const form = mockedFetch.mock.calls[0][1]?.body as FormData; - // @ts-ignore - expect(form?.get("content")?.name).toMatch(new RegExp(`^${type}_`)); - - expect(prediction.input).toEqual({ + const prediction = await client.predictions.create({ + version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + input: { prompt: "Tell me a story", - data: "https://replicate.com/api/files/123", - }); - } - ); + data, + }, + }); + + expect(client.fetch).toHaveBeenCalledWith(new URL("https://api.replicate.com/v1/files"), { + method: "POST", + body: expect.any(FormData), + headers: expect.any(Object), + }); + const form = mockedFetch.mock.calls[0][1]?.body as FormData; + // @ts-ignore + expect(form?.get("content")?.name).toMatch(new RegExp(`^${type}_`)); + + expect(prediction.input).toEqual({ + prompt: "Tell me a story", + data: "https://replicate.com/api/files/123", + }); + }); test.each(fileTestCases)( "converts a $type input into a base64 encoded string", @@ -351,8 +323,7 @@ describe("Replicate client", () => { }); await client.predictions.create({ - version: - "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { prompt: "Tell me a story", data, @@ -361,7 +332,38 @@ describe("Replicate client", () => { }); expect(actual?.input.data).toEqual(expected); - } + }, + ); + + test.each(fileTestCases)( + "raises an error when the file upload fails with 4xx error for a $type input", + async ({ value: data, expected }) => { + let actual: Record | undefined; + nock(BASE_URL) + .post("/files") + .reply(401, "Unauthorized") + .post("/predictions") + .reply(201, (_uri: string, body: Record) => { + actual = body; + return body; + }); + + await expect(async () => { + await client.predictions.create({ + version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + input: { + prompt: "Tell me a story", + data, + }, + stream: true, + }); + }).rejects.toThrowError( + expect.objectContaining({ + name: "ApiError", + message: expect.stringContaining("401"), + }), + ); + }, ); test("Passes stream parameter to API endpoint", async () => { @@ -373,8 +375,7 @@ describe("Replicate client", () => { }); await client.predictions.create({ - version: - "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { prompt: "Tell me a story", }, @@ -385,8 +386,7 @@ describe("Replicate client", () => { test("Throws an error if webhook URL is invalid", async () => { await expect(async () => { await client.predictions.create({ - version: - "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { text: "Alice", }, @@ -402,15 +402,14 @@ describe("Replicate client", () => { status: 400, detail: "Invalid input", }, - { "Content-Type": "application/json" } + { "Content-Type": "application/json" }, ); try { expect.hasAssertions(); await client.predictions.create({ - version: - "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { text: null, }, @@ -429,15 +428,14 @@ describe("Replicate client", () => { { detail: "Too many requests", }, - { "Content-Type": "application/json", "Retry-After": "1" } + { "Content-Type": "application/json", "Retry-After": "1" }, ) .post("/predictions") .reply(201, { id: "ufawqhfynnddngldkgtslldrkq", }); const prediction = await client.predictions.create({ - version: - "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { text: "Alice", }, @@ -451,19 +449,18 @@ describe("Replicate client", () => { { detail: "Internal server error", }, - { "Content-Type": "application/json" } + { "Content-Type": "application/json" }, ); await expect( client.predictions.create({ - version: - "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { text: "Alice", }, - }) + }), ).rejects.toThrow( - `Request to https://api.replicate.com/v1/predictions failed with status 500 Internal Server Error: {"detail":"Internal server error"}.` + `Request to https://api.replicate.com/v1/predictions failed with status 500 Internal Server Error: {"detail":"Internal server error"}.`, ); }); }); @@ -475,12 +472,10 @@ describe("Replicate client", () => { .reply(200, { id: "rrr4z55ocneqzikepnug6xezpe", model: "stability-ai/stable-diffusion", - version: - "be04660a5b93ef2aff61e3668dedb4cbeb14941e62a3fd5998364a32d613e35e", + version: "be04660a5b93ef2aff61e3668dedb4cbeb14941e62a3fd5998364a32d613e35e", urls: { get: "https://api.replicate.com/v1/predictions/rrr4z55ocneqzikepnug6xezpe", - cancel: - "https://api.replicate.com/v1/predictions/rrr4z55ocneqzikepnug6xezpe/cancel", + cancel: "https://api.replicate.com/v1/predictions/rrr4z55ocneqzikepnug6xezpe/cancel", }, created_at: "2022-09-13T22:54:18.578761Z", started_at: "2022-09-13T22:54:19.438525Z", @@ -499,9 +494,7 @@ describe("Replicate client", () => { predict_time: 4.484541, }, }); - const prediction = await client.predictions.get( - "rrr4z55ocneqzikepnug6xezpe" - ); + const prediction = await client.predictions.get("rrr4z55ocneqzikepnug6xezpe"); expect(prediction.id).toBe("rrr4z55ocneqzikepnug6xezpe"); }); @@ -513,16 +506,14 @@ describe("Replicate client", () => { { detail: "Too many requests", }, - { "Content-Type": "application/json", "Retry-After": "1" } + { "Content-Type": "application/json", "Retry-After": "1" }, ) .get("/predictions/rrr4z55ocneqzikepnug6xezpe") .reply(200, { id: "rrr4z55ocneqzikepnug6xezpe", }); - const prediction = await client.predictions.get( - "rrr4z55ocneqzikepnug6xezpe" - ); + const prediction = await client.predictions.get("rrr4z55ocneqzikepnug6xezpe"); expect(prediction.id).toBe("rrr4z55ocneqzikepnug6xezpe"); }); @@ -534,16 +525,14 @@ describe("Replicate client", () => { { detail: "Internal server error", }, - { "Content-Type": "application/json" } + { "Content-Type": "application/json" }, ) .get("/predictions/rrr4z55ocneqzikepnug6xezpe") .reply(200, { id: "rrr4z55ocneqzikepnug6xezpe", }); - const prediction = await client.predictions.get( - "rrr4z55ocneqzikepnug6xezpe" - ); + const prediction = await client.predictions.get("rrr4z55ocneqzikepnug6xezpe"); expect(prediction.id).toBe("rrr4z55ocneqzikepnug6xezpe"); }); }); @@ -555,12 +544,10 @@ describe("Replicate client", () => { .reply(200, { id: "ufawqhfynnddngldkgtslldrkq", model: "replicate/hello-world", - version: - "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", urls: { get: "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", - cancel: - "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel", + cancel: "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel", }, created_at: "2022-04-26T22:13:06.224088Z", started_at: "2022-04-26T22:13:06.224088Z", @@ -575,9 +562,7 @@ describe("Replicate client", () => { metrics: {}, }); - const prediction = await client.predictions.cancel( - "ufawqhfynnddngldkgtslldrkq" - ); + const prediction = await client.predictions.cancel("ufawqhfynnddngldkgtslldrkq"); expect(prediction.status).toBe("canceled"); }); @@ -595,12 +580,10 @@ describe("Replicate client", () => { { id: "jpzd7hm5gfcapbfyt4mqytarku", model: "stability-ai/stable-diffusion", - version: - "b21cbe271e65c1718f2999b038c18b45e21e4fba961181fbfae9342fc53b9e05", + version: "b21cbe271e65c1718f2999b038c18b45e21e4fba961181fbfae9342fc53b9e05", urls: { get: "https://api.replicate.com/v1/predictions/jpzd7hm5gfcapbfyt4mqytarku", - cancel: - "https://api.replicate.com/v1/predictions/jpzd7hm5gfcapbfyt4mqytarku/cancel", + cancel: "https://api.replicate.com/v1/predictions/jpzd7hm5gfcapbfyt4mqytarku/cancel", }, created_at: "2022-04-26T20:00:40.658234Z", started_at: "2022-04-26T20:00:84.583803Z", @@ -623,9 +606,7 @@ describe("Replicate client", () => { results: [{ id: "ufawqhfynnddngldkgtslldrkq" }], next: "https://api.replicate.com/v1/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw", }) - .get( - "/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw" - ) + .get("/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw") .reply(200, { results: [{ id: "rrr4z55ocneqzikepnug6xezpe" }], next: null, @@ -635,10 +616,7 @@ describe("Replicate client", () => { for await (const batch of client.paginate(client.predictions.list)) { results.push(...batch); } - expect(results).toEqual([ - { id: "ufawqhfynnddngldkgtslldrkq" }, - { id: "rrr4z55ocneqzikepnug6xezpe" }, - ]); + expect(results).toEqual([{ id: "ufawqhfynnddngldkgtslldrkq" }, { id: "rrr4z55ocneqzikepnug6xezpe" }]); // Add more tests for error handling, edge cases, etc. }); @@ -647,13 +625,10 @@ describe("Replicate client", () => { describe("trainings.create", () => { test("Calls the correct API route with the correct payload", async () => { nock(BASE_URL) - .post( - "/models/owner/model/versions/632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532/trainings" - ) + .post("/models/owner/model/versions/632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532/trainings") .reply(200, { id: "zz4ibbonubfz7carwiefibzgga", - version: - "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", + version: "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", status: "starting", input: { text: "...", @@ -675,25 +650,20 @@ describe("Replicate client", () => { input: { text: "...", }, - } + }, ); expect(training.id).toBe("zz4ibbonubfz7carwiefibzgga"); }); test("Throws an error if webhook is not a valid URL", async () => { await expect( - client.trainings.create( - "owner", - "model", - "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", - { - destination: "new_owner/new_model", - input: { - text: "...", - }, - webhook: "invalid-url", - } - ) + client.trainings.create("owner", "model", "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", { + destination: "new_owner/new_model", + input: { + text: "...", + }, + webhook: "invalid-url", + }), ).rejects.toThrow("Invalid webhook URL"); }); @@ -753,9 +723,7 @@ describe("Replicate client", () => { completed_at: null, }); - const training = await client.trainings.cancel( - "zz4ibbonubfz7carwiefibzgga" - ); + const training = await client.trainings.cancel("zz4ibbonubfz7carwiefibzgga"); expect(training.status).toBe("canceled"); }); @@ -773,12 +741,10 @@ describe("Replicate client", () => { { id: "jpzd7hm5gfcapbfyt4mqytarku", model: "stability-ai/sdxl", - version: - "b21cbe271e65c1718f2999b038c18b45e21e4fba961181fbfae9342fc53b9e05", + version: "b21cbe271e65c1718f2999b038c18b45e21e4fba961181fbfae9342fc53b9e05", urls: { get: "https://api.replicate.com/v1/trainings/jpzd7hm5gfcapbfyt4mqytarku", - cancel: - "https://api.replicate.com/v1/trainings/jpzd7hm5gfcapbfyt4mqytarku/cancel", + cancel: "https://api.replicate.com/v1/trainings/jpzd7hm5gfcapbfyt4mqytarku/cancel", }, created_at: "2022-04-26T20:00:40.658234Z", started_at: "2022-04-26T20:00:84.583803Z", @@ -801,9 +767,7 @@ describe("Replicate client", () => { results: [{ id: "ufawqhfynnddngldkgtslldrkq" }], next: "https://api.replicate.com/v1/trainings?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw", }) - .get( - "/trainings?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw" - ) + .get("/trainings?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw") .reply(200, { results: [{ id: "rrr4z55ocneqzikepnug6xezpe" }], next: null, @@ -813,10 +777,7 @@ describe("Replicate client", () => { for await (const batch of client.paginate(client.trainings.list)) { results.push(...batch); } - expect(results).toEqual([ - { id: "ufawqhfynnddngldkgtslldrkq" }, - { id: "rrr4z55ocneqzikepnug6xezpe" }, - ]); + expect(results).toEqual([{ id: "ufawqhfynnddngldkgtslldrkq" }, { id: "rrr4z55ocneqzikepnug6xezpe" }]); // Add more tests for error handling, edge cases, etc. }); @@ -829,12 +790,10 @@ describe("Replicate client", () => { .reply(200, { id: "mfrgcyzzme2wkmbwgzrgmntcg", model: "replicate/hello-world", - version: - "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", urls: { get: "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", - cancel: - "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel", + cancel: "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel", }, created_at: "2022-09-10T09:44:22.165836Z", started_at: null, @@ -848,17 +807,13 @@ describe("Replicate client", () => { logs: null, metrics: {}, }); - const prediction = await client.deployments.predictions.create( - "replicate", - "greeter", - { - input: { - text: "Alice", - }, - webhook: "http://test.host/webhook", - webhook_events_filter: ["output", "completed"], - } - ); + const prediction = await client.deployments.predictions.create("replicate", "greeter", { + input: { + text: "Alice", + }, + webhook: "http://test.host/webhook", + webhook_events_filter: ["output", "completed"], + }); expect(prediction.id).toBe("mfrgcyzzme2wkmbwgzrgmntcg"); }); // Add more tests for error handling, edge cases, etc. @@ -874,8 +829,7 @@ describe("Replicate client", () => { current_release: { number: 1, model: "stability-ai/sdxl", - version: - "da77bc59ee60423279fd632efb4795ab731d9e3ca9705ef3341091fb989b7eaf", + version: "da77bc59ee60423279fd632efb4795ab731d9e3ca9705ef3341091fb989b7eaf", created_at: "2024-02-15T16:32:57.018467Z", created_by: { type: "organization", @@ -891,10 +845,7 @@ describe("Replicate client", () => { }, }); - const deployment = await client.deployments.get( - "acme", - "my-app-image-generator" - ); + const deployment = await client.deployments.get("acme", "my-app-image-generator"); expect(deployment.owner).toBe("acme"); expect(deployment.name).toBe("my-app-image-generator"); @@ -913,8 +864,7 @@ describe("Replicate client", () => { current_release: { number: 1, model: "stability-ai/sdxl", - version: - "da77bc59ee60423279fd632efb4795ab731d9e3ca9705ef3341091fb989b7eaf", + version: "da77bc59ee60423279fd632efb4795ab731d9e3ca9705ef3341091fb989b7eaf", created_at: "2024-02-15T16:32:57.018467Z", created_by: { type: "organization", @@ -933,8 +883,7 @@ describe("Replicate client", () => { const deployment = await client.deployments.create({ name: "my-app-image-generator", model: "stability-ai/sdxl", - version: - "da77bc59ee60423279fd632efb4795ab731d9e3ca9705ef3341091fb989b7eaf", + version: "da77bc59ee60423279fd632efb4795ab731d9e3ca9705ef3341091fb989b7eaf", hardware: "gpu-t4", min_instances: 1, max_instances: 5, @@ -957,8 +906,7 @@ describe("Replicate client", () => { current_release: { number: 2, model: "stability-ai/sdxl", - version: - "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", + version: "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", created_at: "2024-02-16T08:14:22.345678Z", created_by: { type: "organization", @@ -974,25 +922,18 @@ describe("Replicate client", () => { }, }); - const deployment = await client.deployments.update( - "acme", - "my-app-image-generator", - { - version: - "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", - hardware: "gpu-a40-large", - min_instances: 3, - max_instances: 10, - } - ); + const deployment = await client.deployments.update("acme", "my-app-image-generator", { + version: "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", + hardware: "gpu-a40-large", + min_instances: 3, + max_instances: 10, + }); expect(deployment.current_release.number).toBe(2); expect(deployment.current_release.version).toBe( - "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532" - ); - expect(deployment.current_release.configuration.hardware).toBe( - "gpu-a40-large" + "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", ); + expect(deployment.current_release.configuration.hardware).toBe("gpu-a40-large"); expect(deployment.current_release.configuration.min_instances).toBe(3); expect(deployment.current_release.configuration.max_instances).toBe(10); }); @@ -1001,14 +942,9 @@ describe("Replicate client", () => { describe("deployments.delete", () => { test("Calls the correct API route with the correct payload", async () => { - nock(BASE_URL) - .delete("/deployments/acme/my-app-image-generator") - .reply(204); + nock(BASE_URL).delete("/deployments/acme/my-app-image-generator").reply(204); - const success = await client.deployments.delete( - "acme", - "my-app-image-generator" - ); + const success = await client.deployments.delete("acme", "my-app-image-generator"); expect(success).toBe(true); }); }); @@ -1054,8 +990,7 @@ describe("Replicate client", () => { status: "starting", created_at: "2023-11-27T13:35:45.99397566Z", urls: { - cancel: - "https://api.replicate.com/v1/predictions/heat2o3bzn3ahtr6bjfftvbaci/cancel", + cancel: "https://api.replicate.com/v1/predictions/heat2o3bzn3ahtr6bjfftvbaci/cancel", get: "https://api.replicate.com/v1/predictions/heat2o3bzn3ahtr6bjfftvbaci", }, }); @@ -1265,7 +1200,7 @@ describe("Replicate client", () => { (prediction) => { const progress = parseProgressFromLogs(prediction); callback(prediction, progress); - } + }, ); expect(output).toBe("Goodbye!"); @@ -1277,7 +1212,7 @@ describe("Replicate client", () => { status: "starting", logs: null, }, - null + null, ); expect(callback).toHaveBeenNthCalledWith( @@ -1291,7 +1226,7 @@ describe("Replicate client", () => { percentage: 0.4, current: 2, total: 5, - } + }, ); expect(callback).toHaveBeenNthCalledWith( @@ -1305,7 +1240,7 @@ describe("Replicate client", () => { percentage: 0.8, current: 4, total: 5, - } + }, ); expect(callback).toHaveBeenNthCalledWith( @@ -1320,7 +1255,7 @@ describe("Replicate client", () => { percentage: 1.0, current: 5, total: 5, - } + }, ); expect(callback).toHaveBeenCalledTimes(4); @@ -1354,7 +1289,7 @@ describe("Replicate client", () => { input: { text: "Hello, world!" }, wait: { interval: 1 }, }, - progress + progress, ); expect(output).toBe("Goodbye!"); @@ -1397,9 +1332,7 @@ describe("Replicate client", () => { output: "foobar", }); - await expect( - client.run("a/b-1.0:abc123", { input: { text: "Hello, world!" } }) - ).resolves.not.toThrow(); + await expect(client.run("a/b-1.0:abc123", { input: { text: "Hello, world!" } })).resolves.not.toThrow(); }); test("Throws an error for invalid identifiers", async () => { @@ -1416,15 +1349,12 @@ describe("Replicate client", () => { test("Throws an error if webhook URL is invalid", async () => { await expect(async () => { - await client.run( - "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", - { - input: { - text: "Alice", - }, - webhook: "invalid-url", - } - ); + await client.run("owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", { + input: { + text: "Alice", + }, + webhook: "invalid-url", + }); }).rejects.toThrow("Invalid webhook URL"); }); @@ -1463,7 +1393,7 @@ describe("Replicate client", () => { input: { text: "Hello, world!" }, signal, }, - onProgress + onProgress, ); expect(body).toBeDefined(); @@ -1475,19 +1405,19 @@ describe("Replicate client", () => { 1, expect.objectContaining({ status: "processing", - }) + }), ); expect(onProgress).toHaveBeenNthCalledWith( 2, expect.objectContaining({ status: "processing", - }) + }), ); expect(onProgress).toHaveBeenNthCalledWith( 3, expect.objectContaining({ status: "canceled", - }) + }), ); scope.done(); @@ -1512,8 +1442,7 @@ describe("Replicate client", () => { "Content-Type": "application/json", "Webhook-ID": "msg_p5jXN8AQM9LWM0D4loKWxJek", "Webhook-Timestamp": "1614265330", - "Webhook-Signature": - "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=", + "Webhook-Signature": "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=", }, body: `{"test": 2432232314}`, }); @@ -1556,7 +1485,7 @@ describe("Replicate client", () => { id: EVENT_2 data: {} - `.replace(/^[ ]+/gm, "") + `.replace(/^[ ]+/gm, ""), ); const iterator = stream[Symbol.asyncIterator](); @@ -1587,7 +1516,7 @@ describe("Replicate client", () => { id: EVENT_3 data: {} - `.replace(/^[ ]+/gm, "") + `.replace(/^[ ]+/gm, ""), ); const iterator = stream[Symbol.asyncIterator](); @@ -1621,7 +1550,7 @@ describe("Replicate client", () => { id: EVENT_2 data: {} - `.replace(/^[ ]+/gm, "") + `.replace(/^[ ]+/gm, ""), ); const iterator = stream[Symbol.asyncIterator](); @@ -1653,7 +1582,7 @@ describe("Replicate client", () => { id: EVENT_2 data: {} - `.replace(/^[ ]+/gm, "") + `.replace(/^[ ]+/gm, ""), ); const iterator = stream[Symbol.asyncIterator](); @@ -1774,7 +1703,7 @@ describe("Replicate client", () => { id: EVENT_1 data: hello world - `.replace(/^[ ]+/gm, "") + `.replace(/^[ ]+/gm, ""), ); const iterator = stream[Symbol.asyncIterator](); @@ -1796,7 +1725,7 @@ describe("Replicate client", () => { id: EVENT_2 data: An unexpected error occurred - `.replace(/^[ ]+/gm, "") + `.replace(/^[ ]+/gm, ""), ); const iterator = stream[Symbol.asyncIterator](); @@ -1804,9 +1733,7 @@ describe("Replicate client", () => { done: false, value: { event: "output", id: "EVENT_1", data: "hello world" }, }); - await expect(iterator.next()).rejects.toThrowError( - "An unexpected error occurred" - ); + await expect(iterator.next()).rejects.toThrowError("An unexpected error occurred"); expect(await iterator.next()).toEqual({ done: true }); }); @@ -1814,7 +1741,7 @@ describe("Replicate client", () => { const stream = createStream("{}", 500); const iterator = stream[Symbol.asyncIterator](); await expect(iterator.next()).rejects.toThrowError( - "Request to https://stream.replicate.com/fake_stream failed with status 500" + "Request to https://stream.replicate.com/fake_stream failed with status 500", ); expect(await iterator.next()).toEqual({ done: true }); }); diff --git a/lib/util.js b/lib/util.js index 3745d9f0..b4483aed 100644 --- a/lib/util.js +++ b/lib/util.js @@ -67,18 +67,11 @@ async function validateWebhook(requestData, secret) { const signedContent = `${id}.${timestamp}.${body}`; - const computedSignature = await createHMACSHA256( - signingSecret.split("_").pop(), - signedContent - ); + const computedSignature = await createHMACSHA256(signingSecret.split("_").pop(), signedContent); - const expectedSignatures = signature - .split(" ") - .map((sig) => sig.split(",")[1]); + const expectedSignatures = signature.split(" ").map((sig) => sig.split(",")[1]); - return expectedSignatures.some( - (expectedSignature) => expectedSignature === computedSignature - ); + return expectedSignatures.some((expectedSignature) => expectedSignature === computedSignature); } /** @@ -105,13 +98,9 @@ async function createHMACSHA256(secret, data) { crypto = require.call(null, "node:crypto").webcrypto; } - const key = await crypto.subtle.importKey( - "raw", - base64ToBytes(secret), - { name: "HMAC", hash: "SHA-256" }, - false, - ["sign"] - ); + const key = await crypto.subtle.importKey("raw", base64ToBytes(secret), { name: "HMAC", hash: "SHA-256" }, false, [ + "sign", + ]); const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(data)); return bytesToBase64(signature); @@ -235,6 +224,9 @@ async function transformFileInputs(client, inputs, strategy) { try { return await transformFileInputsToReplicateFileURLs(client, inputs); } catch (error) { + if (error instanceof ApiError && error.response.status >= 400 && error.response.status < 500) { + throw error; + } return await transformFileInputsToBase64EncodedDataURIs(inputs); } default: @@ -296,7 +288,7 @@ async function transformFileInputsToBase64EncodedDataURIs(inputs) { totalBytes += buffer.byteLength; if (totalBytes > MAX_DATA_URI_SIZE) { throw new Error( - `Combined filesize of prediction ${totalBytes} bytes exceeds 10mb limit for inline encoding, please provide URLs instead` + `Combined filesize of prediction ${totalBytes} bytes exceeds 10mb limit for inline encoding, please provide URLs instead`, ); } @@ -354,14 +346,11 @@ function isPlainObject(value) { if (proto === null) { return true; } - const Ctor = - Object.prototype.hasOwnProperty.call(proto, "constructor") && - proto.constructor; + const Ctor = Object.prototype.hasOwnProperty.call(proto, "constructor") && proto.constructor; return ( typeof Ctor === "function" && Ctor instanceof Ctor && - Function.prototype.toString.call(Ctor) === - Function.prototype.toString.call(Object) + Function.prototype.toString.call(Ctor) === Function.prototype.toString.call(Object) ); } From 4c04a57dcaab4ed1d74673ed74bf3ef33ab5b90c Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 5 Jul 2024 11:16:21 -0700 Subject: [PATCH 156/207] 0.31.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c6906dc8..7c454426 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.31.0", + "version": "0.31.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.31.0", + "version": "0.31.1", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 9d9b60bf..7e13d359 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.31.0", + "version": "0.31.1", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 2fd28906405b6caa82421314d93a3b5e77a84bff Mon Sep 17 00:00:00 2001 From: Mattt Date: Fri, 19 Jul 2024 10:41:30 -0700 Subject: [PATCH 157/207] Pin to Node 22.4 in CI workflow (#293) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27a982cb..a0a2241c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: matrix: suite: [node] # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases - node-version: [18.x, 20.x, 22.x] + node-version: [18.x, 20.x, 22.4] # TODO: unpin to 22.x once https://github.com/nodejs/node/issues/53902 is resolved steps: - uses: actions/checkout@v4 From 9702c17d9e0da4afcaaa038471d58e7d9c60e634 Mon Sep 17 00:00:00 2001 From: Mattt Date: Thu, 25 Jul 2024 12:14:00 -0700 Subject: [PATCH 158/207] Revert "Pin to Node 22.4 in CI workflow (#293)" (#294) This reverts commit 2fd28906405b6caa82421314d93a3b5e77a84bff. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0a2241c..27a982cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: matrix: suite: [node] # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases - node-version: [18.x, 20.x, 22.4] # TODO: unpin to 22.x once https://github.com/nodejs/node/issues/53902 is resolved + node-version: [18.x, 20.x, 22.x] steps: - uses: actions/checkout@v4 From cdd0c9883e7996bb5127c52c0d0988f5d93f6655 Mon Sep 17 00:00:00 2001 From: Mattt Date: Thu, 25 Jul 2024 12:15:32 -0700 Subject: [PATCH 159/207] Add support for models.search endpoint (#292) --- README.md | 12 ++ index.d.ts | 1 + index.js | 1 + index.test.ts | 445 +++++++++++++++++++++++++++++++++----------------- lib/models.js | 19 +++ 5 files changed, 327 insertions(+), 151 deletions(-) diff --git a/README.md b/README.md index 8f4350b1..dabee2ee 100644 --- a/README.md +++ b/README.md @@ -455,6 +455,18 @@ const response = await replicate.models.list(); } ``` +### `replicate.models.search` + +Search for public models on Replicate. + +```js +const response = await replicate.models.search(query); +``` + +| name | type | description | +| ------- | ------ | -------------------------------------- | +| `query` | string | **Required**. The search query string. | + ### `replicate.models.create` Create a new public or private model. diff --git a/index.d.ts b/index.d.ts index 7d4ef0a9..25cd51af 100644 --- a/index.d.ts +++ b/index.d.ts @@ -281,6 +281,7 @@ declare module "replicate" { version_id: string ): Promise; }; + search(query: string): Promise>; }; predictions: { diff --git a/index.js b/index.js index 21e83f95..6b35db65 100644 --- a/index.js +++ b/index.js @@ -98,6 +98,7 @@ class Replicate { list: models.versions.list.bind(this), get: models.versions.get.bind(this), }, + search: models.search.bind(this), }; this.predictions = { diff --git a/index.test.ts b/index.test.ts index c4d7e067..7f9fcf2f 100644 --- a/index.test.ts +++ b/index.test.ts @@ -1,5 +1,11 @@ import { expect, jest, test } from "@jest/globals"; -import Replicate, { ApiError, Model, Prediction, validateWebhook, parseProgressFromLogs } from "replicate"; +import Replicate, { + ApiError, + Model, + Prediction, + validateWebhook, + parseProgressFromLogs, +} from "replicate"; import nock from "nock"; import { Readable } from "node:stream"; import { createReadableStream } from "./lib/stream"; @@ -36,7 +42,8 @@ const fileTestCases = [ describe("Replicate client", () => { let unmatched: any[] = []; - const handleNoMatch = (req: unknown, options: any, body: string) => unmatched.push({ req, options, body }); + const handleNoMatch = (req: unknown, options: any, body: string) => + unmatched.push({ req, options, body }); beforeEach(() => { client = new Replicate({ auth: "test-token" }); @@ -116,7 +123,8 @@ describe("Replicate client", () => { { name: "Super resolution", slug: "super-resolution", - description: "Upscaling models that create high-quality images from low-quality images.", + description: + "Upscaling models that create high-quality images from low-quality images.", }, { name: "Image classification", @@ -139,7 +147,8 @@ describe("Replicate client", () => { nock(BASE_URL).get("/collections/super-resolution").reply(200, { name: "Super resolution", slug: "super-resolution", - description: "Upscaling models that create high-quality images from low-quality images.", + description: + "Upscaling models that create high-quality images from low-quality images.", models: [], }); @@ -179,7 +188,9 @@ describe("Replicate client", () => { results: [{ url: "https://replicate.com/some-user/model-1" }], next: "https://api.replicate.com/v1/models?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw", }) - .get("/models?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw") + .get( + "/models?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw" + ) .reply(200, { results: [{ url: "https://replicate.com/some-user/model-2" }], next: null, @@ -237,10 +248,12 @@ describe("Replicate client", () => { expectedResponse: { id: "ufawqhfynnddngldkgtslldrkq", model: "replicate/hello-world", - version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", urls: { get: "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", - cancel: "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel", + cancel: + "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel", }, input: testCase.input, created_at: "2022-04-26T22:13:06.224088Z", @@ -250,64 +263,79 @@ describe("Replicate client", () => { }, })); - test.each(predictionTestCases)("$description", async ({ input, expectedResponse }) => { - nock(BASE_URL) - .post("/predictions", { - version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + test.each(predictionTestCases)( + "$description", + async ({ input, expectedResponse }) => { + nock(BASE_URL) + .post("/predictions", { + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + input: input as Record, + webhook: "http://test.host/webhook", + webhook_events_filter: ["output", "completed"], + }) + .reply(200, expectedResponse); + + const response = await client.predictions.create({ + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: input as Record, webhook: "http://test.host/webhook", webhook_events_filter: ["output", "completed"], - }) - .reply(200, expectedResponse); + }); - const response = await client.predictions.create({ - version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", - input: input as Record, - webhook: "http://test.host/webhook", - webhook_events_filter: ["output", "completed"], - }); + expect(response.input).toEqual(input); + expect(response.status).toBe(expectedResponse.status); + } + ); - expect(response.input).toEqual(input); - expect(response.status).toBe(expectedResponse.status); - }); + test.each(fileTestCases)( + "converts a $type input into a Replicate file URL", + async ({ value: data, type }) => { + const mockedFetch = jest.spyOn(client, "fetch"); - test.each(fileTestCases)("converts a $type input into a Replicate file URL", async ({ value: data, type }) => { - const mockedFetch = jest.spyOn(client, "fetch"); + nock(BASE_URL) + .post("/files") + .reply(201, { + urls: { + get: "https://replicate.com/api/files/123", + }, + }) + .post( + "/predictions", + (body) => body.input.data === "https://replicate.com/api/files/123" + ) + .reply(201, (_uri: string, body: Record) => { + return body; + }); - nock(BASE_URL) - .post("/files") - .reply(201, { - urls: { - get: "https://replicate.com/api/files/123", + const prediction = await client.predictions.create({ + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + input: { + prompt: "Tell me a story", + data, }, - }) - .post("/predictions", (body) => body.input.data === "https://replicate.com/api/files/123") - .reply(201, (_uri: string, body: Record) => { - return body; }); - const prediction = await client.predictions.create({ - version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", - input: { - prompt: "Tell me a story", - data, - }, - }); - - expect(client.fetch).toHaveBeenCalledWith(new URL("https://api.replicate.com/v1/files"), { - method: "POST", - body: expect.any(FormData), - headers: expect.any(Object), - }); - const form = mockedFetch.mock.calls[0][1]?.body as FormData; - // @ts-ignore - expect(form?.get("content")?.name).toMatch(new RegExp(`^${type}_`)); + expect(client.fetch).toHaveBeenCalledWith( + new URL("https://api.replicate.com/v1/files"), + { + method: "POST", + body: expect.any(FormData), + headers: expect.any(Object), + } + ); + const form = mockedFetch.mock.calls[0][1]?.body as FormData; + // @ts-ignore + expect(form?.get("content")?.name).toMatch(new RegExp(`^${type}_`)); - expect(prediction.input).toEqual({ - prompt: "Tell me a story", - data: "https://replicate.com/api/files/123", - }); - }); + expect(prediction.input).toEqual({ + prompt: "Tell me a story", + data: "https://replicate.com/api/files/123", + }); + } + ); test.each(fileTestCases)( "converts a $type input into a base64 encoded string", @@ -323,7 +351,8 @@ describe("Replicate client", () => { }); await client.predictions.create({ - version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { prompt: "Tell me a story", data, @@ -332,7 +361,7 @@ describe("Replicate client", () => { }); expect(actual?.input.data).toEqual(expected); - }, + } ); test.each(fileTestCases)( @@ -350,7 +379,8 @@ describe("Replicate client", () => { await expect(async () => { await client.predictions.create({ - version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { prompt: "Tell me a story", data, @@ -361,9 +391,9 @@ describe("Replicate client", () => { expect.objectContaining({ name: "ApiError", message: expect.stringContaining("401"), - }), + }) ); - }, + } ); test("Passes stream parameter to API endpoint", async () => { @@ -375,7 +405,8 @@ describe("Replicate client", () => { }); await client.predictions.create({ - version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { prompt: "Tell me a story", }, @@ -386,7 +417,8 @@ describe("Replicate client", () => { test("Throws an error if webhook URL is invalid", async () => { await expect(async () => { await client.predictions.create({ - version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { text: "Alice", }, @@ -402,14 +434,15 @@ describe("Replicate client", () => { status: 400, detail: "Invalid input", }, - { "Content-Type": "application/json" }, + { "Content-Type": "application/json" } ); try { expect.hasAssertions(); await client.predictions.create({ - version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { text: null, }, @@ -428,14 +461,15 @@ describe("Replicate client", () => { { detail: "Too many requests", }, - { "Content-Type": "application/json", "Retry-After": "1" }, + { "Content-Type": "application/json", "Retry-After": "1" } ) .post("/predictions") .reply(201, { id: "ufawqhfynnddngldkgtslldrkq", }); const prediction = await client.predictions.create({ - version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { text: "Alice", }, @@ -449,18 +483,19 @@ describe("Replicate client", () => { { detail: "Internal server error", }, - { "Content-Type": "application/json" }, + { "Content-Type": "application/json" } ); await expect( client.predictions.create({ - version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { text: "Alice", }, - }), + }) ).rejects.toThrow( - `Request to https://api.replicate.com/v1/predictions failed with status 500 Internal Server Error: {"detail":"Internal server error"}.`, + `Request to https://api.replicate.com/v1/predictions failed with status 500 Internal Server Error: {"detail":"Internal server error"}.` ); }); }); @@ -472,10 +507,12 @@ describe("Replicate client", () => { .reply(200, { id: "rrr4z55ocneqzikepnug6xezpe", model: "stability-ai/stable-diffusion", - version: "be04660a5b93ef2aff61e3668dedb4cbeb14941e62a3fd5998364a32d613e35e", + version: + "be04660a5b93ef2aff61e3668dedb4cbeb14941e62a3fd5998364a32d613e35e", urls: { get: "https://api.replicate.com/v1/predictions/rrr4z55ocneqzikepnug6xezpe", - cancel: "https://api.replicate.com/v1/predictions/rrr4z55ocneqzikepnug6xezpe/cancel", + cancel: + "https://api.replicate.com/v1/predictions/rrr4z55ocneqzikepnug6xezpe/cancel", }, created_at: "2022-09-13T22:54:18.578761Z", started_at: "2022-09-13T22:54:19.438525Z", @@ -494,7 +531,9 @@ describe("Replicate client", () => { predict_time: 4.484541, }, }); - const prediction = await client.predictions.get("rrr4z55ocneqzikepnug6xezpe"); + const prediction = await client.predictions.get( + "rrr4z55ocneqzikepnug6xezpe" + ); expect(prediction.id).toBe("rrr4z55ocneqzikepnug6xezpe"); }); @@ -506,14 +545,16 @@ describe("Replicate client", () => { { detail: "Too many requests", }, - { "Content-Type": "application/json", "Retry-After": "1" }, + { "Content-Type": "application/json", "Retry-After": "1" } ) .get("/predictions/rrr4z55ocneqzikepnug6xezpe") .reply(200, { id: "rrr4z55ocneqzikepnug6xezpe", }); - const prediction = await client.predictions.get("rrr4z55ocneqzikepnug6xezpe"); + const prediction = await client.predictions.get( + "rrr4z55ocneqzikepnug6xezpe" + ); expect(prediction.id).toBe("rrr4z55ocneqzikepnug6xezpe"); }); @@ -525,14 +566,16 @@ describe("Replicate client", () => { { detail: "Internal server error", }, - { "Content-Type": "application/json" }, + { "Content-Type": "application/json" } ) .get("/predictions/rrr4z55ocneqzikepnug6xezpe") .reply(200, { id: "rrr4z55ocneqzikepnug6xezpe", }); - const prediction = await client.predictions.get("rrr4z55ocneqzikepnug6xezpe"); + const prediction = await client.predictions.get( + "rrr4z55ocneqzikepnug6xezpe" + ); expect(prediction.id).toBe("rrr4z55ocneqzikepnug6xezpe"); }); }); @@ -544,10 +587,12 @@ describe("Replicate client", () => { .reply(200, { id: "ufawqhfynnddngldkgtslldrkq", model: "replicate/hello-world", - version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", urls: { get: "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", - cancel: "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel", + cancel: + "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel", }, created_at: "2022-04-26T22:13:06.224088Z", started_at: "2022-04-26T22:13:06.224088Z", @@ -562,7 +607,9 @@ describe("Replicate client", () => { metrics: {}, }); - const prediction = await client.predictions.cancel("ufawqhfynnddngldkgtslldrkq"); + const prediction = await client.predictions.cancel( + "ufawqhfynnddngldkgtslldrkq" + ); expect(prediction.status).toBe("canceled"); }); @@ -580,10 +627,12 @@ describe("Replicate client", () => { { id: "jpzd7hm5gfcapbfyt4mqytarku", model: "stability-ai/stable-diffusion", - version: "b21cbe271e65c1718f2999b038c18b45e21e4fba961181fbfae9342fc53b9e05", + version: + "b21cbe271e65c1718f2999b038c18b45e21e4fba961181fbfae9342fc53b9e05", urls: { get: "https://api.replicate.com/v1/predictions/jpzd7hm5gfcapbfyt4mqytarku", - cancel: "https://api.replicate.com/v1/predictions/jpzd7hm5gfcapbfyt4mqytarku/cancel", + cancel: + "https://api.replicate.com/v1/predictions/jpzd7hm5gfcapbfyt4mqytarku/cancel", }, created_at: "2022-04-26T20:00:40.658234Z", started_at: "2022-04-26T20:00:84.583803Z", @@ -606,7 +655,9 @@ describe("Replicate client", () => { results: [{ id: "ufawqhfynnddngldkgtslldrkq" }], next: "https://api.replicate.com/v1/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw", }) - .get("/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw") + .get( + "/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw" + ) .reply(200, { results: [{ id: "rrr4z55ocneqzikepnug6xezpe" }], next: null, @@ -616,7 +667,10 @@ describe("Replicate client", () => { for await (const batch of client.paginate(client.predictions.list)) { results.push(...batch); } - expect(results).toEqual([{ id: "ufawqhfynnddngldkgtslldrkq" }, { id: "rrr4z55ocneqzikepnug6xezpe" }]); + expect(results).toEqual([ + { id: "ufawqhfynnddngldkgtslldrkq" }, + { id: "rrr4z55ocneqzikepnug6xezpe" }, + ]); // Add more tests for error handling, edge cases, etc. }); @@ -625,10 +679,13 @@ describe("Replicate client", () => { describe("trainings.create", () => { test("Calls the correct API route with the correct payload", async () => { nock(BASE_URL) - .post("/models/owner/model/versions/632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532/trainings") + .post( + "/models/owner/model/versions/632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532/trainings" + ) .reply(200, { id: "zz4ibbonubfz7carwiefibzgga", - version: "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", + version: + "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", status: "starting", input: { text: "...", @@ -650,20 +707,25 @@ describe("Replicate client", () => { input: { text: "...", }, - }, + } ); expect(training.id).toBe("zz4ibbonubfz7carwiefibzgga"); }); test("Throws an error if webhook is not a valid URL", async () => { await expect( - client.trainings.create("owner", "model", "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", { - destination: "new_owner/new_model", - input: { - text: "...", - }, - webhook: "invalid-url", - }), + client.trainings.create( + "owner", + "model", + "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", + { + destination: "new_owner/new_model", + input: { + text: "...", + }, + webhook: "invalid-url", + } + ) ).rejects.toThrow("Invalid webhook URL"); }); @@ -723,7 +785,9 @@ describe("Replicate client", () => { completed_at: null, }); - const training = await client.trainings.cancel("zz4ibbonubfz7carwiefibzgga"); + const training = await client.trainings.cancel( + "zz4ibbonubfz7carwiefibzgga" + ); expect(training.status).toBe("canceled"); }); @@ -741,10 +805,12 @@ describe("Replicate client", () => { { id: "jpzd7hm5gfcapbfyt4mqytarku", model: "stability-ai/sdxl", - version: "b21cbe271e65c1718f2999b038c18b45e21e4fba961181fbfae9342fc53b9e05", + version: + "b21cbe271e65c1718f2999b038c18b45e21e4fba961181fbfae9342fc53b9e05", urls: { get: "https://api.replicate.com/v1/trainings/jpzd7hm5gfcapbfyt4mqytarku", - cancel: "https://api.replicate.com/v1/trainings/jpzd7hm5gfcapbfyt4mqytarku/cancel", + cancel: + "https://api.replicate.com/v1/trainings/jpzd7hm5gfcapbfyt4mqytarku/cancel", }, created_at: "2022-04-26T20:00:40.658234Z", started_at: "2022-04-26T20:00:84.583803Z", @@ -767,7 +833,9 @@ describe("Replicate client", () => { results: [{ id: "ufawqhfynnddngldkgtslldrkq" }], next: "https://api.replicate.com/v1/trainings?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw", }) - .get("/trainings?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw") + .get( + "/trainings?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw" + ) .reply(200, { results: [{ id: "rrr4z55ocneqzikepnug6xezpe" }], next: null, @@ -777,7 +845,10 @@ describe("Replicate client", () => { for await (const batch of client.paginate(client.trainings.list)) { results.push(...batch); } - expect(results).toEqual([{ id: "ufawqhfynnddngldkgtslldrkq" }, { id: "rrr4z55ocneqzikepnug6xezpe" }]); + expect(results).toEqual([ + { id: "ufawqhfynnddngldkgtslldrkq" }, + { id: "rrr4z55ocneqzikepnug6xezpe" }, + ]); // Add more tests for error handling, edge cases, etc. }); @@ -790,10 +861,12 @@ describe("Replicate client", () => { .reply(200, { id: "mfrgcyzzme2wkmbwgzrgmntcg", model: "replicate/hello-world", - version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", urls: { get: "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", - cancel: "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel", + cancel: + "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel", }, created_at: "2022-09-10T09:44:22.165836Z", started_at: null, @@ -807,13 +880,17 @@ describe("Replicate client", () => { logs: null, metrics: {}, }); - const prediction = await client.deployments.predictions.create("replicate", "greeter", { - input: { - text: "Alice", - }, - webhook: "http://test.host/webhook", - webhook_events_filter: ["output", "completed"], - }); + const prediction = await client.deployments.predictions.create( + "replicate", + "greeter", + { + input: { + text: "Alice", + }, + webhook: "http://test.host/webhook", + webhook_events_filter: ["output", "completed"], + } + ); expect(prediction.id).toBe("mfrgcyzzme2wkmbwgzrgmntcg"); }); // Add more tests for error handling, edge cases, etc. @@ -829,7 +906,8 @@ describe("Replicate client", () => { current_release: { number: 1, model: "stability-ai/sdxl", - version: "da77bc59ee60423279fd632efb4795ab731d9e3ca9705ef3341091fb989b7eaf", + version: + "da77bc59ee60423279fd632efb4795ab731d9e3ca9705ef3341091fb989b7eaf", created_at: "2024-02-15T16:32:57.018467Z", created_by: { type: "organization", @@ -845,7 +923,10 @@ describe("Replicate client", () => { }, }); - const deployment = await client.deployments.get("acme", "my-app-image-generator"); + const deployment = await client.deployments.get( + "acme", + "my-app-image-generator" + ); expect(deployment.owner).toBe("acme"); expect(deployment.name).toBe("my-app-image-generator"); @@ -864,7 +945,8 @@ describe("Replicate client", () => { current_release: { number: 1, model: "stability-ai/sdxl", - version: "da77bc59ee60423279fd632efb4795ab731d9e3ca9705ef3341091fb989b7eaf", + version: + "da77bc59ee60423279fd632efb4795ab731d9e3ca9705ef3341091fb989b7eaf", created_at: "2024-02-15T16:32:57.018467Z", created_by: { type: "organization", @@ -883,7 +965,8 @@ describe("Replicate client", () => { const deployment = await client.deployments.create({ name: "my-app-image-generator", model: "stability-ai/sdxl", - version: "da77bc59ee60423279fd632efb4795ab731d9e3ca9705ef3341091fb989b7eaf", + version: + "da77bc59ee60423279fd632efb4795ab731d9e3ca9705ef3341091fb989b7eaf", hardware: "gpu-t4", min_instances: 1, max_instances: 5, @@ -906,7 +989,8 @@ describe("Replicate client", () => { current_release: { number: 2, model: "stability-ai/sdxl", - version: "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", + version: + "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", created_at: "2024-02-16T08:14:22.345678Z", created_by: { type: "organization", @@ -922,18 +1006,25 @@ describe("Replicate client", () => { }, }); - const deployment = await client.deployments.update("acme", "my-app-image-generator", { - version: "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", - hardware: "gpu-a40-large", - min_instances: 3, - max_instances: 10, - }); + const deployment = await client.deployments.update( + "acme", + "my-app-image-generator", + { + version: + "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", + hardware: "gpu-a40-large", + min_instances: 3, + max_instances: 10, + } + ); expect(deployment.current_release.number).toBe(2); expect(deployment.current_release.version).toBe( - "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532", + "632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532" + ); + expect(deployment.current_release.configuration.hardware).toBe( + "gpu-a40-large" ); - expect(deployment.current_release.configuration.hardware).toBe("gpu-a40-large"); expect(deployment.current_release.configuration.min_instances).toBe(3); expect(deployment.current_release.configuration.max_instances).toBe(10); }); @@ -942,9 +1033,14 @@ describe("Replicate client", () => { describe("deployments.delete", () => { test("Calls the correct API route with the correct payload", async () => { - nock(BASE_URL).delete("/deployments/acme/my-app-image-generator").reply(204); + nock(BASE_URL) + .delete("/deployments/acme/my-app-image-generator") + .reply(204); - const success = await client.deployments.delete("acme", "my-app-image-generator"); + const success = await client.deployments.delete( + "acme", + "my-app-image-generator" + ); expect(success).toBe(true); }); }); @@ -977,7 +1073,7 @@ describe("Replicate client", () => { describe("predictions.create with model", () => { test("Calls the correct API route with the correct payload", async () => { nock(BASE_URL) - .post("/models/meta/llama-2-70b-chat/predictions") + .post("/models/meta/meta-llama-3-70b-instruct/predictions") .reply(200, { id: "heat2o3bzn3ahtr6bjfftvbaci", model: "replicate/lifeboat-70b", @@ -990,12 +1086,13 @@ describe("Replicate client", () => { status: "starting", created_at: "2023-11-27T13:35:45.99397566Z", urls: { - cancel: "https://api.replicate.com/v1/predictions/heat2o3bzn3ahtr6bjfftvbaci/cancel", + cancel: + "https://api.replicate.com/v1/predictions/heat2o3bzn3ahtr6bjfftvbaci/cancel", get: "https://api.replicate.com/v1/predictions/heat2o3bzn3ahtr6bjfftvbaci", }, }); const prediction = await client.predictions.create({ - model: "meta/llama-2-70b-chat", + model: "meta/meta-llama-3-70b-instruct", input: { prompt: "Please write a haiku about llamas.", }, @@ -1140,6 +1237,44 @@ describe("Replicate client", () => { }); }); + describe("models.search", () => { + test("Calls the correct API route with the correct payload", async () => { + nock(BASE_URL) + .intercept("/models", "QUERY") + .reply(200, { + results: [ + { + url: "https://replicate.com/meta/meta-llama-3-70b-instruct", + owner: "meta", + name: "meta-llama-3-70b-instruct", + description: + "Llama 2 is a collection of pretrained and fine-tuned generative text models ranging in scale from 7 billion to 70 billion parameters.", + visibility: "public", + github_url: null, + paper_url: + "https://ai.meta.com/research/publications/llama-2-open-foundation-and-fine-tuned-chat-models/", + license_url: "https://ai.meta.com/llama/license/", + run_count: 1000000, + cover_image_url: + "https://replicate.delivery/pbxt/IJqFrnAKEDiCBnlXyndzVVxkZvfQ7kLjGVEZZPXTRXxOOPkQA/llama2.png", + default_example: null, + latest_version: null, + }, + // ... more results ... + ], + next: null, + previous: null, + }); + + const searchResults = await client.models.search("llama"); + expect(searchResults.results.length).toBeGreaterThan(0); + expect(searchResults.results[0].owner).toBe("meta"); + expect(searchResults.results[0].name).toBe("meta-llama-3-70b-instruct"); + }); + + // Add more tests for error handling, edge cases, etc. + }); + describe("run", () => { test("Calls the correct API routes", async () => { nock(BASE_URL) @@ -1200,7 +1335,7 @@ describe("Replicate client", () => { (prediction) => { const progress = parseProgressFromLogs(prediction); callback(prediction, progress); - }, + } ); expect(output).toBe("Goodbye!"); @@ -1212,7 +1347,7 @@ describe("Replicate client", () => { status: "starting", logs: null, }, - null, + null ); expect(callback).toHaveBeenNthCalledWith( @@ -1226,7 +1361,7 @@ describe("Replicate client", () => { percentage: 0.4, current: 2, total: 5, - }, + } ); expect(callback).toHaveBeenNthCalledWith( @@ -1240,7 +1375,7 @@ describe("Replicate client", () => { percentage: 0.8, current: 4, total: 5, - }, + } ); expect(callback).toHaveBeenNthCalledWith( @@ -1255,7 +1390,7 @@ describe("Replicate client", () => { percentage: 1.0, current: 5, total: 5, - }, + } ); expect(callback).toHaveBeenCalledTimes(4); @@ -1289,7 +1424,7 @@ describe("Replicate client", () => { input: { text: "Hello, world!" }, wait: { interval: 1 }, }, - progress, + progress ); expect(output).toBe("Goodbye!"); @@ -1332,7 +1467,9 @@ describe("Replicate client", () => { output: "foobar", }); - await expect(client.run("a/b-1.0:abc123", { input: { text: "Hello, world!" } })).resolves.not.toThrow(); + await expect( + client.run("a/b-1.0:abc123", { input: { text: "Hello, world!" } }) + ).resolves.not.toThrow(); }); test("Throws an error for invalid identifiers", async () => { @@ -1349,12 +1486,15 @@ describe("Replicate client", () => { test("Throws an error if webhook URL is invalid", async () => { await expect(async () => { - await client.run("owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", { - input: { - text: "Alice", - }, - webhook: "invalid-url", - }); + await client.run( + "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + { + input: { + text: "Alice", + }, + webhook: "invalid-url", + } + ); }).rejects.toThrow("Invalid webhook URL"); }); @@ -1393,7 +1533,7 @@ describe("Replicate client", () => { input: { text: "Hello, world!" }, signal, }, - onProgress, + onProgress ); expect(body).toBeDefined(); @@ -1405,19 +1545,19 @@ describe("Replicate client", () => { 1, expect.objectContaining({ status: "processing", - }), + }) ); expect(onProgress).toHaveBeenNthCalledWith( 2, expect.objectContaining({ status: "processing", - }), + }) ); expect(onProgress).toHaveBeenNthCalledWith( 3, expect.objectContaining({ status: "canceled", - }), + }) ); scope.done(); @@ -1442,7 +1582,8 @@ describe("Replicate client", () => { "Content-Type": "application/json", "Webhook-ID": "msg_p5jXN8AQM9LWM0D4loKWxJek", "Webhook-Timestamp": "1614265330", - "Webhook-Signature": "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=", + "Webhook-Signature": + "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=", }, body: `{"test": 2432232314}`, }); @@ -1485,7 +1626,7 @@ describe("Replicate client", () => { id: EVENT_2 data: {} - `.replace(/^[ ]+/gm, ""), + `.replace(/^[ ]+/gm, "") ); const iterator = stream[Symbol.asyncIterator](); @@ -1516,7 +1657,7 @@ describe("Replicate client", () => { id: EVENT_3 data: {} - `.replace(/^[ ]+/gm, ""), + `.replace(/^[ ]+/gm, "") ); const iterator = stream[Symbol.asyncIterator](); @@ -1550,7 +1691,7 @@ describe("Replicate client", () => { id: EVENT_2 data: {} - `.replace(/^[ ]+/gm, ""), + `.replace(/^[ ]+/gm, "") ); const iterator = stream[Symbol.asyncIterator](); @@ -1582,7 +1723,7 @@ describe("Replicate client", () => { id: EVENT_2 data: {} - `.replace(/^[ ]+/gm, ""), + `.replace(/^[ ]+/gm, "") ); const iterator = stream[Symbol.asyncIterator](); @@ -1703,7 +1844,7 @@ describe("Replicate client", () => { id: EVENT_1 data: hello world - `.replace(/^[ ]+/gm, ""), + `.replace(/^[ ]+/gm, "") ); const iterator = stream[Symbol.asyncIterator](); @@ -1725,7 +1866,7 @@ describe("Replicate client", () => { id: EVENT_2 data: An unexpected error occurred - `.replace(/^[ ]+/gm, ""), + `.replace(/^[ ]+/gm, "") ); const iterator = stream[Symbol.asyncIterator](); @@ -1733,7 +1874,9 @@ describe("Replicate client", () => { done: false, value: { event: "output", id: "EVENT_1", data: "hello world" }, }); - await expect(iterator.next()).rejects.toThrowError("An unexpected error occurred"); + await expect(iterator.next()).rejects.toThrowError( + "An unexpected error occurred" + ); expect(await iterator.next()).toEqual({ done: true }); }); @@ -1741,7 +1884,7 @@ describe("Replicate client", () => { const stream = createStream("{}", 500); const iterator = stream[Symbol.asyncIterator](); await expect(iterator.next()).rejects.toThrowError( - "Request to https://stream.replicate.com/fake_stream failed with status 500", + "Request to https://stream.replicate.com/fake_stream failed with status 500" ); expect(await iterator.next()).toEqual({ done: true }); }); diff --git a/lib/models.js b/lib/models.js index c6a02fca..272d9edb 100644 --- a/lib/models.js +++ b/lib/models.js @@ -89,9 +89,28 @@ async function createModel(model_owner, model_name, options) { return response.json(); } +/** + * Search for public models + * + * @param {string} query - The search query + * @returns {Promise} Resolves with a page of models matching the search query + */ +async function search(query) { + const response = await this.request("/models", { + method: "QUERY", + headers: { + "Content-Type": "text/plain", + }, + data: query, + }); + + return response.json(); +} + module.exports = { get: getModel, list: listModels, create: createModel, versions: { list: listModelVersions, get: getModelVersion }, + search, }; From ac75077c4b5e126798e9b8e820476ca58f7961a5 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Thu, 25 Jul 2024 12:20:51 -0700 Subject: [PATCH 160/207] 0.32.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7c454426..cdea341f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.31.1", + "version": "0.32.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.31.1", + "version": "0.32.0", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 7e13d359..ae45856e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.31.1", + "version": "0.32.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 0cf57c4ca6de9913abdb6e9ebeef165438d41b5d Mon Sep 17 00:00:00 2001 From: Jake Dahn Date: Thu, 29 Aug 2024 06:52:31 -0600 Subject: [PATCH 161/207] Fix `validateWebhook` backwards incompatibility with older Node.js (#298) * Use backward compatible syntax for accessing object keys on requestData.headers * Stringifying requestData.body when it is of type object. This is backward compatibility for Next.js 12.1.x / Node.js v18 --- lib/util.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/util.js b/lib/util.js index b4483aed..a36c4804 100644 --- a/lib/util.js +++ b/lib/util.js @@ -35,9 +35,15 @@ async function validateWebhook(requestData, secret) { const signingSecret = secret || requestData.secret; if (requestData && requestData.headers && requestData.body) { - id = requestData.headers.get("webhook-id"); - timestamp = requestData.headers.get("webhook-timestamp"); - signature = requestData.headers.get("webhook-signature"); + id = + requestData.headers["webhook-id"] || + requestData.headers.get?.("webhook-id"); + timestamp = + requestData.headers["webhook-timestamp"] || + requestData.headers.get?.("webhook-timestamp"); + signature = + requestData.headers["webhook-signature"] || + requestData.headers.get?.("webhook-signature"); body = requestData.body; } @@ -49,6 +55,8 @@ async function validateWebhook(requestData, secret) { } } else if (isTypedArray(body)) { body = await new Blob([body]).text(); + } else if (typeof body === "object") { + body = JSON.stringify(body); } else if (typeof body !== "string") { throw new Error("Invalid body type"); } From 79ebc5776c272040443535a052673dcfe448477c Mon Sep 17 00:00:00 2001 From: Mattt Date: Thu, 29 Aug 2024 06:02:47 -0700 Subject: [PATCH 162/207] Handle Fetch Headers and subscriptable JS object separately (#300) --- lib/util.js | 58 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/lib/util.js b/lib/util.js index a36c4804..daecfd15 100644 --- a/lib/util.js +++ b/lib/util.js @@ -35,15 +35,17 @@ async function validateWebhook(requestData, secret) { const signingSecret = secret || requestData.secret; if (requestData && requestData.headers && requestData.body) { - id = - requestData.headers["webhook-id"] || - requestData.headers.get?.("webhook-id"); - timestamp = - requestData.headers["webhook-timestamp"] || - requestData.headers.get?.("webhook-timestamp"); - signature = - requestData.headers["webhook-signature"] || - requestData.headers.get?.("webhook-signature"); + if (typeof requestData.headers.get === "function") { + // Headers object (e.g. Fetch API Headers) + id = requestData.headers.get("webhook-id"); + timestamp = requestData.headers.get("webhook-timestamp"); + signature = requestData.headers.get("webhook-signature"); + } else { + // Plain object with header key-value pairs + id = requestData.headers["webhook-id"]; + timestamp = requestData.headers["webhook-timestamp"]; + signature = requestData.headers["webhook-signature"]; + } body = requestData.body; } @@ -75,11 +77,18 @@ async function validateWebhook(requestData, secret) { const signedContent = `${id}.${timestamp}.${body}`; - const computedSignature = await createHMACSHA256(signingSecret.split("_").pop(), signedContent); + const computedSignature = await createHMACSHA256( + signingSecret.split("_").pop(), + signedContent + ); - const expectedSignatures = signature.split(" ").map((sig) => sig.split(",")[1]); + const expectedSignatures = signature + .split(" ") + .map((sig) => sig.split(",")[1]); - return expectedSignatures.some((expectedSignature) => expectedSignature === computedSignature); + return expectedSignatures.some( + (expectedSignature) => expectedSignature === computedSignature + ); } /** @@ -106,9 +115,13 @@ async function createHMACSHA256(secret, data) { crypto = require.call(null, "node:crypto").webcrypto; } - const key = await crypto.subtle.importKey("raw", base64ToBytes(secret), { name: "HMAC", hash: "SHA-256" }, false, [ - "sign", - ]); + const key = await crypto.subtle.importKey( + "raw", + base64ToBytes(secret), + { name: "HMAC", hash: "SHA-256" }, + false, + ["sign"] + ); const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(data)); return bytesToBase64(signature); @@ -232,7 +245,11 @@ async function transformFileInputs(client, inputs, strategy) { try { return await transformFileInputsToReplicateFileURLs(client, inputs); } catch (error) { - if (error instanceof ApiError && error.response.status >= 400 && error.response.status < 500) { + if ( + error instanceof ApiError && + error.response.status >= 400 && + error.response.status < 500 + ) { throw error; } return await transformFileInputsToBase64EncodedDataURIs(inputs); @@ -296,7 +313,7 @@ async function transformFileInputsToBase64EncodedDataURIs(inputs) { totalBytes += buffer.byteLength; if (totalBytes > MAX_DATA_URI_SIZE) { throw new Error( - `Combined filesize of prediction ${totalBytes} bytes exceeds 10mb limit for inline encoding, please provide URLs instead`, + `Combined filesize of prediction ${totalBytes} bytes exceeds 10mb limit for inline encoding, please provide URLs instead` ); } @@ -354,11 +371,14 @@ function isPlainObject(value) { if (proto === null) { return true; } - const Ctor = Object.prototype.hasOwnProperty.call(proto, "constructor") && proto.constructor; + const Ctor = + Object.prototype.hasOwnProperty.call(proto, "constructor") && + proto.constructor; return ( typeof Ctor === "function" && Ctor instanceof Ctor && - Function.prototype.toString.call(Ctor) === Function.prototype.toString.call(Object) + Function.prototype.toString.call(Ctor) === + Function.prototype.toString.call(Object) ); } From e7a1aacb45457ed3439c2e59308904670a998d47 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Thu, 29 Aug 2024 06:03:10 -0700 Subject: [PATCH 163/207] 0.32.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index cdea341f..b134381d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.32.0", + "version": "0.32.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.32.0", + "version": "0.32.1", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index ae45856e..a814ae34 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.32.0", + "version": "0.32.1", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 181c512a7455de9a21cb696af902c8c081da9089 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Wed, 11 Sep 2024 11:39:27 +0100 Subject: [PATCH 164/207] Add new FileOutput type to stream.js This takes a url and creates a `ReadableStream` that has two additional methods `url()` and `blob()` for easily working with remote URLs or base64 encoded data. --- lib/stream.js | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/lib/stream.js b/lib/stream.js index 2e0bbde3..875e0202 100644 --- a/lib/stream.js +++ b/lib/stream.js @@ -98,7 +98,61 @@ function createReadableStream({ url, fetch, options = {} }) { }); } +function createFileOutput({ url, fetch }) { + let type = "application/octet-stream"; + + class FileOutput extends ReadableStream { + async blob() { + const chunks = []; + for await (const chunk of this) { + chunks.push(chunk); + } + return new Blob(chunks, { type }); + } + + url() { + return new URL(url); + } + + toString() { + return url; + } + } + + return new FileOutput({ + async start(controller) { + const response = await fetch(url); + + if (!response.ok) { + const text = await response.text(); + const request = new Request(url, init); + controller.error( + new ApiError( + `Request to ${url} failed with status ${response.status}: ${text}`, + request, + response + ) + ); + } + + if (response.headers.get("Content-Type")) { + type = response.headers.get("Content-Type"); + } + + try { + for await (const chunk of streamAsyncIterator(response.body)) { + controller.enqueue(chunk); + } + controller.close(); + } catch (err) { + controller.error(err); + } + }, + }); +} + module.exports = { + createFileOutput, createReadableStream, ServerSentEvent, }; From 19d606d779e2dc7eeba11d39752f752039b4823d Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Wed, 11 Sep 2024 11:39:48 +0100 Subject: [PATCH 165/207] Export `transform` function from utils.js --- lib/util.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/util.js b/lib/util.js index daecfd15..27afd46a 100644 --- a/lib/util.js +++ b/lib/util.js @@ -452,6 +452,7 @@ async function* streamAsyncIterator(stream) { } module.exports = { + transform, transformFileInputs, validateWebhook, withAutomaticRetries, From 0e609c468bbb5a0305ba01259b32f4cf2b32991d Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Wed, 11 Sep 2024 11:42:42 +0100 Subject: [PATCH 166/207] Return FileOutput from `run()` function This is currently behind the `useFileOutput` flag provided to the Replicate constructor. This allows us to test the feature before rolling it out more widely. When enabled any URLs or data-uris will be converted into a FileOutput type. This is essentially a `ReadableStream` that has two additional methods `url()` to return the underlying URL and `blob()` which will return a `Blob()` object with the file data loaded into memory. The intention here is to make it easier to work with file outputs and allows us to optimize the delivery of file assets to the client in future iterations. --- index.d.ts | 7 ++ index.js | 17 ++++- index.test.ts | 199 +++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 220 insertions(+), 3 deletions(-) diff --git a/index.d.ts b/index.d.ts index 25cd51af..4801654d 100644 --- a/index.d.ts +++ b/index.d.ts @@ -8,6 +8,12 @@ declare module "replicate" { response: Response; } + export interface FileOutput extends ReadableStream { + blob(): Promise; + url(): URL; + toString(): string; + } + export interface Account { type: "user" | "organization"; username: string; @@ -137,6 +143,7 @@ declare module "replicate" { init?: RequestInit ) => Promise; fileEncodingStrategy?: FileEncodingStrategy; + useFileOutput?: boolean; }); auth: string; diff --git a/index.js b/index.js index 6b35db65..4b45d259 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,8 @@ const ApiError = require("./lib/error"); const ModelVersionIdentifier = require("./lib/identifier"); -const { createReadableStream } = require("./lib/stream"); +const { createReadableStream, createFileOutput } = require("./lib/stream"); const { + transform, withAutomaticRetries, validateWebhook, parseProgressFromLogs, @@ -47,6 +48,7 @@ class Replicate { * @param {string} options.userAgent - Identifier of your app * @param {string} [options.baseUrl] - Defaults to https://api.replicate.com/v1 * @param {Function} [options.fetch] - Fetch function to use. Defaults to `globalThis.fetch` + * @param {boolean} [options.useFileOutput] - Set to `true` to return `FileOutput` objects from `run` instead of URLs, defaults to false. * @param {"default" | "upload" | "data-uri"} [options.fileEncodingStrategy] - Determines the file encoding strategy to use */ constructor(options = {}) { @@ -58,6 +60,7 @@ class Replicate { this.baseUrl = options.baseUrl || "https://api.replicate.com/v1"; this.fetch = options.fetch || globalThis.fetch; this.fileEncodingStrategy = options.fileEncodingStrategy ?? "default"; + this.useFileOutput = options.useFileOutput ?? false; this.accounts = { current: accounts.current.bind(this), @@ -196,7 +199,17 @@ class Replicate { throw new Error(`Prediction failed: ${prediction.error}`); } - return prediction.output; + return transform(prediction.output, (value) => { + if ( + typeof value === "string" && + (value.startsWith("https:") || value.startsWith("data:")) + ) { + return this.useFileOutput + ? createFileOutput({ url: value, fetch: this.fetch }) + : value; + } + return value; + }); } /** diff --git a/index.test.ts b/index.test.ts index 7f9fcf2f..5ca3e542 100644 --- a/index.test.ts +++ b/index.test.ts @@ -1,13 +1,13 @@ import { expect, jest, test } from "@jest/globals"; import Replicate, { ApiError, + FileOutput, Model, Prediction, validateWebhook, parseProgressFromLogs, } from "replicate"; import nock from "nock"; -import { Readable } from "node:stream"; import { createReadableStream } from "./lib/stream"; let client: Replicate; @@ -1562,6 +1562,203 @@ describe("Replicate client", () => { scope.done(); }); + + test("returns FileOutput for URLs when useFileOutput is true", async () => { + client = new Replicate({ auth: "foo", useFileOutput: true }); + + nock(BASE_URL) + .post("/predictions") + .reply(201, { + id: "ufawqhfynnddngldkgtslldrkq", + status: "starting", + logs: null, + }) + .get("/predictions/ufawqhfynnddngldkgtslldrkq") + .reply(200, { + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", + logs: [].join("\n"), + }) + .get("/predictions/ufawqhfynnddngldkgtslldrkq") + .reply(200, { + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", + logs: [].join("\n"), + }) + .get("/predictions/ufawqhfynnddngldkgtslldrkq") + .reply(200, { + id: "ufawqhfynnddngldkgtslldrkq", + status: "succeeded", + output: "https://example.com", + logs: [].join("\n"), + }); + + nock("https://example.com") + .get("/") + .reply(200, "hello world", { "Content-Type": "text/plain" }); + + const output = (await client.run( + "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + { + input: { text: "Hello, world!" }, + } + )) as FileOutput; + + expect(output).toBeInstanceOf(ReadableStream); + expect(output.url()).toEqual(new URL("https://example.com")); + + const blob = await output.blob(); + expect(blob.type).toEqual("text/plain"); + expect(blob.arrayBuffer()).toEqual( + new Blob(["Hello, world!"]).arrayBuffer() + ); + }); + + test("returns FileOutput for URLs when useFileOutput is true - acts like string", async () => { + client = new Replicate({ auth: "foo", useFileOutput: true }); + + nock(BASE_URL) + .post("/predictions") + .reply(201, { + id: "ufawqhfynnddngldkgtslldrkq", + status: "starting", + logs: null, + }) + .get("/predictions/ufawqhfynnddngldkgtslldrkq") + .reply(200, { + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", + logs: [].join("\n"), + }) + .get("/predictions/ufawqhfynnddngldkgtslldrkq") + .reply(200, { + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", + logs: [].join("\n"), + }) + .get("/predictions/ufawqhfynnddngldkgtslldrkq") + .reply(200, { + id: "ufawqhfynnddngldkgtslldrkq", + status: "succeeded", + output: "https://example.com", + logs: [].join("\n"), + }); + + nock("https://example.com") + .get("/") + .reply(200, "hello world", { "Content-Type": "text/plain" }); + + const output = (await client.run( + "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + { + input: { text: "Hello, world!" }, + } + )) as unknown as string; + + expect(fetch(output).then((r) => r.text())).resolves.toEqual( + "hello world" + ); + }); + + test("returns FileOutput for URLs when useFileOutput is true - array output", async () => { + client = new Replicate({ auth: "foo", useFileOutput: true }); + + nock(BASE_URL) + .post("/predictions") + .reply(201, { + id: "ufawqhfynnddngldkgtslldrkq", + status: "starting", + logs: null, + }) + .get("/predictions/ufawqhfynnddngldkgtslldrkq") + .reply(200, { + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", + logs: [].join("\n"), + }) + .get("/predictions/ufawqhfynnddngldkgtslldrkq") + .reply(200, { + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", + logs: [].join("\n"), + }) + .get("/predictions/ufawqhfynnddngldkgtslldrkq") + .reply(200, { + id: "ufawqhfynnddngldkgtslldrkq", + status: "succeeded", + output: ["https://example.com"], + logs: [].join("\n"), + }); + + nock("https://example.com") + .get("/") + .reply(200, "hello world", { "Content-Type": "text/plain" }); + + const [output] = (await client.run( + "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + { + input: { text: "Hello, world!" }, + } + )) as FileOutput[]; + + expect(output).toBeInstanceOf(ReadableStream); + expect(output.url()).toEqual(new URL("https://example.com")); + + const blob = await output.blob(); + expect(blob.type).toEqual("text/plain"); + expect(blob.arrayBuffer()).toEqual( + new Blob(["Hello, world!"]).arrayBuffer() + ); + }); + + test("returns FileOutput for URLs when useFileOutput is true - data uri", async () => { + client = new Replicate({ auth: "foo", useFileOutput: true }); + + nock(BASE_URL) + .post("/predictions") + .reply(201, { + id: "ufawqhfynnddngldkgtslldrkq", + status: "starting", + logs: null, + }) + .get("/predictions/ufawqhfynnddngldkgtslldrkq") + .reply(200, { + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", + logs: [].join("\n"), + }) + .get("/predictions/ufawqhfynnddngldkgtslldrkq") + .reply(200, { + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", + logs: [].join("\n"), + }) + .get("/predictions/ufawqhfynnddngldkgtslldrkq") + .reply(200, { + id: "ufawqhfynnddngldkgtslldrkq", + status: "succeeded", + output: "data:text/plain;base64,SGVsbG8sIHdvcmxkIQ==", + logs: [].join("\n"), + }); + + const output = (await client.run( + "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + { + input: { text: "Hello, world!" }, + } + )) as FileOutput; + + expect(output).toBeInstanceOf(ReadableStream); + expect(output.url()).toEqual( + new URL("data:text/plain;base64,SGVsbG8sIHdvcmxkIQ==") + ); + + const blob = await output.blob(); + expect(blob.type).toEqual("text/plain"); + expect(blob.arrayBuffer()).toEqual( + new Blob(["Hello, world!"]).arrayBuffer() + ); + }); }); describe("webhooks.default.secret.get", () => { From 3380bbe24d6f6b74069767867e1abbb1b1c5fe36 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Wed, 11 Sep 2024 11:45:47 -0700 Subject: [PATCH 167/207] Add documentation comments to createFileOutput declaration --- lib/stream.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/stream.js b/lib/stream.js index 875e0202..2f72e2c4 100644 --- a/lib/stream.js +++ b/lib/stream.js @@ -98,6 +98,15 @@ function createReadableStream({ url, fetch, options = {} }) { }); } +/** + * Create a new readable stream for an output file + * created by running a Replicate model. + * + * @param {object} config + * @param {string} config.url The URL to connect to. + * @param {typeof fetch} [config.fetch] The URL to connect to. + * @returns {ReadableStream} + */ function createFileOutput({ url, fetch }) { let type = "application/octet-stream"; From 445f2abe5f5eb5222b8ee77d715e3143ad506a1b Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Wed, 11 Sep 2024 11:48:39 -0700 Subject: [PATCH 168/207] Replace use of ?? with || --- index.js | 4 ++-- lib/util.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 4b45d259..f2c3e1ea 100644 --- a/index.js +++ b/index.js @@ -59,8 +59,8 @@ class Replicate { options.userAgent || `replicate-javascript/${packageJSON.version}`; this.baseUrl = options.baseUrl || "https://api.replicate.com/v1"; this.fetch = options.fetch || globalThis.fetch; - this.fileEncodingStrategy = options.fileEncodingStrategy ?? "default"; - this.useFileOutput = options.useFileOutput ?? false; + this.fileEncodingStrategy = options.fileEncodingStrategy || "default"; + this.useFileOutput = options.useFileOutput || false; this.accounts = { current: accounts.current.bind(this), diff --git a/lib/util.js b/lib/util.js index 27afd46a..bd3c31e9 100644 --- a/lib/util.js +++ b/lib/util.js @@ -318,7 +318,7 @@ async function transformFileInputsToBase64EncodedDataURIs(inputs) { } const data = bytesToBase64(buffer); - mime = mime ?? "application/octet-stream"; + mime = mime || "application/octet-stream"; return `data:${mime};base64,${data}`; }); From eb9adfd925b18875f075d08ca0f039bf35183ebd Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Mon, 16 Sep 2024 00:31:06 -0700 Subject: [PATCH 169/207] 0.33.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b134381d..7e70966f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.32.1", + "version": "0.33.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.32.1", + "version": "0.33.0", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index a814ae34..77ae3d42 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.32.1", + "version": "0.33.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From ac47fb43daeda6e52358105f93fdb3621c3913b8 Mon Sep 17 00:00:00 2001 From: Mattt Date: Mon, 23 Sep 2024 04:04:56 -0700 Subject: [PATCH 170/207] Deprecate stream parameter for predictions.create (#291) --- index.d.ts | 1 + index.js | 2 -- index.test.ts | 20 -------------------- lib/predictions.js | 6 ++---- 4 files changed, 3 insertions(+), 26 deletions(-) diff --git a/index.d.ts b/index.d.ts index 4801654d..78457e08 100644 --- a/index.d.ts +++ b/index.d.ts @@ -297,6 +297,7 @@ declare module "replicate" { model?: string; version?: string; input: object; + /** @deprecated */ stream?: boolean; webhook?: string; webhook_events_filter?: WebhookEventType[]; diff --git a/index.js b/index.js index f2c3e1ea..f0d3e757 100644 --- a/index.js +++ b/index.js @@ -318,13 +318,11 @@ class Replicate { prediction = await this.predictions.create({ ...data, version: identifier.version, - stream: true, }); } else if (identifier.owner && identifier.name) { prediction = await this.predictions.create({ ...data, model: `${identifier.owner}/${identifier.name}`, - stream: true, }); } else { throw new Error("Invalid model version identifier"); diff --git a/index.test.ts b/index.test.ts index 5ca3e542..08fbf7c8 100644 --- a/index.test.ts +++ b/index.test.ts @@ -357,7 +357,6 @@ describe("Replicate client", () => { prompt: "Tell me a story", data, }, - stream: true, }); expect(actual?.input.data).toEqual(expected); @@ -385,7 +384,6 @@ describe("Replicate client", () => { prompt: "Tell me a story", data, }, - stream: true, }); }).rejects.toThrowError( expect.objectContaining({ @@ -396,24 +394,6 @@ describe("Replicate client", () => { } ); - test("Passes stream parameter to API endpoint", async () => { - nock(BASE_URL) - .post("/predictions") - .reply(201, (_uri, body) => { - expect((body as any).stream).toBe(true); - return body; - }); - - await client.predictions.create({ - version: - "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", - input: { - prompt: "Tell me a story", - }, - stream: true, - }); - }); - test("Throws an error if webhook URL is invalid", async () => { await expect(async () => { await client.predictions.create({ diff --git a/lib/predictions.js b/lib/predictions.js index c290d40f..88bed328 100644 --- a/lib/predictions.js +++ b/lib/predictions.js @@ -9,11 +9,11 @@ const { transformFileInputs } = require("./util"); * @param {object} options.input - Required. An object with the model inputs * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) - * @param {boolean} [options.stream] - Whether to stream the prediction output. Defaults to false + * @param {boolean} [options.stream] - Whether to stream the prediction output. Defaults to false. Streaming is now enabled by default for all predictions. For more information, see https://replicate.com/changelog/2024-07-15-streams-always-available-stream-parameter-deprecated * @returns {Promise} Resolves with the created prediction */ async function createPrediction(options) { - const { model, version, stream, input, ...data } = options; + const { model, version, input, ...data } = options; if (data.webhook) { try { @@ -36,7 +36,6 @@ async function createPrediction(options) { this.fileEncodingStrategy ), version, - stream, }, }); } else if (model) { @@ -49,7 +48,6 @@ async function createPrediction(options) { input, this.fileEncodingStrategy ), - stream, }, }); } else { From 02b06d4057c67011909424455489a294452e0384 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 06:10:09 -0700 Subject: [PATCH 171/207] Bump actions/download-artifact from 3 to 4.1.7 in /.github/workflows (#302) * Bump actions/download-artifact from 3 to 4.1.7 in /.github/workflows Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4.1.7. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v3...v4.1.7) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Update actions/upload-artifact to v4 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mattt Zmuda --- .github/workflows/ci.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27a982cb..b951d2ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: id: pack run: | echo "tarball-name=$(npm --loglevel error pack)" >> $GITHUB_OUTPUT - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: package-tarball path: ${{ steps.pack.outputs.tarball-name }} @@ -74,7 +74,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4.1.7 with: name: package-tarball - name: Use Node.js ${{ matrix.node-version }} @@ -103,7 +103,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4.1.7 with: name: package-tarball - name: Use Node.js ${{ matrix.node-version }} @@ -133,7 +133,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4.1.7 with: name: package-tarball - name: Use Node.js ${{ matrix.node-version }} @@ -161,7 +161,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4.1.7 with: name: package-tarball - name: Use Bun ${{ matrix.bun-version }} @@ -191,7 +191,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4.1.7 with: name: package-tarball - name: Use Deno ${{ matrix.deno-version }} @@ -218,7 +218,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4.1.7 with: name: package-tarball - name: Use Node.js From 3c7003a7144f874f209283ff4a0ed8bf5e7f199d Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Tue, 24 Sep 2024 13:12:44 -0700 Subject: [PATCH 172/207] document typescript usage tidy up example Co-Authored-By: Charlie Gleason --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index dabee2ee..f42b4265 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,32 @@ const output = await replicate.run(model, { input }); > upload the file to your own storage provider > and pass a publicly accessible URL. +## TypeScript usage + +This library exports TypeScript definitions. You can import them like this: + +```ts +import Replicate, { Prediction } from 'replicate'; +``` + +Here's an example that uses the `Prediction` type with a custom `onProgress` callback: + +```ts +import Replicate, { Prediction } from 'replicate'; + +const replicate = new Replicate(); +const model = "black-forest-labs/flux-schnell"; +const prompt = "a 19th century portrait of a raccoon gentleman wearing a suit"; +const onProgress = (prediction: Prediction) => { + console.log({ prediction }); +}; + +const output = await replicate.run(model, { input: { prompt } }, onProgress) +console.log({ output }) +``` + +See the full list of exported types in [index.d.ts](./index.d.ts). + ### Webhooks Webhooks provide real-time updates about your prediction. Specify an endpoint when you create a prediction, and Replicate will send HTTP POST requests to that URL when the prediction is created, updated, and finished. From d5af4d0b19f527541ca249e21d5b2f7ad684a9e9 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Tue, 24 Sep 2024 15:20:29 -0700 Subject: [PATCH 173/207] Update README.md Co-authored-by: F --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f42b4265..247f7410 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ const output = await replicate.run(model, { input }); This library exports TypeScript definitions. You can import them like this: ```ts -import Replicate, { Prediction } from 'replicate'; +import Replicate, { type Prediction } from 'replicate'; ``` Here's an example that uses the `Prediction` type with a custom `onProgress` callback: From 13f1bf35bc7b8942028211ac8a105e20c4d1209a Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Tue, 24 Sep 2024 15:20:38 -0700 Subject: [PATCH 174/207] Update README.md Co-authored-by: F --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 247f7410..b6b8f489 100644 --- a/README.md +++ b/README.md @@ -128,9 +128,9 @@ import Replicate, { Prediction } from 'replicate'; const replicate = new Replicate(); const model = "black-forest-labs/flux-schnell"; const prompt = "a 19th century portrait of a raccoon gentleman wearing a suit"; -const onProgress = (prediction: Prediction) => { +function onProgress(prediction: Prediction) { console.log({ prediction }); -}; +} const output = await replicate.run(model, { input: { prompt } }, onProgress) console.log({ output }) From af9d6e2320821468aa8dade0adb1d68be134ba96 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Tue, 24 Sep 2024 15:20:45 -0700 Subject: [PATCH 175/207] Update README.md Co-authored-by: F --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b6b8f489..d65fe06d 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ import Replicate, { type Prediction } from 'replicate'; Here's an example that uses the `Prediction` type with a custom `onProgress` callback: ```ts -import Replicate, { Prediction } from 'replicate'; +import Replicate, { type Prediction } from 'replicate'; const replicate = new Replicate(); const model = "black-forest-labs/flux-schnell"; From 7f02a64598fe374e16753a3ce2d743ae806bbbc3 Mon Sep 17 00:00:00 2001 From: Mattt Date: Wed, 25 Sep 2024 11:29:47 -0700 Subject: [PATCH 176/207] Extend support for `useFileOutput` to `stream` (#309) * Break stream on done event without enqueuing it * Fix doc comment for fetch parameter * Transform URLs in streaming responses when useFileOutput is enabled * Revert 'Break stream on done event without enqueuing it' * Default to useFileOutput = true for readable streams --- lib/stream.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/stream.js b/lib/stream.js index 2f72e2c4..2c899bd9 100644 --- a/lib/stream.js +++ b/lib/stream.js @@ -48,15 +48,18 @@ class ServerSentEvent { * @param {string} config.url The URL to connect to. * @param {typeof fetch} [config.fetch] The URL to connect to. * @param {object} [config.options] The EventSource options. + * @param {boolean} [config.options.useFileOutput] Whether to use the file output stream. * @returns {ReadableStream & AsyncIterable} */ function createReadableStream({ url, fetch, options = {} }) { + const { useFileOutput = true, headers = {}, ...initOptions } = options; + return new ReadableStream({ async start(controller) { const init = { - ...options, + ...initOptions, headers: { - ...options.headers, + ...headers, Accept: "text/event-stream", }, }; @@ -84,9 +87,15 @@ function createReadableStream({ url, fetch, options = {} }) { break; } - controller.enqueue( - new ServerSentEvent(event.event, event.data, event.id) - ); + let data = event.data; + if ( + useFileOutput && + typeof data === "string" && + (data.startsWith("https:") || data.startsWith("data:")) + ) { + data = createFileOutput({ data, fetch }); + } + controller.enqueue(new ServerSentEvent(event.event, data, event.id)); if (event.event === "done") { break; @@ -104,7 +113,7 @@ function createReadableStream({ url, fetch, options = {} }) { * * @param {object} config * @param {string} config.url The URL to connect to. - * @param {typeof fetch} [config.fetch] The URL to connect to. + * @param {typeof fetch} [config.fetch] The fetch function. * @returns {ReadableStream} */ function createFileOutput({ url, fetch }) { From 173b31da148096e3ddfd7252564c68ac21c1bd99 Mon Sep 17 00:00:00 2001 From: Mattt Date: Wed, 25 Sep 2024 11:35:40 -0700 Subject: [PATCH 177/207] Add `wait` parameter to prediction creation methods (#308) * Add block parameter to prediction creation methods * Add support for block: or wait: true to run * Remove unused stream parameter * Apply suggestions from code review Co-authored-by: Aron Carroll * Replace X-Sync header with Prefer: wait * Rename block to wait Update type definitions to capture expectations of wait parameter in run * Normalize wait value to nonzero integer --------- Co-authored-by: Aron Carroll --- index.d.ts | 6 +++++- index.js | 4 +++- lib/deployments.js | 11 ++++++++--- lib/predictions.js | 16 ++++++++++++++-- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/index.d.ts b/index.d.ts index 78457e08..eabcc9b3 100644 --- a/index.d.ts +++ b/index.d.ts @@ -156,7 +156,7 @@ declare module "replicate" { identifier: `${string}/${string}` | `${string}/${string}:${string}`, options: { input: object; - wait?: { interval?: number }; + wait?: boolean | number | { mode?: "poll"; interval?: number }; webhook?: string; webhook_events_filter?: WebhookEventType[]; signal?: AbortSignal; @@ -189,6 +189,7 @@ declare module "replicate" { wait( prediction: Prediction, options?: { + mode?: "poll"; interval?: number; }, stop?: (prediction: Prediction) => Promise @@ -210,9 +211,11 @@ declare module "replicate" { deployment_name: string, options: { input: object; + /** @deprecated */ stream?: boolean; webhook?: string; webhook_events_filter?: WebhookEventType[]; + block?: boolean; } ): Promise; }; @@ -301,6 +304,7 @@ declare module "replicate" { stream?: boolean; webhook?: string; webhook_events_filter?: WebhookEventType[]; + block?: boolean; } & ({ version: string } | { model: string }) ): Promise; get(prediction_id: string): Promise; diff --git a/index.js b/index.js index f0d3e757..712bc59b 100644 --- a/index.js +++ b/index.js @@ -133,7 +133,7 @@ class Replicate { * @param {string} ref - Required. The model version identifier in the format "owner/name" or "owner/name:version" * @param {object} options * @param {object} options.input - Required. An object with the model inputs - * @param {object} [options.wait] - Options for waiting for the prediction to finish + * @param {object} [options.wait] - Options for waiting for the prediction to finish. If `wait` is explicitly true, the function will block and wait for the prediction to finish. * @param {number} [options.wait.interval] - Polling interval in milliseconds. Defaults to 500 * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) @@ -153,11 +153,13 @@ class Replicate { prediction = await this.predictions.create({ ...data, version: identifier.version, + wait: wait, }); } else if (identifier.owner && identifier.name) { prediction = await this.predictions.create({ ...data, model: `${identifier.owner}/${identifier.name}`, + wait: wait, }); } else { throw new Error("Invalid model version identifier"); diff --git a/lib/deployments.js b/lib/deployments.js index 56ed2406..6cab2613 100644 --- a/lib/deployments.js +++ b/lib/deployments.js @@ -7,13 +7,13 @@ const { transformFileInputs } = require("./util"); * @param {string} deployment_name - Required. The name of the deployment * @param {object} options * @param {object} options.input - Required. An object with the model inputs - * @param {boolean} [options.stream] - Whether to stream the prediction output. Defaults to false * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) + * @param {boolean} [options.block] - Whether to wait until the prediction is completed before returning. Defaults to false * @returns {Promise} Resolves with the created prediction data */ async function createPrediction(deployment_owner, deployment_name, options) { - const { stream, input, ...data } = options; + const { input, block, ...data } = options; if (data.webhook) { try { @@ -24,10 +24,16 @@ async function createPrediction(deployment_owner, deployment_name, options) { } } + const headers = {}; + if (block) { + headers["Prefer"] = "wait"; + } + const response = await this.request( `/deployments/${deployment_owner}/${deployment_name}/predictions`, { method: "POST", + headers, data: { ...data, input: await transformFileInputs( @@ -35,7 +41,6 @@ async function createPrediction(deployment_owner, deployment_name, options) { input, this.fileEncodingStrategy ), - stream, }, } ); diff --git a/lib/predictions.js b/lib/predictions.js index 88bed328..f8e1c5ac 100644 --- a/lib/predictions.js +++ b/lib/predictions.js @@ -9,11 +9,11 @@ const { transformFileInputs } = require("./util"); * @param {object} options.input - Required. An object with the model inputs * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) - * @param {boolean} [options.stream] - Whether to stream the prediction output. Defaults to false. Streaming is now enabled by default for all predictions. For more information, see https://replicate.com/changelog/2024-07-15-streams-always-available-stream-parameter-deprecated + * @param {boolean|integer} [options.wait] - Whether to wait until the prediction is completed before returning. If an integer is provided, it will wait for that many seconds. Defaults to false * @returns {Promise} Resolves with the created prediction */ async function createPrediction(options) { - const { model, version, input, ...data } = options; + const { model, version, input, wait, ...data } = options; if (data.webhook) { try { @@ -24,10 +24,21 @@ async function createPrediction(options) { } } + const headers = {}; + if (wait) { + if (typeof wait === "number") { + const n = Math.max(1, Math.ceil(Number(wait)) || 1); + headers["Prefer"] = `wait=${n}`; + } else { + headers["Prefer"] = "wait"; + } + } + let response; if (version) { response = await this.request("/predictions", { method: "POST", + headers, data: { ...data, input: await transformFileInputs( @@ -41,6 +52,7 @@ async function createPrediction(options) { } else if (model) { response = await this.request(`/models/${model}/predictions`, { method: "POST", + headers, data: { ...data, input: await transformFileInputs( From 4cba156dc063a7ab66eff5db484c4244116dd83c Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Wed, 25 Sep 2024 11:36:11 -0700 Subject: [PATCH 178/207] 0.34.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7e70966f..2c3d24e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.33.0", + "version": "0.34.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.33.0", + "version": "0.34.0", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 77ae3d42..f6c18183 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.33.0", + "version": "0.34.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From be0f32318a64edbca77351d864b67a24045d99fe Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Fri, 27 Sep 2024 10:06:43 -0700 Subject: [PATCH 179/207] docs: create predictions for official models (#312) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d65fe06d..c644e75e 100644 --- a/README.md +++ b/README.md @@ -615,8 +615,9 @@ const response = await replicate.predictions.create(options); | name | type | description | | ------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `options.version` | string | **Required**. The model version | | `options.input` | object | **Required**. An object with the model's inputs | +| `options.model` | string | The name of the model, e.g. `black-forest-labs/flux-schnell`. This is required if you're running an [official model](https://replicate.com/explore#official-models). | +| `options.version` | string | The 64-character [model version id](https://replicate.com/docs/topics/models/versions), e.g. `80537f9eead1a5bfa72d5ac6ea6414379be41d4d4f6679fd776e9535d1eb58bb`. This is required if you're not specifying a `model`. | | `options.stream` | boolean | Requests a URL for streaming output output | | `options.webhook` | string | An HTTPS URL for receiving a webhook when the prediction has new output | | `options.webhook_events_filter` | string[] | You can change which events trigger webhook requests by specifying webhook events (`start` \| `output` \| `logs` \| `completed`) | From c1a12b0f60122442c4115c23cc964fa96f2042fc Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Tue, 1 Oct 2024 23:44:15 +0100 Subject: [PATCH 180/207] Bug fixes for the `wait` option in `replicate.run` (#315) There were a couple of small bugs in the current implementation: 1. We would pass non-boolean, non-integer values through to `predictions.create` when it was an object with an interval, resulting in the blocking mode being used accidentally. 2. We would pass the boolean/integer values through to `wait` which would create a runtime error when the `wait` function expects an object. 3. We continued to poll for the prediction despite the blocking response returning the output data. This PR addresses these three issues by checking if the run should be blocking and passing the correct arguments in the correct places. We also assume that if the returned prediction is not in `starting` state then it is completed. This isn't ideal but works for the moment. Lastly, in the case where the blocking request times out the client will fall back to polling at the default interval. --- index.d.ts | 10 +++++----- index.js | 40 ++++++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/index.d.ts b/index.d.ts index eabcc9b3..45a24301 100644 --- a/index.d.ts +++ b/index.d.ts @@ -93,9 +93,9 @@ declare module "replicate" { model: string; version: string; input: object; - output?: any; + output?: any; // TODO: this should be `unknown` source: "api" | "web"; - error?: any; + error?: unknown; logs?: string; metrics?: { predict_time?: number; @@ -156,7 +156,7 @@ declare module "replicate" { identifier: `${string}/${string}` | `${string}/${string}:${string}`, options: { input: object; - wait?: boolean | number | { mode?: "poll"; interval?: number }; + wait?: boolean | number | { interval?: number }; webhook?: string; webhook_events_filter?: WebhookEventType[]; signal?: AbortSignal; @@ -215,7 +215,7 @@ declare module "replicate" { stream?: boolean; webhook?: string; webhook_events_filter?: WebhookEventType[]; - block?: boolean; + wait?: boolean | number | { mode?: "poll"; interval?: number }; } ): Promise; }; @@ -304,7 +304,7 @@ declare module "replicate" { stream?: boolean; webhook?: string; webhook_events_filter?: WebhookEventType[]; - block?: boolean; + wait?: boolean | number | { mode?: "poll"; interval?: number }; } & ({ version: string } | { model: string }) ): Promise; get(prediction_id: string): Promise; diff --git a/index.js b/index.js index 712bc59b..ac4b815d 100644 --- a/index.js +++ b/index.js @@ -147,19 +147,20 @@ class Replicate { const { wait, signal, ...data } = options; const identifier = ModelVersionIdentifier.parse(ref); + const isBlocking = typeof wait === "boolean" || typeof wait === "number"; let prediction; if (identifier.version) { prediction = await this.predictions.create({ ...data, version: identifier.version, - wait: wait, + wait: isBlocking ? wait : false, }); } else if (identifier.owner && identifier.name) { prediction = await this.predictions.create({ ...data, model: `${identifier.owner}/${identifier.name}`, - wait: wait, + wait: isBlocking ? wait : false, }); } else { throw new Error("Invalid model version identifier"); @@ -170,23 +171,26 @@ class Replicate { progress(prediction); } - prediction = await this.wait( - prediction, - wait || {}, - async (updatedPrediction) => { - // Call progress callback with the updated prediction object - if (progress) { - progress(updatedPrediction); + const isDone = isBlocking && prediction.status !== "starting"; + if (!isDone) { + prediction = await this.wait( + prediction, + isBlocking ? {} : wait, + async (updatedPrediction) => { + // Call progress callback with the updated prediction object + if (progress) { + progress(updatedPrediction); + } + + // We handle the cancel later in the function. + if (signal && signal.aborted) { + return true; // stop polling + } + + return false; // continue polling } - - // We handle the cancel later in the function. - if (signal && signal.aborted) { - return true; // stop polling - } - - return false; // continue polling - } - ); + ); + } if (signal && signal.aborted) { prediction = await this.predictions.cancel(prediction.id); From a7dfbfdc774e935444e47f371757f2b3120501b0 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 1 Oct 2024 16:48:14 -0700 Subject: [PATCH 181/207] 0.34.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2c3d24e6..f4c62d7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.34.0", + "version": "0.34.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.34.0", + "version": "0.34.1", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index f6c18183..1bc8a045 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.34.0", + "version": "0.34.1", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From abe1029d65094d199a4e62a18fa7fa7cb96df7a5 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Wed, 9 Oct 2024 15:09:41 -0700 Subject: [PATCH 182/207] document `wait` option for *.prediction.create() methods (#319) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c644e75e..9671a6d2 100644 --- a/README.md +++ b/README.md @@ -618,6 +618,7 @@ const response = await replicate.predictions.create(options); | `options.input` | object | **Required**. An object with the model's inputs | | `options.model` | string | The name of the model, e.g. `black-forest-labs/flux-schnell`. This is required if you're running an [official model](https://replicate.com/explore#official-models). | | `options.version` | string | The 64-character [model version id](https://replicate.com/docs/topics/models/versions), e.g. `80537f9eead1a5bfa72d5ac6ea6414379be41d4d4f6679fd776e9535d1eb58bb`. This is required if you're not specifying a `model`. | +| `options.wait` | number | Wait up to 60s for the prediction to finish before returning. Disabled by default. See [Synchronous predictions](https://replicate.com/docs/topics/predictions/create-a-prediction#sync-mode) for more information. | | `options.stream` | boolean | Requests a URL for streaming output output | | `options.webhook` | string | An HTTPS URL for receiving a webhook when the prediction has new output | | `options.webhook_events_filter` | string[] | You can change which events trigger webhook requests by specifying webhook events (`start` \| `output` \| `logs` \| `completed`) | From 6ad79c8793eab5d20666fdf82ffb64c365194c07 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Wed, 9 Oct 2024 15:10:04 -0700 Subject: [PATCH 183/207] Enable FileObject and blocking mode by default (#316) * Enable FileObject and blocking mode by default * 1.0.0-beta.1 --- index.d.ts | 9 +++++---- index.js | 18 ++++++++---------- index.test.ts | 24 ++++++++++++++++++------ package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 34 insertions(+), 23 deletions(-) diff --git a/index.d.ts b/index.d.ts index 45a24301..1e417ae7 100644 --- a/index.d.ts +++ b/index.d.ts @@ -156,7 +156,9 @@ declare module "replicate" { identifier: `${string}/${string}` | `${string}/${string}:${string}`, options: { input: object; - wait?: boolean | number | { interval?: number }; + wait?: + | { mode: "block"; interval?: number; timeout?: number } + | { mode: "poll"; interval?: number }; webhook?: string; webhook_events_filter?: WebhookEventType[]; signal?: AbortSignal; @@ -189,7 +191,6 @@ declare module "replicate" { wait( prediction: Prediction, options?: { - mode?: "poll"; interval?: number; }, stop?: (prediction: Prediction) => Promise @@ -215,7 +216,7 @@ declare module "replicate" { stream?: boolean; webhook?: string; webhook_events_filter?: WebhookEventType[]; - wait?: boolean | number | { mode?: "poll"; interval?: number }; + wait?: number | boolean; } ): Promise; }; @@ -304,7 +305,7 @@ declare module "replicate" { stream?: boolean; webhook?: string; webhook_events_filter?: WebhookEventType[]; - wait?: boolean | number | { mode?: "poll"; interval?: number }; + wait?: boolean | number; } & ({ version: string } | { model: string }) ): Promise; get(prediction_id: string): Promise; diff --git a/index.js b/index.js index ac4b815d..a5755d91 100644 --- a/index.js +++ b/index.js @@ -48,7 +48,7 @@ class Replicate { * @param {string} options.userAgent - Identifier of your app * @param {string} [options.baseUrl] - Defaults to https://api.replicate.com/v1 * @param {Function} [options.fetch] - Fetch function to use. Defaults to `globalThis.fetch` - * @param {boolean} [options.useFileOutput] - Set to `true` to return `FileOutput` objects from `run` instead of URLs, defaults to false. + * @param {boolean} [options.useFileOutput] - Set to `false` to disable `FileOutput` objects from `run` instead of URLs, defaults to true. * @param {"default" | "upload" | "data-uri"} [options.fileEncodingStrategy] - Determines the file encoding strategy to use */ constructor(options = {}) { @@ -60,7 +60,7 @@ class Replicate { this.baseUrl = options.baseUrl || "https://api.replicate.com/v1"; this.fetch = options.fetch || globalThis.fetch; this.fileEncodingStrategy = options.fileEncodingStrategy || "default"; - this.useFileOutput = options.useFileOutput || false; + this.useFileOutput = options.useFileOutput === false ? false : true; this.accounts = { current: accounts.current.bind(this), @@ -133,8 +133,7 @@ class Replicate { * @param {string} ref - Required. The model version identifier in the format "owner/name" or "owner/name:version" * @param {object} options * @param {object} options.input - Required. An object with the model inputs - * @param {object} [options.wait] - Options for waiting for the prediction to finish. If `wait` is explicitly true, the function will block and wait for the prediction to finish. - * @param {number} [options.wait.interval] - Polling interval in milliseconds. Defaults to 500 + * @param {{mode: "block", timeout?: number, interval?: number} | {mode: "poll", interval?: number }} [options.wait] - Options for waiting for the prediction to finish. If `wait` is explicitly true, the function will block and wait for the prediction to finish. * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) * @param {AbortSignal} [options.signal] - AbortSignal to cancel the prediction @@ -144,23 +143,22 @@ class Replicate { * @returns {Promise} - Resolves with the output of running the model */ async run(ref, options, progress) { - const { wait, signal, ...data } = options; + const { wait = { mode: "block" }, signal, ...data } = options; const identifier = ModelVersionIdentifier.parse(ref); - const isBlocking = typeof wait === "boolean" || typeof wait === "number"; let prediction; if (identifier.version) { prediction = await this.predictions.create({ ...data, version: identifier.version, - wait: isBlocking ? wait : false, + wait: wait.mode === "block" ? wait.timeout ?? true : false, }); } else if (identifier.owner && identifier.name) { prediction = await this.predictions.create({ ...data, model: `${identifier.owner}/${identifier.name}`, - wait: isBlocking ? wait : false, + wait: wait.mode === "block" ? wait.timeout ?? true : false, }); } else { throw new Error("Invalid model version identifier"); @@ -171,11 +169,11 @@ class Replicate { progress(prediction); } - const isDone = isBlocking && prediction.status !== "starting"; + const isDone = wait.mode === "block" && prediction.status !== "starting"; if (!isDone) { prediction = await this.wait( prediction, - isBlocking ? {} : wait, + { interval: wait.mode === "poll" ? wait.interval : undefined }, async (updatedPrediction) => { // Call progress callback with the updated prediction object if (progress) { diff --git a/index.test.ts b/index.test.ts index 08fbf7c8..c67035a3 100644 --- a/index.test.ts +++ b/index.test.ts @@ -1310,7 +1310,7 @@ describe("Replicate client", () => { "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", { input: { text: "Hello, world!" }, - wait: { interval: 1 }, + wait: { mode: "poll", interval: 1 }, }, (prediction) => { const progress = parseProgressFromLogs(prediction); @@ -1402,7 +1402,7 @@ describe("Replicate client", () => { "replicate/hello-world", { input: { text: "Hello, world!" }, - wait: { interval: 1 }, + wait: { mode: "poll", interval: 1 }, }, progress ); @@ -1448,12 +1448,18 @@ describe("Replicate client", () => { }); await expect( - client.run("a/b-1.0:abc123", { input: { text: "Hello, world!" } }) + client.run("a/b-1.0:abc123", { + wait: { mode: "poll" }, + input: { text: "Hello, world!" }, + }) ).resolves.not.toThrow(); }); test("Throws an error for invalid identifiers", async () => { - const options = { input: { text: "Hello, world!" } }; + const options = { + wait: { mode: "poll" } as { mode: "poll" }, + input: { text: "Hello, world!" }, + }; // @ts-expect-error await expect(client.run("owner:abc123", options)).rejects.toThrow(); @@ -1469,6 +1475,7 @@ describe("Replicate client", () => { await client.run( "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", { + wait: { mode: "poll" }, input: { text: "Alice", }, @@ -1492,7 +1499,7 @@ describe("Replicate client", () => { }) .reply(201, { id: "ufawqhfynnddngldkgtslldrkq", - status: "processing", + status: "starting", }) .persist() .get("/predictions/ufawqhfynnddngldkgtslldrkq") @@ -1510,6 +1517,7 @@ describe("Replicate client", () => { const output = await client.run( "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", { + wait: { mode: "poll" }, input: { text: "Hello, world!" }, signal, }, @@ -1524,7 +1532,7 @@ describe("Replicate client", () => { expect(onProgress).toHaveBeenNthCalledWith( 1, expect.objectContaining({ - status: "processing", + status: "starting", }) ); expect(onProgress).toHaveBeenNthCalledWith( @@ -1580,6 +1588,7 @@ describe("Replicate client", () => { const output = (await client.run( "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", { + wait: { mode: "poll" }, input: { text: "Hello, world!" }, } )) as FileOutput; @@ -1631,6 +1640,7 @@ describe("Replicate client", () => { const output = (await client.run( "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", { + wait: { mode: "poll" }, input: { text: "Hello, world!" }, } )) as unknown as string; @@ -1677,6 +1687,7 @@ describe("Replicate client", () => { const [output] = (await client.run( "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", { + wait: { mode: "poll" }, input: { text: "Hello, world!" }, } )) as FileOutput[]; @@ -1724,6 +1735,7 @@ describe("Replicate client", () => { const output = (await client.run( "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", { + wait: { mode: "poll" }, input: { text: "Hello, world!" }, } )) as FileOutput; diff --git a/package-lock.json b/package-lock.json index f4c62d7c..dd0188a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "0.34.1", + "version": "1.0.0-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.34.1", + "version": "1.0.0-beta.1", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 1bc8a045..2572d526 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "0.34.1", + "version": "1.0.0-beta.1", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 2b55fcb87a75f8417e942289c1b8c59d527077e1 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Wed, 9 Oct 2024 15:10:39 -0700 Subject: [PATCH 184/207] 1.0.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index dd0188a6..300e5382 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "1.0.0-beta.1", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "1.0.0-beta.1", + "version": "1.0.0", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 2572d526..fcd47e8f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "1.0.0-beta.1", + "version": "1.0.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From ec31f40cc3d297e6d4a17bd0dca9d656ba55174c Mon Sep 17 00:00:00 2001 From: Dominic Baggott Date: Tue, 15 Oct 2024 11:25:23 +0100 Subject: [PATCH 185/207] Update deployments.createPrediction to use wait A first draft of this interface used `block`, but we ended up going with `wait` (as either a boolean or a number) for the predictions.createPrediction method. This commit brings the two implementations inline, removing the undocumented and unintended `block` parameter. --- lib/deployments.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/deployments.js b/lib/deployments.js index 6cab2613..716c8e19 100644 --- a/lib/deployments.js +++ b/lib/deployments.js @@ -9,11 +9,11 @@ const { transformFileInputs } = require("./util"); * @param {object} options.input - Required. An object with the model inputs * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) - * @param {boolean} [options.block] - Whether to wait until the prediction is completed before returning. Defaults to false + * @param {boolean|integer} [options.wait] - Whether to wait until the prediction is completed before returning. If an integer is provided, it will wait for that many seconds. Defaults to false * @returns {Promise} Resolves with the created prediction data */ async function createPrediction(deployment_owner, deployment_name, options) { - const { input, block, ...data } = options; + const { input, wait, ...data } = options; if (data.webhook) { try { @@ -25,8 +25,13 @@ async function createPrediction(deployment_owner, deployment_name, options) { } const headers = {}; - if (block) { - headers["Prefer"] = "wait"; + if (wait) { + if (typeof wait === "number") { + const n = Math.max(1, Math.ceil(Number(wait)) || 1); + headers["Prefer"] = `wait=${n}`; + } else { + headers["Prefer"] = "wait"; + } } const response = await this.request( From 8b75b39ee8b122f1f85487890f91fd7b52b09d5b Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Mon, 21 Oct 2024 10:26:12 -0700 Subject: [PATCH 186/207] Update README.md to fix examples using FileOutput (#324) --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9671a6d2..8afd43f6 100644 --- a/README.md +++ b/README.md @@ -57,8 +57,11 @@ const model = "stability-ai/stable-diffusion:27b93a2413e7f36cd83da926f3656280b29 const input = { prompt: "a 19th century portrait of a raccoon gentleman wearing a suit", }; -const output = await replicate.run(model, { input }); -// ['https://replicate.delivery/pbxt/GtQb3Sgve42ZZyVnt8xjquFk9EX5LP0fF68NTIWlgBMUpguQA/out-0.png'] +const [output] = await replicate.run(model, { input }); +// FileOutput('https://replicate.delivery/pbxt/GtQb3Sgve42ZZyVnt8xjquFk9EX5LP0fF68NTIWlgBMUpguQA/out-0.png') + +console.log(output.url()); // 'https://replicate.delivery/pbxt/GtQb3Sgve42ZZyVnt8xjquFk9EX5LP0fF68NTIWlgBMUpguQA/out-0.png' +console.log(output.blob()); // Blob ``` You can also run a model in the background: @@ -100,8 +103,8 @@ const model = "nightmareai/real-esrgan:42fed1c4974146d4d2414e2be2c5277c7fcf05fcc const input = { image: await fs.readFile("path/to/image.png"), }; -const output = await replicate.run(model, { input }); -// ['https://replicate.delivery/mgxm/e7b0e122-9daa-410e-8cde-006c7308ff4d/output.png'] +const [output] = await replicate.run(model, { input }); +// FileOutput('https://replicate.delivery/mgxm/e7b0e122-9daa-410e-8cde-006c7308ff4d/output.png') ``` > [!NOTE] From d0316950e0c74eecdd81b3d3ae9c2b1cac519796 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Thu, 24 Oct 2024 13:28:42 -0700 Subject: [PATCH 187/207] Document the `FileOutput` and `wait` parameters (#326) This commit takes a first pass at documenting the `FileOutput` and `wait` parameters introduced in 1.0.0 as well as calling out various potential gotchas with the API around URLs and data-uris based on feedback from the issues. --- README.md | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8afd43f6..08b6e5c8 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,15 @@ console.log(output.url()); // 'https://replicate.delivery/pbxt/GtQb3Sgve42ZZyVnt console.log(output.blob()); // Blob ``` +> [!NOTE] +> A model that outputs file data returns a `FileOutput` object by default. This is an implementation +> of `ReadableStream` that returns the file contents. It has a `.blob()` method for accessing a +> `Blob` representation and a `.url()` method that will return the underlying data-source. +> +> **This data source can be either a remote URL or a data-uri with base64 encoded data. Check +> out the documentation on [creating a prediction](https://replicate.com/docs/topics/predictions/create-a-prediction) +> for more information.** + You can also run a model in the background: ```js @@ -277,6 +286,7 @@ const replicate = new Replicate(options); | `options.baseUrl` | string | Defaults to https://api.replicate.com/v1 | | `options.fetch` | function | Fetch function to use. Defaults to `globalThis.fetch` | | `options.fileEncodingStrategy` | string | Determines the file encoding strategy to use. Possible values: `"default"`, `"upload"`, or `"data-uri"`. Defaults to `"default"` | +| `options.useFileOutput` | boolean | Determines if the `replicate.run()` method should convert file output into `FileOutput` objects | The client makes requests to Replicate's API using @@ -333,8 +343,10 @@ const output = await replicate.run(identifier, options, progress); | ------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `identifier` | string | **Required**. The model version identifier in the format `{owner}/{name}:{version}`, for example `stability-ai/sdxl:8beff3369e81422112d93b89ca01426147de542cd4684c244b673b105188fe5f` | | `options.input` | object | **Required**. An object with the model inputs. | -| `options.wait` | object | Options for waiting for the prediction to finish | -| `options.wait.interval` | number | Polling interval in milliseconds. Defaults to 500 | +| `options.wait` | object | Options for waiting for the prediction to finish | +| `options.wait.type` | `"poll" \| "block"` | `"block"` holds the request open, `"poll"` makes repeated requests to fetch the prediction. Defaults to `"block"` | +| `options.wait.interval` | number | Polling interval in milliseconds. Defaults to 500 | +| `options.wait.timeout` | number | In `"block"` mode determines how long the request will be held open until falling back to polling. Defaults to 60 | | `options.webhook` | string | An HTTPS URL for receiving a webhook when the prediction has new output | | `options.webhook_events_filter` | string[] | An array of events which should trigger [webhooks](https://replicate.com/docs/webhooks). Allowable values are `start`, `output`, `logs`, and `completed` | | `options.signal` | object | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the prediction | @@ -342,7 +354,12 @@ const output = await replicate.run(identifier, options, progress); Throws `Error` if the prediction failed. -Returns `Promise` which resolves with the output of running the model. +Returns `Promise` which resolves with the output of running the model. + +> [!NOTE] +> Currently the TypeScript return type of `replicate.run()` is `Promise` this is +> misleading as a model can return array types as well as primative types like strings, +> numbers and booleans. Example: @@ -364,6 +381,26 @@ const onProgress = (prediction) => { const output = await replicate.run(model, { input }, onProgress) ``` +#### Sync vs. Async API (`"poll"` vs. `"block"`) + +The `replicate.run()` API takes advantage of the [Replicate sync API](https://replicate.com/docs/topics/predictions/create-a-prediction) +which is optimized for low latency requests to file models like `black-forest-labs/flux-dev` and +`black-forest-labs/flux-schnell`. When creating a prediction this will hold a connection open to the +server and return a `FileObject` containing the generated file as quickly as possible. + +> [!NOTE] +> In this mode the `url()` method on the `FileObject` may refer to either a remote URL or +> base64 encoded data-uri. The latter is an optimization we make on certain models to deliver +> the files faster to the client. +> +> If you need the prediction URLs for whatever reason you can opt out of the sync mode by +> passing `wait: { "type": "poll" }` to the `run()` method. +> +> ```js +> const output = await replicate.run(model, { input, wait: { type: "poll" } }); +> output.url() // URL +> ``` + ### `replicate.stream` Run a model and stream its output. Unlike [`replicate.prediction.create`](#replicatepredictionscreate), this method returns only the prediction output rather than the entire prediction object. @@ -1192,6 +1229,41 @@ The `replicate.request()` method is used by the other methods to interact with the Replicate API. You can call this method directly to make other requests to the API. +### `FileOutput` + +`FileOutput` is a `ReadableStream` instance that represents a model file output. It can be used to stream file data to disk or as a `Response` body to an HTTP request. + +```javascript +const [output] = await replicate.run("black-forest-labs/flux-schnell", { + input: { prompt: "astronaut riding a rocket like a horse" } +}); + +// To access the file URL (or data-uri): +console.log(output.url()); //=> "http://example.com" + +// To write the file to disk: +fs.writeFile("my-image.png", output); + +// To stream the file back to a browser: +return new Response(output); + +// To read the file in chunks: +for await (const chunk of output) { + console.log(chunk); // UInt8Array +} +``` + +You can opt out of FileOutput by passing `useFileOutput: false` to the `Replicate` constructor: + +```javascript +const replicate = new Replicate({ useFileOutput: false }); +``` + +| method | returns | description | +| -------------------- | ------ | ------------------------------------------------------------ | +| `url()` | string | A `URL` object representing the HTTP URL or data-uri | +| `blob()` | object | A `Blob` instance containing the binary file | + ## Troubleshooting ### Predictions hanging in Next.js From 6a338aa4b4ac79faae48c0d7081694c3e8f42129 Mon Sep 17 00:00:00 2001 From: Radovenchyk Date: Mon, 28 Oct 2024 18:25:33 +0200 Subject: [PATCH 188/207] Update README.md (#329) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 08b6e5c8..9f882472 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ To validate webhooks: 1. Check out the [webhooks guide](https://replicate.com/docs/webhooks) to get started. 1. [Retrieve your webhook signing secret](https://replicate.com/docs/webhooks#retrieving-the-webhook-signing-key) and store it in your enviroment. -1. Update your webhook handler to call `validateWebhook(request, secret)`, where `request` is an instance of a [web-standard `Request` object](https://developer.mozilla.org/en-US/docs/Web/API/object, and `secret` is the signing secret for your environment. +1. Update your webhook handler to call `validateWebhook(request, secret)`, where `request` is an instance of a [web-standard `Request` object](https://developer.mozilla.org/en-US/docs/Web/API/object), and `secret` is the signing secret for your environment. Here's an example of how to validate webhooks using Next.js: From 5a98ca7c9dcd33e30aa0be3a95ce990630546284 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Wed, 30 Oct 2024 04:20:23 -0700 Subject: [PATCH 189/207] Remove mention of returning data URLs from sync API (#330) This change has now been reverted while we figure out how to provide a more consistent implementation. --- README.md | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 9f882472..2fa9f0f6 100644 --- a/README.md +++ b/README.md @@ -69,9 +69,8 @@ console.log(output.blob()); // Blob > of `ReadableStream` that returns the file contents. It has a `.blob()` method for accessing a > `Blob` representation and a `.url()` method that will return the underlying data-source. > -> **This data source can be either a remote URL or a data-uri with base64 encoded data. Check -> out the documentation on [creating a prediction](https://replicate.com/docs/topics/predictions/create-a-prediction) -> for more information.** +> We recommend accessing file data directly either as readable stream or via `.blob()` as the +> `.url()` property may not always return a HTTP URL in future. You can also run a model in the background: @@ -388,19 +387,6 @@ which is optimized for low latency requests to file models like `black-forest-la `black-forest-labs/flux-schnell`. When creating a prediction this will hold a connection open to the server and return a `FileObject` containing the generated file as quickly as possible. -> [!NOTE] -> In this mode the `url()` method on the `FileObject` may refer to either a remote URL or -> base64 encoded data-uri. The latter is an optimization we make on certain models to deliver -> the files faster to the client. -> -> If you need the prediction URLs for whatever reason you can opt out of the sync mode by -> passing `wait: { "type": "poll" }` to the `run()` method. -> -> ```js -> const output = await replicate.run(model, { input, wait: { type: "poll" } }); -> output.url() // URL -> ``` - ### `replicate.stream` Run a model and stream its output. Unlike [`replicate.prediction.create`](#replicatepredictionscreate), this method returns only the prediction output rather than the entire prediction object. @@ -1238,7 +1224,7 @@ const [output] = await replicate.run("black-forest-labs/flux-schnell", { input: { prompt: "astronaut riding a rocket like a horse" } }); -// To access the file URL (or data-uri): +// To access the file URL: console.log(output.url()); //=> "http://example.com" // To write the file to disk: @@ -1261,8 +1247,8 @@ const replicate = new Replicate({ useFileOutput: false }); | method | returns | description | | -------------------- | ------ | ------------------------------------------------------------ | -| `url()` | string | A `URL` object representing the HTTP URL or data-uri | | `blob()` | object | A `Blob` instance containing the binary file | +| `url()` | string | A `URL` object pointing to the underlying data source. Please note that this may not always be an HTTP URL in future. | ## Troubleshooting From 0b7c8ebd9adb30f84ab420d1288e2ce1dac349cf Mon Sep 17 00:00:00 2001 From: Charlie Gleason Date: Fri, 8 Nov 2024 13:46:58 -0800 Subject: [PATCH 190/207] Fix typo on README (#334) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2fa9f0f6..1e14ec24 100644 --- a/README.md +++ b/README.md @@ -596,7 +596,7 @@ const response = await replicate.models.versions.list(model_owner, model_name); ### `replicate.models.versions.get` -Get metatadata for a specific version of a model. +Get metadata for a specific version of a model. ```js const response = await replicate.models.versions.get(model_owner, model_name, version_id); From 720f786b4c6f15576ba1bf331e8c31683204f4bd Mon Sep 17 00:00:00 2001 From: Marius Kleidl <1375043+Acconut@users.noreply.github.com> Date: Wed, 26 Mar 2025 14:23:13 +0100 Subject: [PATCH 191/207] Correct documentaiton for wait mode (#337) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e14ec24..7d2d61e0 100644 --- a/README.md +++ b/README.md @@ -343,7 +343,7 @@ const output = await replicate.run(identifier, options, progress); | `identifier` | string | **Required**. The model version identifier in the format `{owner}/{name}:{version}`, for example `stability-ai/sdxl:8beff3369e81422112d93b89ca01426147de542cd4684c244b673b105188fe5f` | | `options.input` | object | **Required**. An object with the model inputs. | | `options.wait` | object | Options for waiting for the prediction to finish | -| `options.wait.type` | `"poll" \| "block"` | `"block"` holds the request open, `"poll"` makes repeated requests to fetch the prediction. Defaults to `"block"` | +| `options.wait.mode` | `"poll" \| "block"` | `"block"` holds the request open, `"poll"` makes repeated requests to fetch the prediction. Defaults to `"block"` | | `options.wait.interval` | number | Polling interval in milliseconds. Defaults to 500 | | `options.wait.timeout` | number | In `"block"` mode determines how long the request will be held open until falling back to polling. Defaults to 60 | | `options.webhook` | string | An HTTPS URL for receiving a webhook when the prediction has new output | From 01c4e09b1ac3408beb99589b464d2b81077e4e4b Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Wed, 26 Mar 2025 12:16:12 +0000 Subject: [PATCH 192/207] Remove usage of webcrypto from `node:crypto` in Node 18 The original code was intended to shim in support for the webcrypto interface in Node 18, which was included indirectly as `webcrypto` in the "node:crypto" module or globally via the `--no-experimental-global-webcrypto` flag. This change has caused many issues with bundlers and static analyzers which do not like the obfuscated call to `require()`. Node 18 will no longer receive security support as of 30 April 2025[1] and as such it feels like we can now drop this workaround in favor of documenting alternative approaches. [1]: https://endoflife.date/nodejs --- lib/util.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/lib/util.js b/lib/util.js index bd3c31e9..8dc06c8f 100644 --- a/lib/util.js +++ b/lib/util.js @@ -97,24 +97,6 @@ async function validateWebhook(requestData, secret) { */ async function createHMACSHA256(secret, data) { const encoder = new TextEncoder(); - let crypto = globalThis.crypto; - - // In Node 18 the `crypto` global is behind a --no-experimental-global-webcrypto flag - if (typeof crypto === "undefined" && typeof require === "function") { - // NOTE: Webpack (primarily as it's used by Next.js) and perhaps some - // other bundlers do not currently support the `node` protocol and will - // error if it's found in the source. Other platforms like CloudFlare - // will only support requires when using the node protocol. - // - // As this line is purely to support Node 18.x we make an indirect request - // to the require function which fools Webpack... - // - // We may be able to remove this in future as it looks like Webpack is getting - // support for requiring using the `node:` protocol. - // See: https://github.com/webpack/webpack/issues/18277 - crypto = require.call(null, "node:crypto").webcrypto; - } - const key = await crypto.subtle.importKey( "raw", base64ToBytes(secret), From 2d42001bf1c5c49b78b92914c62e56f99adb4997 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Wed, 26 Mar 2025 13:04:05 +0000 Subject: [PATCH 193/207] Fix validateWebhook interfaces and update tests --- index.d.ts | 24 ++++++++++++++---------- index.test.ts | 36 ++++++++++++++++++++++++++++++++++-- lib/util.js | 41 ++++++++++++++++++++++++++++++++++------- 3 files changed, 82 insertions(+), 19 deletions(-) diff --git a/index.d.ts b/index.d.ts index 1e417ae7..5b35bf2d 100644 --- a/index.d.ts +++ b/index.d.ts @@ -340,16 +340,20 @@ declare module "replicate" { } export function validateWebhook( - requestData: - | Request - | { - id?: string; - timestamp?: string; - body: string; - secret?: string; - signature?: string; - }, - secret: string + request: Request, + secret: string, + crypto?: Crypto + ): Promise; + + export function validateWebhook( + requestData: { + id: string; + timestamp: string; + signature: string; + body: string; + secret: string; + }, + crypto?: Crypto ): Promise; export function parseProgressFromLogs(logs: Prediction | string): { diff --git a/index.test.ts b/index.test.ts index c67035a3..f5bd6093 100644 --- a/index.test.ts +++ b/index.test.ts @@ -9,6 +9,7 @@ import Replicate, { } from "replicate"; import nock from "nock"; import { createReadableStream } from "./lib/stream"; +import { webcrypto } from "node:crypto"; let client: Replicate; const BASE_URL = "https://api.replicate.com/v1"; @@ -1779,9 +1780,40 @@ describe("Replicate client", () => { // This is a test secret and should not be used in production const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"; + if (globalThis.crypto) { + const isValid = await validateWebhook(request, secret); + expect(isValid).toBe(true); + } else { + const isValid = await validateWebhook( + request, + secret, + webcrypto as Crypto // Node 18 requires this to be passed manually + ); + expect(isValid).toBe(true); + } + }); - const isValid = await validateWebhook(request, secret); - expect(isValid).toBe(true); + test("Can be used to validate webhook", async () => { + // Test case from https://github.com/svix/svix-webhooks/blob/b41728cd98a7e7004a6407a623f43977b82fcba4/javascript/src/webhook.test.ts#L190-L200 + const requestData = { + id: "msg_p5jXN8AQM9LWM0D4loKWxJek", + timestamp: "1614265330", + signature: "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=", + body: `{"test": 2432232314}`, + secret: "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw", + }; + + // This is a test secret and should not be used in production + if (globalThis.crypto) { + const isValid = await validateWebhook(requestData); + expect(isValid).toBe(true); + } else { + const isValid = await validateWebhook( + requestData, + webcrypto as Crypto // Node 18 requires this to be passed manually + ); + expect(isValid).toBe(true); + } }); // Add more tests for error handling, edge cases, etc. diff --git a/lib/util.js b/lib/util.js index 8dc06c8f..8ef59ac8 100644 --- a/lib/util.js +++ b/lib/util.js @@ -10,6 +10,7 @@ const { create: createFile } = require("./files"); * @param {string} requestData.body - The raw body of the incoming webhook request. * @param {string} requestData.secret - The webhook secret, obtained from `replicate.webhooks.defaul.secret` method. * @param {string} requestData.signature - The webhook signature header from the incoming request, comprising one or more space-delimited signatures. + * @param {Crypto} [crypto] - An optional `Crypto` implementation that conforms to the [browser Crypto interface](https://developer.mozilla.org/en-US/docs/Web/API/Window/crypto) */ /** @@ -22,6 +23,7 @@ const { create: createFile } = require("./files"); * @param {string} requestData.headers["webhook-signature"] - The webhook signature header from the incoming request, comprising one or more space-delimited signatures * @param {string} requestData.body - The raw body of the incoming webhook request * @param {string} secret - The webhook secret, obtained from `replicate.webhooks.defaul.secret` method + * @param {Crypto} [crypto] - An optional `Crypto` implementation that conforms to the [browser Crypto interface](https://developer.mozilla.org/en-US/docs/Web/API/Window/crypto) */ /** @@ -30,9 +32,13 @@ const { create: createFile } = require("./files"); * @returns {Promise} - True if the signature is valid * @throws {Error} - If the request is missing required headers, body, or secret */ -async function validateWebhook(requestData, secret) { - let { id, timestamp, body, signature } = requestData; - const signingSecret = secret || requestData.secret; +async function validateWebhook(requestData, secretOrCrypto, customCrypto) { + let id; + let body; + let timestamp; + let signature; + let secret; + let crypto = globalThis.crypto; if (requestData && requestData.headers && requestData.body) { if (typeof requestData.headers.get === "function") { @@ -47,6 +53,19 @@ async function validateWebhook(requestData, secret) { signature = requestData.headers["webhook-signature"]; } body = requestData.body; + secret = secretOrCrypto; + if (customCrypto) { + crypto = customCrypto; + } + } else { + id = requestData.id; + body = requestData.body; + timestamp = requestData.timestamp; + signature = requestData.signature; + secret = requestData.secret; + if (secretOrCrypto) { + crypto = secretOrCrypto; + } } if (body instanceof ReadableStream || body.readable) { @@ -71,15 +90,22 @@ async function validateWebhook(requestData, secret) { throw new Error("Missing required body"); } - if (!signingSecret) { + if (!secret) { throw new Error("Missing required secret"); } + if (!crypto) { + throw new Error( + 'Missing `crypto` implementation. If using Node 18 pass in require("node:crypto").webcrypto' + ); + } + const signedContent = `${id}.${timestamp}.${body}`; const computedSignature = await createHMACSHA256( - signingSecret.split("_").pop(), - signedContent + secret.split("_").pop(), + signedContent, + crypto ); const expectedSignatures = signature @@ -94,8 +120,9 @@ async function validateWebhook(requestData, secret) { /** * @param {string} secret - base64 encoded string * @param {string} data - text body of request + * @param {Crypto} crypto - an implementation of the web Crypto api */ -async function createHMACSHA256(secret, data) { +async function createHMACSHA256(secret, data, crypto) { const encoder = new TextEncoder(); const key = await crypto.subtle.importKey( "raw", From 37aa363e05590a738e2e0aef145645f641c5047e Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Wed, 26 Mar 2025 13:04:18 +0000 Subject: [PATCH 194/207] Document the validateWebhook interface on Node 18 --- README.md | 7 +++++++ lib/util.js | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/README.md b/README.md index 7d2d61e0..67c8cab1 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,13 @@ const requestData = { const webhookIsValid = await validateWebhook(requestData); ``` +> [!NOTE] +> The `validateWebhook` function uses the global `crypto` API available in most JavaScript runtimes. Node <= 18 does not provide this global so in this case you need to either call node with the `--no-experimental-global-webcrypto` or provide the `webcrypto` module manually. +> ```js +> const crypto = require("node:crypto").webcrypto; +> const webhookIsValid = await valdiateWebhook(requestData, crypto); +> ``` + ## TypeScript The `Replicate` constructor and all `replicate.*` methods are fully typed. diff --git a/lib/util.js b/lib/util.js index 8ef59ac8..2fa49190 100644 --- a/lib/util.js +++ b/lib/util.js @@ -53,6 +53,12 @@ async function validateWebhook(requestData, secretOrCrypto, customCrypto) { signature = requestData.headers["webhook-signature"]; } body = requestData.body; + if (typeof secretOrCrypto !== "string") { + throw new Error( + "Unexpected value for secret passed to validateWebhook, expected a string" + ); + } + secret = secretOrCrypto; if (customCrypto) { crypto = customCrypto; From ac5caba3fd6c931803923689ae8fb01486b64646 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Wed, 26 Mar 2025 17:30:42 +0000 Subject: [PATCH 195/207] Add support for `AbortSignal` to all API methods (#339) This PR adds support for passing `AbortSignal` to all API methods that make HTTP requests, these are passed directly into the native `fetch()` implementation, so it's up to the user to handle the `AbortError` raised, if any. ```js const controller = new AbortController(); try { const prediction = await replicate.predictions.create({ version: 'xyz', ..., signal: controller.signal, }); } catch (err) { if (err instanceof DOMException && err.name === "AbortError") { ... } } ``` The `paginate` function also checks to see whether the signal was aborted before proceeding to the next iteration. If so it returns immediately to avoid making a redundant fetch call. This allows the client to take advantage of various frameworks that provide an `AbortSignal` instance to tear down any in flight requests. --- README.md | 14 ++-- index.d.ts | 114 +++++++++++++++++++++++--------- index.js | 17 +++-- index.test.ts | 84 +++++++++++++++++++++++ integration/next/pages/index.js | 8 +-- lib/accounts.js | 5 +- lib/collections.js | 10 ++- lib/deployments.js | 40 +++++++++-- lib/files.js | 20 ++++-- lib/hardware.js | 5 +- lib/models.js | 35 ++++++++-- lib/predictions.js | 20 ++++-- lib/trainings.js | 19 ++++-- lib/util.js | 12 ++-- lib/webhooks.js | 5 +- 15 files changed, 326 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 67c8cab1..cf12865c 100644 --- a/README.md +++ b/README.md @@ -1213,15 +1213,21 @@ Low-level method used by the Replicate client to interact with API endpoints. const response = await replicate.request(route, parameters); ``` -| name | type | description | -| -------------------- | ------ | ------------------------------------------------------------ | -| `options.route` | string | Required. REST API endpoint path. | -| `options.parameters` | object | URL, query, and request body parameters for the given route. | +| name | type | description | +| -------------------- | ------------------- | ----------- | +| `options.route` | `string` | Required. REST API endpoint path. +| `options.params` | `object` | URL query parameters for the given route. | +| `options.method` | `string` | HTTP method for the given route. | +| `options.headers` | `object` | Additional HTTP headers for the given route. | +| `options.data` | `object \| FormData` | Request body. | +| `options.signal` | `AbortSignal` | Optional `AbortSignal`. | The `replicate.request()` method is used by the other methods to interact with the Replicate API. You can call this method directly to make other requests to the API. +The method accepts an `AbortSignal` which can be used to cancel the request in flight. + ### `FileOutput` `FileOutput` is a `ReadableStream` instance that represents a model file output. It can be used to stream file data to disk or as a `Response` body to an HTTP request. diff --git a/index.d.ts b/index.d.ts index 5b35bf2d..709f4665 100644 --- a/index.d.ts +++ b/index.d.ts @@ -183,10 +183,14 @@ declare module "replicate" { headers?: object | Headers; params?: object; data?: object; + signal?: AbortSignal; } ): Promise; - paginate(endpoint: () => Promise>): AsyncGenerator<[T]>; + paginate( + endpoint: () => Promise>, + options?: { signal?: AbortSignal } + ): AsyncGenerator; wait( prediction: Prediction, @@ -197,12 +201,15 @@ declare module "replicate" { ): Promise; accounts: { - current(): Promise; + current(options?: { signal?: AbortSignal }): Promise; }; collections: { - list(): Promise>; - get(collection_slug: string): Promise; + list(options?: { signal?: AbortSignal }): Promise>; + get( + collection_slug: string, + options?: { signal?: AbortSignal } + ): Promise; }; deployments: { @@ -217,21 +224,26 @@ declare module "replicate" { webhook?: string; webhook_events_filter?: WebhookEventType[]; wait?: number | boolean; + signal?: AbortSignal; } ): Promise; }; get( deployment_owner: string, - deployment_name: string + deployment_name: string, + options?: { signal?: AbortSignal } + ): Promise; + create( + deployment_config: { + name: string; + model: string; + version: string; + hardware: string; + min_instances: number; + max_instances: number; + }, + options?: { signal?: AbortSignal } ): Promise; - create(deployment_config: { - name: string; - model: string; - version: string; - hardware: string; - min_instances: number; - max_instances: number; - }): Promise; update( deployment_owner: string, deployment_name: string, @@ -245,32 +257,45 @@ declare module "replicate" { | { hardware: string } | { min_instances: number } | { max_instances: number } - ) + ), + options?: { signal?: AbortSignal } ): Promise; delete( deployment_owner: string, - deployment_name: string + deployment_name: string, + options?: { signal?: AbortSignal } ): Promise; - list(): Promise>; + list(options?: { signal?: AbortSignal }): Promise>; }; files: { create( file: Blob | Buffer, - metadata?: Record + metadata?: Record, + options?: { signal?: AbortSignal } ): Promise; - list(): Promise>; - get(file_id: string): Promise; - delete(file_id: string): Promise; + list(options?: { signal?: AbortSignal }): Promise>; + get( + file_id: string, + options?: { signal?: AbortSignal } + ): Promise; + delete( + file_id: string, + options?: { signal?: AbortSignal } + ): Promise; }; hardware: { - list(): Promise; + list(options?: { signal?: AbortSignal }): Promise; }; models: { - get(model_owner: string, model_name: string): Promise; - list(): Promise>; + get( + model_owner: string, + model_name: string, + options?: { signal?: AbortSignal } + ): Promise; + list(options?: { signal?: AbortSignal }): Promise>; create( model_owner: string, model_name: string, @@ -282,17 +307,26 @@ declare module "replicate" { paper_url?: string; license_url?: string; cover_image_url?: string; + signal?: AbortSignal; } ): Promise; versions: { - list(model_owner: string, model_name: string): Promise; + list( + model_owner: string, + model_name: string, + options?: { signal?: AbortSignal } + ): Promise; get( model_owner: string, model_name: string, - version_id: string + version_id: string, + options?: { signal?: AbortSignal } ): Promise; }; - search(query: string): Promise>; + search( + query: string, + options?: { signal?: AbortSignal } + ): Promise>; }; predictions: { @@ -306,11 +340,18 @@ declare module "replicate" { webhook?: string; webhook_events_filter?: WebhookEventType[]; wait?: boolean | number; + signal?: AbortSignal; } & ({ version: string } | { model: string }) ): Promise; - get(prediction_id: string): Promise; - cancel(prediction_id: string): Promise; - list(): Promise>; + get( + prediction_id: string, + options?: { signal?: AbortSignal } + ): Promise; + cancel( + prediction_id: string, + options?: { signal?: AbortSignal } + ): Promise; + list(options?: { signal?: AbortSignal }): Promise>; }; trainings: { @@ -323,17 +364,24 @@ declare module "replicate" { input: object; webhook?: string; webhook_events_filter?: WebhookEventType[]; + signal?: AbortSignal; } ): Promise; - get(training_id: string): Promise; - cancel(training_id: string): Promise; - list(): Promise>; + get( + training_id: string, + options?: { signal?: AbortSignal } + ): Promise; + cancel( + training_id: string, + options?: { signal?: AbortSignal } + ): Promise; + list(options?: { signal?: AbortSignal }): Promise>; }; webhooks: { default: { secret: { - get(): Promise; + get(options?: { signal?: AbortSignal }): Promise; }; }; }; diff --git a/index.js b/index.js index a5755d91..b1248e70 100644 --- a/index.js +++ b/index.js @@ -225,6 +225,7 @@ class Replicate { * @param {object} [options.params] - Query parameters * @param {object|Headers} [options.headers] - HTTP headers * @param {object} [options.data] - Body parameters + * @param {AbortSignal} [options.signal] - AbortSignal to cancel the request * @returns {Promise} - Resolves with the response object * @throws {ApiError} If the request failed */ @@ -241,7 +242,7 @@ class Replicate { ); } - const { method = "GET", params = {}, data } = options; + const { method = "GET", params = {}, data, signal } = options; for (const [key, value] of Object.entries(params)) { url.searchParams.append(key, value); @@ -273,6 +274,7 @@ class Replicate { method, headers, body, + signal, }; const shouldRetry = @@ -354,15 +356,20 @@ class Replicate { * console.log(page); * } * @param {Function} endpoint - Function that returns a promise for the next page of results + * @param {object} [options] + * @param {AbortSignal} [options.signal] - AbortSignal to cancel the request. * @yields {object[]} Each page of results */ - async *paginate(endpoint) { + async *paginate(endpoint, options = {}) { const response = await endpoint(); yield response.results; - if (response.next) { + if (response.next && !(options.signal && options.signal.aborted)) { const nextPage = () => - this.request(response.next, { method: "GET" }).then((r) => r.json()); - yield* this.paginate(nextPage); + this.request(response.next, { + method: "GET", + signal: options.signal, + }).then((r) => r.json()); + yield* this.paginate(nextPage, options); } } diff --git a/index.test.ts b/index.test.ts index f5bd6093..4905908a 100644 --- a/index.test.ts +++ b/index.test.ts @@ -99,6 +99,90 @@ describe("Replicate client", () => { }); }); + describe("paginate", () => { + test("pages through results", async () => { + nock(BASE_URL) + .get("/collections") + .reply(200, { + results: [ + { + name: "Super resolution", + slug: "super-resolution", + description: + "Upscaling models that create high-quality images from low-quality images.", + }, + ], + next: `${BASE_URL}/collections?page=2`, + previous: null, + }); + nock(BASE_URL) + .get("/collections?page=2") + .reply(200, { + results: [ + { + name: "Image classification", + slug: "image-classification", + description: "Models that classify images.", + }, + ], + next: null, + previous: null, + }); + + const iterator = client.paginate(client.collections.list); + + const firstPage = (await iterator.next()).value; + expect(firstPage.length).toBe(1); + + const secondPage = (await iterator.next()).value; + expect(secondPage.length).toBe(1); + }); + + test("accepts an abort signal", async () => { + nock(BASE_URL) + .get("/collections") + .reply(200, { + results: [ + { + name: "Super resolution", + slug: "super-resolution", + description: + "Upscaling models that create high-quality images from low-quality images.", + }, + ], + next: `${BASE_URL}/collections?page=2`, + previous: null, + }); + nock(BASE_URL) + .get("/collections?page=2") + .reply(200, { + results: [ + { + name: "Image classification", + slug: "image-classification", + description: "Models that classify images.", + }, + ], + next: null, + previous: null, + }); + + const controller = new AbortController(); + const iterator = client.paginate(client.collections.list, { + signal: controller.signal, + }); + + const firstIteration = await iterator.next(); + expect(firstIteration.value.length).toBe(1); + + controller.abort(); + + const secondIteration = await iterator.next(); + expect(secondIteration.value).toBeUndefined(); + expect(secondIteration.done).toBe(true); + }); + }); + describe("account.get", () => { test("Calls the correct API route", async () => { nock(BASE_URL).get("/account").reply(200, { diff --git a/integration/next/pages/index.js b/integration/next/pages/index.js index fc9581a7..09124388 100644 --- a/integration/next/pages/index.js +++ b/integration/next/pages/index.js @@ -1,5 +1,5 @@ export default () => ( -
-

Welcome to Next.js

-
-) +
+

Welcome to Next.js

+
+); diff --git a/lib/accounts.js b/lib/accounts.js index b3bbd9fe..72a94afa 100644 --- a/lib/accounts.js +++ b/lib/accounts.js @@ -1,11 +1,14 @@ /** * Get the current account * + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with the current account */ -async function getCurrentAccount() { +async function getCurrentAccount({ signal } = {}) { const response = await this.request("/account", { method: "GET", + signal, }); return response.json(); diff --git a/lib/collections.js b/lib/collections.js index 9332aaab..7b8e8f11 100644 --- a/lib/collections.js +++ b/lib/collections.js @@ -2,11 +2,14 @@ * Fetch a model collection * * @param {string} collection_slug - Required. The slug of the collection. See http://replicate.com/collections + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} - Resolves with the collection data */ -async function getCollection(collection_slug) { +async function getCollection(collection_slug, { signal } = {}) { const response = await this.request(`/collections/${collection_slug}`, { method: "GET", + signal, }); return response.json(); @@ -15,11 +18,14 @@ async function getCollection(collection_slug) { /** * Fetch a list of model collections * + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} - Resolves with the collections data */ -async function listCollections() { +async function listCollections({ signal } = {}) { const response = await this.request("/collections", { method: "GET", + signal, }); return response.json(); diff --git a/lib/deployments.js b/lib/deployments.js index 716c8e19..d45c4f39 100644 --- a/lib/deployments.js +++ b/lib/deployments.js @@ -10,10 +10,11 @@ const { transformFileInputs } = require("./util"); * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) * @param {boolean|integer} [options.wait] - Whether to wait until the prediction is completed before returning. If an integer is provided, it will wait for that many seconds. Defaults to false + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with the created prediction data */ async function createPrediction(deployment_owner, deployment_name, options) { - const { input, wait, ...data } = options; + const { input, wait, signal, ...data } = options; if (data.webhook) { try { @@ -47,6 +48,7 @@ async function createPrediction(deployment_owner, deployment_name, options) { this.fileEncodingStrategy ), }, + signal, } ); @@ -58,13 +60,20 @@ async function createPrediction(deployment_owner, deployment_name, options) { * * @param {string} deployment_owner - Required. The username of the user or organization who owns the deployment * @param {string} deployment_name - Required. The name of the deployment + * @param {object] [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with the deployment data */ -async function getDeployment(deployment_owner, deployment_name) { +async function getDeployment( + deployment_owner, + deployment_name, + { signal } = {} +) { const response = await this.request( `/deployments/${deployment_owner}/${deployment_name}`, { method: "GET", + signal, } ); @@ -84,13 +93,16 @@ async function getDeployment(deployment_owner, deployment_name) { /** * Create a deployment * - * @param {DeploymentCreateRequest} config - Required. The deployment config. + * @param {DeploymentCreateRequest} deployment_config - Required. The deployment config. + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with the deployment data */ -async function createDeployment(deployment_config) { +async function createDeployment(deployment_config, { signal } = {}) { const response = await this.request("/deployments", { method: "POST", data: deployment_config, + signal, }); return response.json(); @@ -110,18 +122,22 @@ async function createDeployment(deployment_config) { * @param {string} deployment_owner - Required. The username of the user or organization who owns the deployment * @param {string} deployment_name - Required. The name of the deployment * @param {DeploymentUpdateRequest} deployment_config - Required. The deployment changes. + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with the deployment data */ async function updateDeployment( deployment_owner, deployment_name, - deployment_config + deployment_config, + { signal } = {} ) { const response = await this.request( `/deployments/${deployment_owner}/${deployment_name}`, { method: "PATCH", data: deployment_config, + signal, } ); @@ -133,13 +149,20 @@ async function updateDeployment( * * @param {string} deployment_owner - Required. The username of the user or organization who owns the deployment * @param {string} deployment_name - Required. The name of the deployment + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with true if the deployment was deleted */ -async function deleteDeployment(deployment_owner, deployment_name) { +async function deleteDeployment( + deployment_owner, + deployment_name, + { signal } = {} +) { const response = await this.request( `/deployments/${deployment_owner}/${deployment_name}`, { method: "DELETE", + signal, } ); @@ -149,11 +172,14 @@ async function deleteDeployment(deployment_owner, deployment_name) { /** * List all deployments * + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} - Resolves with a page of deployments */ -async function listDeployments() { +async function listDeployments({ signal } = {}) { const response = await this.request("/deployments", { method: "GET", + signal, }); return response.json(); diff --git a/lib/files.js b/lib/files.js index de49c580..c8101398 100644 --- a/lib/files.js +++ b/lib/files.js @@ -3,9 +3,11 @@ * * @param {object} file - Required. The file object. * @param {object} metadata - Optional. User-provided metadata associated with the file. + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} - Resolves with the file data */ -async function createFile(file, metadata = {}) { +async function createFile(file, metadata = {}, { signal } = {}) { const form = new FormData(); let filename; @@ -36,6 +38,7 @@ async function createFile(file, metadata = {}) { headers: { "Content-Type": "multipart/form-data", }, + signal, }); return response.json(); @@ -44,11 +47,14 @@ async function createFile(file, metadata = {}) { /** * List all files * + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} - Resolves with the files data */ -async function listFiles() { +async function listFiles({ signal } = {}) { const response = await this.request("/files", { method: "GET", + signal, }); return response.json(); @@ -58,11 +64,14 @@ async function listFiles() { * Get a file * * @param {string} file_id - Required. The ID of the file. + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} - Resolves with the file data */ -async function getFile(file_id) { +async function getFile(file_id, { signal } = {}) { const response = await this.request(`/files/${file_id}`, { method: "GET", + signal, }); return response.json(); @@ -72,11 +81,14 @@ async function getFile(file_id) { * Delete a file * * @param {string} file_id - Required. The ID of the file. + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} - Resolves with true if the file was deleted */ -async function deleteFile(file_id) { +async function deleteFile(file_id, { signal } = {}) { const response = await this.request(`/files/${file_id}`, { method: "DELETE", + signal, }); return response.status === 204; diff --git a/lib/hardware.js b/lib/hardware.js index d717548f..e981b1fa 100644 --- a/lib/hardware.js +++ b/lib/hardware.js @@ -1,11 +1,14 @@ /** * List hardware * + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with the array of hardware */ -async function listHardware() { +async function listHardware({ signal } = {}) { const response = await this.request("/hardware", { method: "GET", + signal, }); return response.json(); diff --git a/lib/models.js b/lib/models.js index 272d9edb..4a3fcdde 100644 --- a/lib/models.js +++ b/lib/models.js @@ -3,11 +3,14 @@ * * @param {string} model_owner - Required. The name of the user or organization that owns the model * @param {string} model_name - Required. The name of the model + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with the model data */ -async function getModel(model_owner, model_name) { +async function getModel(model_owner, model_name, { signal } = {}) { const response = await this.request(`/models/${model_owner}/${model_name}`, { method: "GET", + signal, }); return response.json(); @@ -18,13 +21,16 @@ async function getModel(model_owner, model_name) { * * @param {string} model_owner - Required. The name of the user or organization that owns the model * @param {string} model_name - Required. The name of the model + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with the list of model versions */ -async function listModelVersions(model_owner, model_name) { +async function listModelVersions(model_owner, model_name, { signal } = {}) { const response = await this.request( `/models/${model_owner}/${model_name}/versions`, { method: "GET", + signal, } ); @@ -37,13 +43,21 @@ async function listModelVersions(model_owner, model_name) { * @param {string} model_owner - Required. The name of the user or organization that owns the model * @param {string} model_name - Required. The name of the model * @param {string} version_id - Required. The model version + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with the model version data */ -async function getModelVersion(model_owner, model_name, version_id) { +async function getModelVersion( + model_owner, + model_name, + version_id, + { signal } = {} +) { const response = await this.request( `/models/${model_owner}/${model_name}/versions/${version_id}`, { method: "GET", + signal, } ); @@ -53,11 +67,14 @@ async function getModelVersion(model_owner, model_name, version_id) { /** * List all public models * + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with the model version data */ -async function listModels() { +async function listModels({ signal } = {}) { const response = await this.request("/models", { method: "GET", + signal, }); return response.json(); @@ -76,14 +93,17 @@ async function listModels() { * @param {string} options.paper_url - A URL for the model's paper. * @param {string} options.license_url - A URL for the model's license. * @param {string} options.cover_image_url - A URL for the model's cover image. This should be an image file. + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with the model version data */ async function createModel(model_owner, model_name, options) { - const data = { owner: model_owner, name: model_name, ...options }; + const { signal, ...rest } = options; + const data = { owner: model_owner, name: model_name, ...rest }; const response = await this.request("/models", { method: "POST", data, + signal, }); return response.json(); @@ -93,15 +113,18 @@ async function createModel(model_owner, model_name, options) { * Search for public models * * @param {string} query - The search query + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with a page of models matching the search query */ -async function search(query) { +async function search(query, { signal } = {}) { const response = await this.request("/models", { method: "QUERY", headers: { "Content-Type": "text/plain", }, data: query, + signal, }); return response.json(); diff --git a/lib/predictions.js b/lib/predictions.js index f8e1c5ac..708d04b3 100644 --- a/lib/predictions.js +++ b/lib/predictions.js @@ -10,10 +10,11 @@ const { transformFileInputs } = require("./util"); * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) * @param {boolean|integer} [options.wait] - Whether to wait until the prediction is completed before returning. If an integer is provided, it will wait for that many seconds. Defaults to false + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with the created prediction */ async function createPrediction(options) { - const { model, version, input, wait, ...data } = options; + const { model, version, input, wait, signal, ...data } = options; if (data.webhook) { try { @@ -48,6 +49,7 @@ async function createPrediction(options) { ), version, }, + signal, }); } else if (model) { response = await this.request(`/models/${model}/predictions`, { @@ -61,6 +63,7 @@ async function createPrediction(options) { this.fileEncodingStrategy ), }, + signal, }); } else { throw new Error("Either model or version must be specified"); @@ -73,11 +76,14 @@ async function createPrediction(options) { * Fetch a prediction by ID * * @param {number} prediction_id - Required. The prediction ID + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with the prediction data */ -async function getPrediction(prediction_id) { +async function getPrediction(prediction_id, { signal } = {}) { const response = await this.request(`/predictions/${prediction_id}`, { method: "GET", + signal, }); return response.json(); @@ -87,11 +93,14 @@ async function getPrediction(prediction_id) { * Cancel a prediction by ID * * @param {string} prediction_id - Required. The training ID + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with the data for the training */ -async function cancelPrediction(prediction_id) { +async function cancelPrediction(prediction_id, { signal } = {}) { const response = await this.request(`/predictions/${prediction_id}/cancel`, { method: "POST", + signal, }); return response.json(); @@ -100,11 +109,14 @@ async function cancelPrediction(prediction_id) { /** * List all predictions * + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} - Resolves with a page of predictions */ -async function listPredictions() { +async function listPredictions({ signal } = {}) { const response = await this.request("/predictions", { method: "GET", + signal, }); return response.json(); diff --git a/lib/trainings.js b/lib/trainings.js index 6b13dca2..49640b9e 100644 --- a/lib/trainings.js +++ b/lib/trainings.js @@ -9,10 +9,11 @@ * @param {object} options.input - Required. An object with the model inputs * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the training updates * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with the data for the created training */ async function createTraining(model_owner, model_name, version_id, options) { - const { ...data } = options; + const { signal, ...data } = options; if (data.webhook) { try { @@ -28,6 +29,7 @@ async function createTraining(model_owner, model_name, version_id, options) { { method: "POST", data, + signal, } ); @@ -38,11 +40,14 @@ async function createTraining(model_owner, model_name, version_id, options) { * Fetch a training by ID * * @param {string} training_id - Required. The training ID + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with the data for the training */ -async function getTraining(training_id) { +async function getTraining(training_id, { signal } = {}) { const response = await this.request(`/trainings/${training_id}`, { method: "GET", + signal, }); return response.json(); @@ -52,11 +57,14 @@ async function getTraining(training_id) { * Cancel a training by ID * * @param {string} training_id - Required. The training ID + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with the data for the training */ -async function cancelTraining(training_id) { +async function cancelTraining(training_id, { signal } = {}) { const response = await this.request(`/trainings/${training_id}/cancel`, { method: "POST", + signal, }); return response.json(); @@ -65,11 +73,14 @@ async function cancelTraining(training_id) { /** * List all trainings * + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} - Resolves with a page of trainings */ -async function listTrainings() { +async function listTrainings({ signal } = {}) { const response = await this.request("/trainings", { method: "GET", + signal, }); return response.json(); diff --git a/lib/util.js b/lib/util.js index 2fa49190..09665774 100644 --- a/lib/util.js +++ b/lib/util.js @@ -247,7 +247,7 @@ async function withAutomaticRetries(request, options = {}) { * @param {Replicate} client - The client used to upload the file * @param {object} inputs - The inputs to transform * @param {"default" | "upload" | "data-uri"} strategy - Whether to upload files to Replicate, encode as dataURIs or try both. - * @returns {object} - The transformed inputs + * @returns {Promise} - The transformed inputs * @throws {ApiError} If the request to upload the file fails */ async function transformFileInputs(client, inputs, strategy) { @@ -280,7 +280,7 @@ async function transformFileInputs(client, inputs, strategy) { * * @param {Replicate} client - The client used to upload the file * @param {object} inputs - The inputs to transform - * @returns {object} - The transformed inputs + * @returns {Promise} - The transformed inputs * @throws {ApiError} If the request to upload the file fails */ async function transformFileInputsToReplicateFileURLs(client, inputs) { @@ -301,8 +301,8 @@ const MAX_DATA_URI_SIZE = 10_000_000; * base64-encoded data URI. * * @param {object} inputs - The inputs to transform - * @returns {object} - The transformed inputs - * @throws {Error} If the size of inputs exceeds a given threshould set by MAX_DATA_URI_SIZE + * @returns {Promise} - The transformed inputs + * @throws {Error} If the size of inputs exceeds a given threshold set by MAX_DATA_URI_SIZE */ async function transformFileInputsToBase64EncodedDataURIs(inputs) { let totalBytes = 0; @@ -311,10 +311,10 @@ async function transformFileInputsToBase64EncodedDataURIs(inputs) { let mime; if (value instanceof Blob) { - // Currently we use a NodeJS only API for base64 encoding, as + // Currently, we use a NodeJS only API for base64 encoding, as // we move to support the browser we could support either using // btoa (which does string encoding), the FileReader API or - // a JavaScript implenentation like base64-js. + // a JavaScript implementation like base64-js. // See: https://developer.mozilla.org/en-US/docs/Glossary/Base64 // See: https://github.com/beatgammit/base64-js buffer = await value.arrayBuffer(); diff --git a/lib/webhooks.js b/lib/webhooks.js index f1324ec7..8da6fdf3 100644 --- a/lib/webhooks.js +++ b/lib/webhooks.js @@ -1,11 +1,14 @@ /** * Get the default webhook signing secret * + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with the signing secret for the default webhook */ -async function getDefaultWebhookSecret() { +async function getDefaultWebhookSecret({ signal } = {}) { const response = await this.request("/webhooks/default/secret", { method: "GET", + signal, }); return response.json(); From 5ccf9f3bb9247026d983d2810d2a8b951e8c28ed Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Wed, 26 Mar 2025 17:43:47 +0000 Subject: [PATCH 196/207] Update interface for replicate.models.versions.list() (#349) The endpoint returns a paginated list rather than an array --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 709f4665..2b183d04 100644 --- a/index.d.ts +++ b/index.d.ts @@ -315,7 +315,7 @@ declare module "replicate" { model_owner: string, model_name: string, options?: { signal?: AbortSignal } - ): Promise; + ): Promise>; get( model_owner: string, model_name: string, From f8ab2e8ead2997ac0f2e257652037483b7a3ff52 Mon Sep 17 00:00:00 2001 From: Matt Rothenberg Date: Mon, 12 May 2025 14:26:12 -0400 Subject: [PATCH 197/207] 1.1.0-0 (#352) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 300e5382..c0232d31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "1.0.0", + "version": "1.1.0-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "1.0.0", + "version": "1.1.0-0", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index fcd47e8f..8bf27d6b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "1.0.0", + "version": "1.1.0-0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 7825a4333257062a1fe51428adb536bfd55e8e77 Mon Sep 17 00:00:00 2001 From: Will Sackfield Date: Wed, 20 Aug 2025 15:36:23 -0400 Subject: [PATCH 198/207] Add aborted to Status types (#355) * Aborted is now a first class status --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 2b183d04..83bf34a7 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,5 +1,5 @@ declare module "replicate" { - type Status = "starting" | "processing" | "succeeded" | "failed" | "canceled"; + type Status = "starting" | "processing" | "succeeded" | "failed" | "canceled" | "aborted"; type Visibility = "public" | "private"; type WebhookEventType = "start" | "output" | "logs" | "completed"; From 80f1c37b97eeffbdb6badb6f054b72ac05de2a7d Mon Sep 17 00:00:00 2001 From: Matt Rothenberg Date: Wed, 20 Aug 2025 15:58:34 -0400 Subject: [PATCH 199/207] 1.1.0 (#356) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c0232d31..0b97bfb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "1.1.0-0", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "1.1.0-0", + "version": "1.1.0", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 8bf27d6b..217f6164 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "1.1.0-0", + "version": "1.1.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From e6a83b929ba25a9bdb3a8d62e47d2849841e4fdc Mon Sep 17 00:00:00 2001 From: Matt Rothenberg Date: Thu, 18 Sep 2025 09:59:28 -0400 Subject: [PATCH 200/207] feat: expose is_official boolean on models (#358) --- index.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/index.d.ts b/index.d.ts index 83bf34a7..800a524a 100644 --- a/index.d.ts +++ b/index.d.ts @@ -71,6 +71,7 @@ declare module "replicate" { name: string; description?: string; visibility: "public" | "private"; + is_official: boolean; github_url?: string; paper_url?: string; license_url?: string; From ec80783d7b2e7475568b32dfb17a4e7f506cfe66 Mon Sep 17 00:00:00 2001 From: Matt Rothenberg Date: Thu, 18 Sep 2025 10:12:19 -0400 Subject: [PATCH 201/207] 1.2.0 (#359) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0b97bfb4..22b3376a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "1.1.0", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "1.1.0", + "version": "1.2.0", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 217f6164..3dfa3500 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "1.1.0", + "version": "1.2.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 0f19335f6bc8292e78a8006d3fd3b0e05e4562ce Mon Sep 17 00:00:00 2001 From: Matt Rothenberg Date: Wed, 8 Oct 2025 09:54:07 -0400 Subject: [PATCH 202/207] feat(collection): add typescript info for full_description field (#361) --- index.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/index.d.ts b/index.d.ts index 800a524a..a0cba1db 100644 --- a/index.d.ts +++ b/index.d.ts @@ -25,6 +25,7 @@ declare module "replicate" { name: string; slug: string; description: string; + full_description: string | null; models?: Model[]; } From 922bf8e4621a2d839070f133c348dd517baef926 Mon Sep 17 00:00:00 2001 From: Matt Rothenberg Date: Wed, 8 Oct 2025 09:59:48 -0400 Subject: [PATCH 203/207] 1.3.0 (#362) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 22b3376a..4a3430e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "1.2.0", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "1.2.0", + "version": "1.3.0", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 3dfa3500..6e76641e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "1.2.0", + "version": "1.3.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 6124a0eebb4cac468a009ef7896e6cee5a15f843 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Fri, 24 Oct 2025 09:53:50 -0700 Subject: [PATCH 204/207] feat: update types to match OpenAPI schema (#363) * feat: update types to match OpenAPI schema This PR updates TypeScript types to match the OpenAPI schema from https://api.replicate.com/openapi.json Changes: - Account: add avatar_url field - Status: remove "aborted" status (not in OpenAPI schema) - FileObject: replace name, etag, checksum with checksums object - Prediction: add data_removed, deadline, deployment fields; change version to support "hidden"; update metrics to use total_time; add web URL - Training: convert from type alias to full interface with proper output structure - ModelVersion: make cog_version and openapi_schema nullable * fix: keep predict_time in Prediction metrics for backward compatibility * feat: add "aborted" status to Status type Per the OpenAPI schema, both predictions and trainings can have an "aborted" status, which indicates the task was terminated before it started running (e.g., when a deadline is reached before execution begins). * fix: add web URL to Training.urls Per the OpenAPI schema, Training objects include a web URL in their urls object, matching the Prediction interface. --- index.d.ts | 47 ++++++++++++++++++++++++++++++++++++++++------- index.test.ts | 23 +++++++++++------------ 2 files changed, 51 insertions(+), 19 deletions(-) diff --git a/index.d.ts b/index.d.ts index a0cba1db..fee693d0 100644 --- a/index.d.ts +++ b/index.d.ts @@ -19,6 +19,7 @@ declare module "replicate" { username: string; name: string; github_url?: string; + avatar_url?: string; } export interface Collection { @@ -48,11 +49,11 @@ declare module "replicate" { export interface FileObject { id: string; - name: string; content_type: string; size: number; - etag: string; - checksum: string; + checksums: { + sha256: string; + }; metadata: Record; created_at: string; expires_at: string | null; @@ -85,22 +86,26 @@ declare module "replicate" { export interface ModelVersion { id: string; created_at: string; - cog_version: string; - openapi_schema: object; + cog_version: string | null; + openapi_schema: object | null; } export interface Prediction { id: string; status: Status; model: string; - version: string; + version: string | "hidden"; input: object; output?: any; // TODO: this should be `unknown` source: "api" | "web"; error?: unknown; logs?: string; + data_removed: boolean; + deadline?: string; + deployment?: string; metrics?: { predict_time?: number; + total_time?: number; }; webhook?: string; webhook_events_filter?: WebhookEventType[]; @@ -111,10 +116,38 @@ declare module "replicate" { get: string; cancel: string; stream?: string; + web?: string; }; } - export type Training = Prediction; + export interface Training { + id: string; + status: Status; + model: string; + version: string; + input: object; + output?: { + version?: string; + weights?: string; + }; + source: "api" | "web"; + error?: unknown; + logs?: string; + metrics?: { + predict_time?: number; + total_time?: number; + }; + webhook?: string; + webhook_events_filter?: WebhookEventType[]; + created_at: string; + started_at?: string; + completed_at?: string; + urls: { + get: string; + cancel: string; + web?: string; + }; + } export type FileEncodingStrategy = "default" | "upload" | "data-uri"; diff --git a/index.test.ts b/index.test.ts index 4905908a..df277bf7 100644 --- a/index.test.ts +++ b/index.test.ts @@ -4,6 +4,7 @@ import Replicate, { FileOutput, Model, Prediction, + Training, validateWebhook, parseProgressFromLogs, } from "replicate"; @@ -906,7 +907,7 @@ describe("Replicate client", () => { next: null, }); - const results: Prediction[] = []; + const results: Training[] = []; for await (const batch of client.paginate(client.trainings.list)) { results.push(...batch); } @@ -1176,11 +1177,11 @@ describe("Replicate client", () => { .post("/files") .reply(200, { id: "123", - name: "test-file", content_type: "application/octet-stream", size: 1024, - etag: "abc123", - checksum: "sha256:1234567890abcdef", + checksums: { + sha256: "1234567890abcdef", + }, metadata: {}, created_at: "2023-01-01T00:00:00Z", expires_at: null, @@ -1190,7 +1191,6 @@ describe("Replicate client", () => { }); const file = await client.files.create(testCase.value); expect(file.id).toBe("123"); - expect(file.name).toBe("test-file"); } }); }); @@ -1201,11 +1201,11 @@ describe("Replicate client", () => { .get("/files/123") .reply(200, { id: "123", - name: "test-file", content_type: "application/octet-stream", size: 1024, - etag: "abc123", - checksum: "sha256:1234567890abcdef", + checksums: { + sha256: "1234567890abcdef", + }, metadata: {}, created_at: "2023-01-01T00:00:00Z", expires_at: null, @@ -1216,7 +1216,6 @@ describe("Replicate client", () => { const file = await client.files.get("123"); expect(file.id).toBe("123"); - expect(file.name).toBe("test-file"); }); }); @@ -1230,11 +1229,11 @@ describe("Replicate client", () => { results: [ { id: "123", - name: "test-file", content_type: "application/octet-stream", size: 1024, - etag: "abc123", - checksum: "sha256:1234567890abcdef", + checksums: { + sha256: "1234567890abcdef", + }, metadata: {}, created_at: "2023-01-01T00:00:00Z", expires_at: null, From 0eac811aaedef1c4b46ee3c60b2040b26a554eb9 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Fri, 24 Oct 2025 09:55:48 -0700 Subject: [PATCH 205/207] 1.3.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4a3430e8..f27bdbf4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "1.3.0", + "version": "1.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "1.3.0", + "version": "1.3.1", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index 6e76641e..f88fa2a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "1.3.0", + "version": "1.3.1", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", From 6e544d017a37962d16013b33a45767ae079f6346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sita=20B=C3=A9r=C3=A9t=C3=A9?= Date: Fri, 14 Nov 2025 15:20:44 +0000 Subject: [PATCH 206/207] Fix url key of createFileOutput options for streaming (#350) Fix bug with broken `FileOutput` object being created as part of the streaming API as the wrong value for URL was passed to the constructor. This also extends the `replicate.stream()` interface to accept a `useFileOutput` configuration object. Lastly, we now use the stream URL itself to detect if we should convert URL objects into `FileOutput`. --------- Co-authored-by: Aron Carroll --- biome.json | 6 +--- index.d.ts | 1 + index.js | 12 +++++-- index.test.ts | 99 +++++++++++++++++++++++++++++++++++++++++++++++++-- lib/stream.js | 16 ++++++--- 5 files changed, 121 insertions(+), 13 deletions(-) diff --git a/biome.json b/biome.json index 094cf0ec..ba1c236b 100644 --- a/biome.json +++ b/biome.json @@ -1,11 +1,7 @@ { "$schema": "https://biomejs.dev/schemas/1.0.0/schema.json", "files": { - "ignore": [ - ".wrangler", - "node_modules", - "vendor/*" - ] + "ignore": [".wrangler", "node_modules", "vendor/*"] }, "formatter": { "indentStyle": "space", diff --git a/index.d.ts b/index.d.ts index fee693d0..d4e07840 100644 --- a/index.d.ts +++ b/index.d.ts @@ -208,6 +208,7 @@ declare module "replicate" { webhook?: string; webhook_events_filter?: WebhookEventType[]; signal?: AbortSignal; + useFileOutput?: boolean; } ): AsyncGenerator; diff --git a/index.js b/index.js index b1248e70..3fc14dd5 100644 --- a/index.js +++ b/index.js @@ -315,7 +315,12 @@ class Replicate { * @yields {ServerSentEvent} Each streamed event from the prediction */ async *stream(ref, options) { - const { wait, signal, ...data } = options; + const { + wait, + signal, + useFileOutput = this.useFileOutput, + ...data + } = options; const identifier = ModelVersionIdentifier.parse(ref); @@ -338,7 +343,10 @@ class Replicate { const stream = createReadableStream({ url: prediction.urls.stream, fetch: this.fetch, - ...(signal ? { options: { signal } } : {}), + options: { + useFileOutput, + ...(signal ? { signal } : {}), + }, }); yield* streamAsyncIterator(stream); diff --git a/index.test.ts b/index.test.ts index df277bf7..96f50db7 100644 --- a/index.test.ts +++ b/index.test.ts @@ -1905,8 +1905,12 @@ describe("Replicate client", () => { // Continue with tests for other methods describe("createReadableStream", () => { - function createStream(body: string | ReadableStream, status = 200) { - const streamEndpoint = "https://stream.replicate.com/fake_stream"; + function createStream( + body: string | ReadableStream, + status = 200, + streamEndpoint = "https://stream.replicate.com/fake_stream", + options: { useFileOutput?: boolean } = {} + ) { const fetch = jest.fn((url) => { if (url !== streamEndpoint) { throw new Error(`Unmocked call to fetch() with url: ${url}`); @@ -1916,6 +1920,7 @@ describe("Replicate client", () => { return createReadableStream({ url: streamEndpoint, fetch: fetch as any, + options, }); } @@ -2192,5 +2197,95 @@ describe("Replicate client", () => { ); expect(await iterator.next()).toEqual({ done: true }); }); + + describe("file streams", () => { + test("emits FileOutput objects", async () => { + const stream = createStream( + ` + event: output + id: EVENT_1 + data: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg== + + event: output + id: EVENT_2 + data: https://delivery.replicate.com/my_file.png + + event: done + id: EVENT_3 + data: {} + + `.replace(/^[ ]+/gm, ""), + 200, + "https://stream.replicate.com/v1/files/abcd" + ); + + const iterator = stream[Symbol.asyncIterator](); + const { value: event1 } = await iterator.next(); + expect(event1.data).toBeInstanceOf(ReadableStream); + expect(event1.data.url().href).toEqual( + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" + ); + + const { value: event2 } = await iterator.next(); + expect(event2.data).toBeInstanceOf(ReadableStream); + expect(event2.data.url().href).toEqual( + "https://delivery.replicate.com/my_file.png" + ); + + expect(await iterator.next()).toEqual({ + done: false, + value: { event: "done", id: "EVENT_3", data: "{}" }, + }); + + expect(await iterator.next()).toEqual({ done: true }); + }); + + test("emits strings when useFileOutput is false", async () => { + const stream = createStream( + ` + event: output + id: EVENT_1 + data: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg== + + event: output + id: EVENT_2 + data: https://delivery.replicate.com/my_file.png + + event: done + id: EVENT_3 + data: {} + + `.replace(/^[ ]+/gm, ""), + 200, + "https://stream.replicate.com/v1/files/abcd", + { useFileOutput: false } + ); + + const iterator = stream[Symbol.asyncIterator](); + + expect(await iterator.next()).toEqual({ + done: false, + value: { + event: "output", + id: "EVENT_1", + data: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", + }, + }); + expect(await iterator.next()).toEqual({ + done: false, + value: { + event: "output", + id: "EVENT_2", + data: "https://delivery.replicate.com/my_file.png", + }, + }); + expect(await iterator.next()).toEqual({ + done: false, + value: { event: "done", id: "EVENT_3", data: "{}" }, + }); + + expect(await iterator.next()).toEqual({ done: true }); + }); + }); }); }); diff --git a/lib/stream.js b/lib/stream.js index 2c899bd9..802a98e1 100644 --- a/lib/stream.js +++ b/lib/stream.js @@ -53,6 +53,7 @@ class ServerSentEvent { */ function createReadableStream({ url, fetch, options = {} }) { const { useFileOutput = true, headers = {}, ...initOptions } = options; + const shouldProcessFileOutput = useFileOutput && isFileStream(url); return new ReadableStream({ async start(controller) { @@ -89,11 +90,11 @@ function createReadableStream({ url, fetch, options = {} }) { let data = event.data; if ( - useFileOutput && - typeof data === "string" && - (data.startsWith("https:") || data.startsWith("data:")) + event.event === "output" && + shouldProcessFileOutput && + typeof data === "string" ) { - data = createFileOutput({ data, fetch }); + data = createFileOutput({ url: data, fetch }); } controller.enqueue(new ServerSentEvent(event.event, data, event.id)); @@ -169,6 +170,13 @@ function createFileOutput({ url, fetch }) { }); } +function isFileStream(url) { + try { + return new URL(url).pathname.startsWith("/v1/files/"); + } catch {} + return false; +} + module.exports = { createFileOutput, createReadableStream, From 2fd6f3944f5e252f2eb7cd4e9e5416786b9c4861 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Mon, 17 Nov 2025 11:01:57 +0000 Subject: [PATCH 207/207] 1.4.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f27bdbf4..6c869967 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "replicate", - "version": "1.3.1", + "version": "1.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "1.3.1", + "version": "1.4.0", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", diff --git a/package.json b/package.json index f88fa2a3..90cde175 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "replicate", - "version": "1.3.1", + "version": "1.4.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme",