From 53948d8daaba998a39474217c5242357563bdde8 Mon Sep 17 00:00:00 2001 From: Mattt Date: Mon, 31 Jul 2023 07:11:06 -0700 Subject: [PATCH 001/184] 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 0b59dcb..7a7ab37 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 002/184] 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 90f2158..773f9b6 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 1d72d26..0023360 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 003/184] 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 bf16aff..e41e8ae 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 52e7c25..8a139b8 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 773f9b6..be0ef21 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 0023360..afaa7a2 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 073eb38..7a564ee 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 004/184] 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 7a7ab37..470680d 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 b147ea5..84806a0 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 005/184] 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 afaa7a2..b9daf8e 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 006/184] 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 84806a0..6a23eff 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 8a139b8..818f874 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 0000000..1b9cf9d --- /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 007/184] 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 be0ef21..0fd5c22 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 b9daf8e..fb75739 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 008/184] 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 6a23eff..d8ee42e 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 818f874..442e36e 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 009/184] 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 0fd5c22..a1ba6dd 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 fb75739..6651443 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 010/184] 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 a1ba6dd..1a2395c 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 6651443..b19b778 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 011/184] 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 d8ee42e..293f447 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 012/184] 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 1a2395c..85a3acf 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 b19b778..dc28462 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 013/184] 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 470680d..5f94ce9 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 014/184] 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 85a3acf..49816ea 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 dc28462..7c8f4a3 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 015/184] 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 20e11ba..13968ca 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 5f94ce9..7f4461c 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 293f447..9662235 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 442e36e..fb65f29 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 0000000..4682c9b --- /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 016/184] 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 49816ea..1e52f8b 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 7c8f4a3..1dd4c07 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 017/184] 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 fb65f29..a560da1 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 018/184] document the replicate.run() method (#138) --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index 13968ca..a0beff1 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 019/184] fix run usage docs (#139) --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a0beff1..2842358 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 020/184] 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 7f4461c..50ebd1c 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 021/184] 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 1e52f8b..9983fbc 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 1dd4c07..1a01fa3 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 022/184] 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 2842358..d6f0257 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 50ebd1c..32f279a 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 9662235..c6a2cc2 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 a560da1..c35b41f 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 023/184] 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 9983fbc..989f02f 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 1a01fa3..131663d 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 024/184] 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 d6f0257..55e443c 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 025/184] 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 55e443c..36fbcb0 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 32f279a..601e15b 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 c6a2cc2..4f74985 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 c35b41f..ab4e9d6 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 373ed23..be05750 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 026/184] 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 989f02f..145bcb1 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 131663d..c032728 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 027/184] 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 4f74985..902ba57 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 028/184] 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 145bcb1..ca16970 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 c032728..2f53743 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 029/184] 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 ca16970..8ff3fde 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 030/184] 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 36fbcb0..1eb7a2b 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 601e15b..a3e2ee0 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 902ba57..acb07eb 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 ab4e9d6..377357b 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 0000000..487f3b8 --- /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 be05750..3c4e5b1 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 031/184] 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 8ff3fde..bed1d6f 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 2f53743..597df68 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 032/184] 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 a3e2ee0..d2402ca 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 033/184] 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 bed1d6f..fb10a1e 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 597df68..1b1d1ef 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 034/184] Add user research to README (#158) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 1eb7a2b..4b4a7e1 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 035/184] Revert update to README.md (#159) --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 4b4a7e1..1eb7a2b 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 036/184] 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 d2402ca..bf9cd5d 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 377357b..d033902 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 037/184] 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 fb10a1e..061b8c0 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 1b1d1ef..64c392e 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 038/184] 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 bf9cd5d..c415bf0 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 acb07eb..0552ea5 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 d033902..ec9e523 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 3c4e5b1..12419fc 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 039/184] 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 061b8c0..7d1654e 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 64c392e..d1ce61b 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 040/184] 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 c415bf0..1218cf6 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 0552ea5..8736bb8 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 ec9e523..2684b85 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 0000000..07e21d1 --- /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 041/184] 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 e33833f..0000000 --- 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 0000000..63919df --- /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 7d1654e..1c211cf 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 d1ce61b..7656ee3 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 042/184] 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 0000000..699ed73 --- /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 043/184] 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 f89ed5f..3d9e69c 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 044/184] 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 2684b85..c5c30d5 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 045/184] 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 1218cf6..6e72d36 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 8736bb8..3bd13a2 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 c5c30d5..5e03ca2 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 046/184] Add format script --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 7656ee3..3bb36b6 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 047/184] 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 6e72d36..3b9d80c 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 3bd13a2..b908226 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 5e03ca2..98bff4a 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 36d66b5..821539f 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 6ea5468..b83b3f3 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 b40d091..9332aaa 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 4682c9b..6f32cdb 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 81dbe47..cf05cd1 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 487f3b8..d717548 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 07e21d1..86e23ee 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 12419fc..cb32e9e 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 32b18ad..b1f7fe5 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 edb037c..6b13dca 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 1b9cf9d..7b12633 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 048/184] 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 0000000..006ebf9 --- /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 049/184] 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 1c211cf..e95e536 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 3bb36b6..991e200 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 050/184] 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 1eb7a2b..fd13c70 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 3b9d80c..05edbe6 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 b908226..5a6c532 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 0000000..dfc7c61 --- /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 051/184] 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 05edbe6..5620f3b 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 5a6c532..a3ed7f8 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 98bff4a..5b5a1dd 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 cb32e9e..c6a02fc 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 b1f7fe5..294e8d9 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 052/184] 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 e95e536..e7e2699 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 991e200..3551bbd 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 053/184] 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 dfc7c61..012d6d0 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 e7e2699..62c0c78 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 3551bbd..5878281 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 054/184] 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 62c0c78..b560a27 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 5878281..b576735 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 055/184] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fd13c70..0afa9c5 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 056/184] 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 e41e8ae..155ea01 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 0afa9c5..4d6e728 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 63919df..807b901 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 a3ed7f8..ce407f9 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 057/184] 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 b560a27..19133da 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 b576735..24a088b 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 058/184] 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 4d6e728..bd7e43d 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 059/184] 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 155ea01..de7cec3 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 0000000..abe859a --- /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 0000000..5ef7b63 --- /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 0000000..1584af5 --- /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 0000000..7fb6fc8 --- /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 0000000..547b726 --- /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 0000000..2bd276f --- /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 0000000..2a17c88 --- /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 0000000..51076d7 --- /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 0000000..be4ab90 --- /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 0000000..8e27a3b --- /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 0000000..f309b1b --- /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 0000000..4adae99 --- /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 0000000..4b733f2 --- /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 821539f..058816a 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 060/184] 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 bd7e43d..9d5bef3 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 061/184] Specify type field in package.json (#192) --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 24a088b..ca06b8b 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 062/184] 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 ca06b8b..d0d77fb 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 063/184] 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 de7cec3..b42984c 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 064/184] 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 19133da..3f74872 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 d0d77fb..61b4c87 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 065/184] 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 9d5bef3..90ed101 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 066/184] 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 5620f3b..76df9d4 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 ce407f9..a85ea4e 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 5b5a1dd..d50ccb4 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 0000000..b3bbd9f --- /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 067/184] 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 3f74872..35659fa 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 61b4c87..1c8680a 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 068/184] 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 76df9d4..ab40ed8 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 a85ea4e..13207ca 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 d50ccb4..106cc58 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 7b12633..6bd70ec 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 0000000..f1324ec --- /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 069/184] 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 ab40ed8..69e651b 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 13207ca..83b9888 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 106cc58..ae01338 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 6f32cdb..8ba5ea3 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 070/184] 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 35659fa..519bc25 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 1c8680a..1234e6e 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 071/184] 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 90ed101..6893da2 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 072/184] 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 6893da2..222deff 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 ae01338..f00a7e6 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 8ba5ea3..3e1ceeb 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 294e8d9..5b0370e 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 6bd70ec..48d7563 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 073/184] 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 519bc25..8ba3c03 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 1234e6e..098bb3a 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 074/184] 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 222deff..75ab0d2 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 075/184] 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 69e651b..8dc998a 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 83b9888..24376fe 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 f00a7e6..97abc6f 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 48d7563..949bafa 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 076/184] 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 abe859a..40f00cd 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 5ef7b63..6e0f8b2 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 547b726..9264f3c 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 2bd276f..7549e90 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 be4ab90..b7928c5 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 8e27a3b..e49f75c 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 077/184] 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 0000000..cc27ab8 --- /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 0000000..c2ce492 --- /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 0000000..ce1486f --- /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 0000000..ae692d9 --- /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 0000000..4885e82 --- /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 0000000..4fbf209 --- /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 078/184] 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 949bafa..a9406e3 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 079/184] 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 b42984c..cc4d90e 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 24376fe..8f56ab5 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 cc27ab8..8394e05 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 0000000..b15cbc2 --- /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 c2ce492..be18d53 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 ce1486f..0c0fc5e 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 ae692d9..0000000 --- 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 4885e82..94ae94c 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 0000000..b15cbc2 --- /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 1584af5..0000000 --- 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 0000000..b15cbc2 --- /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 2a17c88..0000000 --- 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 0000000..b15cbc2 --- /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 f309b1b..0000000 --- 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 a9406e3..ac99e08 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 080/184] 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 75ab0d2..5341fd4 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 807b901..ecb665f 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 8dc998a..abf68dc 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 8f56ab5..cd299f4 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 97abc6f..6624eb2 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 012d6d0..a97642d 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 ac99e08..ff9dacc 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 8ba3c03..c3a18b4 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 098bb3a..c2ed3c9 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 7a564ee..b699d79 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 0000000..88465da --- /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 081/184] 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 c3a18b4..0a727c6 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 c2ed3c9..48a0b45 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 082/184] 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 6624eb2..7e0ae22 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 b15cbc2..7775040 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 0c0fc5e..932d8f5 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 b15cbc2..7775040 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 b15cbc2..7775040 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 b15cbc2..7775040 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 a97642d..cd9274c 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 083/184] 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 cc4d90e..8d4345e 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 084/184] 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 0a727c6..1d6c9cd 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 48a0b45..123574d 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 085/184] Document webhooks in README --- README.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5341fd4..255c8f1 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 086/184] 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 8d4345e..c6ca04e 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 0000000..3c3629e --- /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 0000000..263a86e --- /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 0000000..f4ba202 --- /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 0000000..2ffa937 --- /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 b699d79..e6b4ed6 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 087/184] 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 c6ca04e..b3dbba4 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 088/184] 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 b3dbba4..5fe3c62 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 cd299f4..6c6590b 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 0000000..7775040 --- /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 0000000..575d779 --- /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 0000000..9c0ae65 --- /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 0000000..380d813 --- /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 0000000..91ba179 --- /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 0000000..142a177 --- /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 cd9274c..a6e5f84 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 ff9dacc..22a14c8 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 e6b4ed6..8a2eb35 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 089/184] 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 5fe3c62..f42a291 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 255c8f1..70a1a9c 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 e9fbaab..1357e5b 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 263a86e..fc95399 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 a6e5f84..2e0bbde 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 0000000..f400709 --- /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 090/184] 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 abf68dc..d1602ad 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 6c6590b..042af91 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 7e0ae22..55adee5 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 3e1ceeb..4f6f3c6 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 8a2eb35..d77efdc 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 091/184] 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 70a1a9c..e8ecd57 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 092/184] 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 f42a291..74995fc 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 093/184] 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 1d6c9cd..9a3a3f3 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 123574d..26f546e 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 094/184] 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 d1602ad..31a2325 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 55adee5..d2f064c 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 095/184] 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 9a3a3f3..1eb7db5 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 26f546e..ba884be 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 096/184] 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 74995fc..966cb77 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 1eb7db5..caab4f7 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 ba884be..1d838df 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 097/184] 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 e8ecd57..f6d798c 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 098/184] Use Bearer authorization scheme (#246) --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 042af91..ce5d502 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 099/184] 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 ce5d502..74f667b 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 d2f064c..f62c7bc 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 100/184] 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 74f667b..79b8f4d 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 f62c7bc..53737e0 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 101/184] 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 966cb77..2144cbf 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 caab4f7..776133d 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 1d838df..b61d4b9 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 102/184] 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 776133d..07d2bda 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 b61d4b9..d4cf9b2 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 103/184] 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 79b8f4d..f4c0e2c 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 104/184] 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 2144cbf..ed2cab8 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 105/184] 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 ed2cab8..e0a90e4 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 22a14c8..68b1d9d 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 106/184] 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 07d2bda..0a2d156 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 d4cf9b2..79082df 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 107/184] 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 f4c0e2c..dd60cec 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 be18d53..32ec9fc 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 108/184] 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 0a2d156..955a42d 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 79082df..3550449 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 109/184] 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 f6d798c..e88bb6a 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 110/184] document node 18 requirement --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e88bb6a..270c6d2 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 111/184] 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 0000000..58baaec --- /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 112/184] 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 ecb665f..094cf0e 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 31a2325..1ef9e89 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 dd60cec..70cbe86 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 53737e0..7502969 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 4f6f3c6..27a2f6a 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 0000000..f6620e9 --- /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 5b0370e..c290d40 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 68b1d9d..e164899 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 113/184] 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 955a42d..6303b3b 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 3550449..23d7472 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 114/184] 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 ab1d859..0b5e894 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 270c6d2..29c31c6 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 115/184] 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 7502969..834b786 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 e164899..3745d9f 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 116/184] 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 6303b3b..f268ac6 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 23d7472..502ee21 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 117/184] 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 70cbe86..bf53449 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 834b786..8ecb5ae 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 118/184] 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 f6620e9..520f2aa 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 119/184] 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 f268ac6..710a1e9 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 502ee21..f31fb22 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 120/184] 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 58baaec..0000000 --- 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 121/184] 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 29c31c6..797af77 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 122/184] 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 710a1e9..6af9303 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 123/184] 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 1ef9e89..e6fd2d9 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 bf53449..b92503e 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 8ecb5ae..9e22cbe 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 27a2f6a..56ed240 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 124/184] 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 e6fd2d9..7d4ef0a 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 b92503e..21e83f9 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 9e22cbe..2645ca4 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 520f2aa..de49c58 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 125/184] 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 6af9303..c6906dc 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 f31fb22..9d9b60b 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 126/184] 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 e0a90e4..87b1f75 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 0000000..6b3e0a0 --- /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 0000000..057d612 --- /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 0000000..28965d5 --- /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 0000000..069e28a --- /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 127/184] 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 0000000..0967ef4 --- /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 128/184] 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 797af77..0f26281 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 129/184] 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 87b1f75..27a982c 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 1357e5b..f0665a6 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 40f00cd..6d8c436 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 6e0f8b2..afc3c01 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 28965d5..e944d77 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 069e28a..a235699 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 9264f3c..48feccb 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 7549e90..c95ab8b 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 b7928c5..0f0f356 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 e49f75c..1b276bc 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 130/184] Document alternate interface to `validateWebhook()` Fixes #261 --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 0f26281..401bfad 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 131/184] Document 100MiB file upload limit (#287) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 401bfad..8f4350b 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 132/184] 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 2645ca4..c4d7e06 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 3745d9f..b4483ae 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 133/184] 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 c6906dc..7c45442 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 9d9b60b..7e13d35 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 134/184] 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 27a982c..a0a2241 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 135/184] 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 a0a2241..27a982c 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 136/184] 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 8f4350b..dabee2e 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 7d4ef0a..25cd51a 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 21e83f9..6b35db6 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 c4d7e06..7f9fcf2 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 c6a02fc..272d9ed 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 137/184] 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 7c45442..cdea341 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 7e13d35..ae45856 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 138/184] 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 b4483ae..a36c480 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 139/184] 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 a36c480..daecfd1 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 140/184] 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 cdea341..b134381 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 ae45856..a814ae3 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 141/184] 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 2e0bbde..875e020 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 142/184] 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 daecfd1..27afd46 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 143/184] 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 25cd51a..4801654 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 6b35db6..4b45d25 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 7f9fcf2..5ca3e54 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 144/184] 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 875e020..2f72e2c 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 145/184] 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 4b45d25..f2c3e1e 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 27afd46..bd3c31e 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 146/184] 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 b134381..7e70966 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 a814ae3..77ae3d4 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 147/184] 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 4801654..78457e0 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 f2c3e1e..f0d3e75 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 5ca3e54..08fbf7c 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 c290d40..88bed32 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 148/184] 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 27a982c..b951d2e 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 149/184] 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 dabee2e..f42b426 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 150/184] 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 f42b426..247f741 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 151/184] 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 247f741..b6b8f48 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 152/184] 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 b6b8f48..d65fe06 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 153/184] 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 2f72e2c..2c899bd 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 154/184] 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 78457e0..eabcc9b 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 f0d3e75..712bc59 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 56ed240..6cab261 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 88bed32..f8e1c5a 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 155/184] 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 7e70966..2c3d24e 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 77ae3d4..f6c1818 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 156/184] 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 d65fe06..c644e75 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 157/184] 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 eabcc9b..45a2430 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 712bc59..ac4b815 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 158/184] 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 2c3d24e..f4c62d7 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 f6c1818..1bc8a04 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 159/184] 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 c644e75..9671a6d 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 160/184] 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 45a2430..1e417ae 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 ac4b815..a5755d9 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 08fbf7c..c67035a 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 f4c62d7..dd0188a 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 1bc8a04..2572d52 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 161/184] 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 dd0188a..300e538 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 2572d52..fcd47e8 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 162/184] 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 6cab261..716c8e1 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 163/184] 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 9671a6d..8afd43f 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 164/184] 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 8afd43f..08b6e5c 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 165/184] Update README.md (#329) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 08b6e5c..9f88247 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 166/184] 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 9f88247..2fa9f0f 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 167/184] Fix typo on README (#334) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2fa9f0f..1e14ec2 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 168/184] 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 1e14ec2..7d2d61e 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 169/184] 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 bd3c31e..8dc06c8 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 170/184] 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 1e417ae..5b35bf2 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 c67035a..f5bd609 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 8dc06c8..8ef59ac 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 171/184] 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 7d2d61e..67c8cab 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 8ef59ac..2fa4919 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 172/184] 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 67c8cab..cf12865 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 5b35bf2..709f466 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 a5755d9..b1248e7 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 f5bd609..4905908 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 fc9581a..0912438 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 b3bbd9f..72a94af 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 9332aaa..7b8e8f1 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 716c8e1..d45c4f3 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 de49c58..c810139 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 d717548..e981b1f 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 272d9ed..4a3fcdd 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 f8e1c5a..708d04b 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 6b13dca..49640b9 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 2fa4919..0966577 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 f1324ec..8da6fdf 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 173/184] 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 709f466..2b183d0 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 174/184] 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 300e538..c0232d3 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 fcd47e8..8bf27d6 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 175/184] 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 2b183d0..83bf34a 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 176/184] 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 c0232d3..0b97bfb 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 8bf27d6..217f616 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 177/184] 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 83bf34a..800a524 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 178/184] 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 0b97bfb..22b3376 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 217f616..3dfa350 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 179/184] 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 800a524..a0cba1d 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 180/184] 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 22b3376..4a3430e 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 3dfa350..6e76641 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 181/184] 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 a0cba1d..fee693d 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 4905908..df277bf 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 182/184] 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 4a3430e..f27bdbf 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 6e76641..f88fa2a 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 183/184] 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 094cf0e..ba1c236 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 fee693d..d4e0784 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 b1248e7..3fc14dd 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 df277bf..96f50db 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 2c899bd..802a98e 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 184/184] 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 f27bdbf..6c86996 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 f88fa2a..90cde17 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",