diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index e33833fb..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,28 +0,0 @@ -module.exports = { - env: { - browser: true, - commonjs: true, - es2021: true, - node: true, - }, - extends: [ - 'airbnb-base', - 'plugin:jest/recommended', - 'plugin:jsdoc/recommended', - ], - overrides: [], - parserOptions: { - ecmaVersion: 'latest', - project: './jsconfig.json', - }, - plugins: ['jest', 'jsdoc'], - rules: { - camelcase: 'off', - 'comma-dangle': 'off', - 'generator-star-spacing': 'off', - 'max-len': 'off', - 'operator-linebreak': 'off', - 'jsdoc/require-param-description': 'off', - 'jsdoc/tag-lines': ['error', 'any', { startLines: 1 }], - }, -}; diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..006ebf9b --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Apply automatic formatting +66d81afa121fb205cfbe46cfe7e2845183b1b237 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf16aff1..b951d2ed 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: @@ -12,16 +12,221 @@ jobs: strategy: matrix: - node-version: [18.x] - # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + suite: [node] + # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases + node-version: [18.x, 20.x, 22.x] 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@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" + - run: npm ci + - run: npm run test + - run: npm run check + - run: npm run lint + + + # Build a production tarball and use it to run the integration + 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 ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" + - name: Build tarball + id: pack + run: | + echo "tarball-name=$(npm --loglevel error pack)" >> $GITHUB_OUTPUT + - uses: actions/upload-artifact@v4 + with: + name: package-tarball + path: ${{ steps.pack.outputs.tarball-name }} + + + integration-node: + needs: [test, build] + runs-on: ubuntu-latest + + env: + REPLICATE_API_TOKEN: ${{ secrets.REPLICATE_API_TOKEN }} + + 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] + fail-fast: false + + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4.1.7 + with: + name: package-tarball + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" + - run: | + npm --prefix integration/${{ matrix.suite }} install + 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: + suite: ["browser"] + browser: ["chromium", "firefox", "webkit"] + node-version: [20.x] + fail-fast: false + + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4.1.7 + with: + name: package-tarball + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + 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] + runs-on: ubuntu-latest + + env: + REPLICATE_API_TOKEN: ${{ secrets.REPLICATE_API_TOKEN }} + + strategy: + matrix: + suite: [cloudflare-worker] + node-version: [20.x] + + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4.1.7 + with: + name: package-tarball + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" + - run: | + test "${{ matrix.suite }}" = "cloudflare-worker" && echo "REPLICATE_API_TOKEN=${{ secrets.REPLICATE_API_TOKEN }}" > integration/${{ matrix.suite }}/.dev.vars + npm --prefix integration/${{ matrix.suite }} install + 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 + + env: + REPLICATE_API_TOKEN: ${{ secrets.REPLICATE_API_TOKEN }} + + strategy: + matrix: + suite: [bun] + bun-version: [1.0.11] + + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4.1.7 + with: + name: package-tarball + - name: Use Bun ${{ matrix.bun-version }} + uses: oven-sh/setup-bun@v1 + with: + bun-version: ${{ matrix.bun-version }} + - run: | + cd integration/${{ matrix.suite }} + bun uninstall replicate + bun install "file:../../${{ needs.build.outputs.tarball-name }}" + retries=3 + for ((i=0; i [!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 20e11ba0..cf12865c 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,22 @@ 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. +## Supported platforms + +- [Node.js](https://nodejs.org) >= 18 +- [Bun](https://bun.sh) >= 1.0 +- [Deno](https://deno.com) >= 1.28 + +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: @@ -20,14 +31,22 @@ npm install replicate ## Usage -Create the client: +Import or require the package: ```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"; +``` + +Instantiate the client: +```js const replicate = new Replicate({ - // get your token from https://replicate.com/account - auth: process.env.REPLICATE_API_TOKEN, + // get your token from https://replicate.com/account/api-tokens + auth: "my api token", // defaults to process.env.REPLICATE_API_TOKEN }); ``` @@ -38,10 +57,21 @@ 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 ``` +> [!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. +> +> 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: ```js @@ -67,29 +97,186 @@ 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 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 -import { promises as fs } from "fs"; +const fs = require("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}`; +// Or when using ESM. +// import fs from "node:fs/promises"; 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'] +const [output] = await replicate.run(model, { input }); +// FileOutput('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. +> 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. + +## TypeScript usage + +This library exports TypeScript definitions. You can import them like this: + +```ts +import Replicate, { type Prediction } from 'replicate'; +``` + +Here's an example that uses the `Prediction` type with a custom `onProgress` callback: + +```ts +import Replicate, { type 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"; +function 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. + +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", ... } +``` + +## 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 }); +} +``` + +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); +``` + +> [!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. + +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 @@ -98,12 +285,15 @@ const output = await replicate.run(model, { input }); 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"` | +| `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 [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch). @@ -111,7 +301,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 @@ -121,32 +311,149 @@ 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"); -const replicate = new Replicate({ - // get your token from https://replicate.com/account - auth: process.env.REPLICATE_API_TOKEN, - fetch: fetch, -}); +// Using ESM: +// import Replicate from "replicate"; +// 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 -client.fetch = (url, options) => { - const headers = new Headers(options && options.headers); - headers.append("X-Custom-Header", "some value"); +const customFetch = (url, options) => { + const headers = options && options.headers ? { ...options.headers } : {}; + headers["X-Custom-Header"] = "some value"; console.log("fetch", { url, ...options, headers }); return fetch(url, { ...options, headers }); }; + +const replicate = new Replicate({ fetch: customFetch }); ``` +### `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 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.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 | +| `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. + +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: + +```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 }); +``` + +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) +``` + +#### 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. + +### `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 +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, data } of replicate.stream(model, options)) { + if (event === "output") { + output.push(data); + } +} + +console.log(output.join("").trim()); +``` + +### 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. + ```js const response = await replicate.models.get(model_owner, model_name); ``` @@ -172,8 +479,94 @@ 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.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. + +```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. + ```js const response = await replicate.models.versions.list(model_owner, model_name); ``` @@ -210,6 +603,8 @@ const response = await replicate.models.versions.list(model_owner, model_name); ### `replicate.models.versions.get` +Get metadata for a specific version of a model. + ```js const response = await replicate.models.versions.get(model_owner, model_name, version_id); ``` @@ -233,6 +628,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); ``` @@ -243,14 +640,18 @@ 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); ``` | 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.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`) | @@ -353,6 +754,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); ``` @@ -385,6 +788,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(); ``` @@ -416,7 +821,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). @@ -461,6 +866,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); ``` @@ -492,6 +899,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); ``` @@ -523,6 +932,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(); ``` @@ -552,6 +963,226 @@ 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); +``` + +| 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.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.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 @@ -576,19 +1207,73 @@ 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); ``` -| 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. -## TypeScript +The method accepts an `AbortSignal` which can be used to cancel the request in flight. -The `Replicate` constructor and all `replicate.*` methods are fully typed. +### `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: +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 | +| -------------------- | ------ | ------------------------------------------------------------ | +| `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 + +### Predictions hanging in Next.js + +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: + +```js +replicate = new Replicate({/*...*/}) +replicate.fetch = (url, options) => { + return fetch(url, { ...options, cache: "no-store" }); +}; +``` + +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. diff --git a/biome.json b/biome.json new file mode 100644 index 00000000..ba1c236b --- /dev/null +++ b/biome.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.0.0/schema.json", + "files": { + "ignore": [".wrangler", "node_modules", "vendor/*"] + }, + "formatter": { + "indentStyle": "space", + "indentWidth": 2 + }, + "javascript": { + "formatter": { + "trailingComma": "es5" + } + }, + "linter": { + "enabled": true, + "rules": { + "a11y": { + "useAltText": "off", + "useMediaCaption": "off", + "noSvgWithoutTitle": "off" + }, + "complexity": { + "useLiteralKeys": "off", + "useOptionalChain": "off" + }, + "performance": { + "noAccumulatingSpread": "off" + }, + "suspicious": { + "noArrayIndexKey": "off", + "noExplicitAny": "off" + } + } + } +} diff --git a/index.d.ts b/index.d.ts index 0b59dcb4..d4e07840 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,25 +1,79 @@ -declare module 'replicate' { - type Status = 'starting' | 'processing' | 'succeeded' | 'failed' | 'canceled'; - type WebhookEventType = 'start' | 'output' | 'logs' | 'completed'; +declare module "replicate" { + type Status = "starting" | "processing" | "succeeded" | "failed" | "canceled" | "aborted"; + type Visibility = "public" | "private"; + type WebhookEventType = "start" | "output" | "logs" | "completed"; export interface ApiError extends Error { request: Request; response: Response; } + export interface FileOutput extends ReadableStream { + blob(): Promise; + url(): URL; + toString(): string; + } + + export interface Account { + type: "user" | "organization"; + username: string; + name: string; + github_url?: string; + avatar_url?: string; + } + export interface Collection { name: string; slug: string; description: string; + full_description: string | null; 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 FileObject { + id: string; + content_type: string; + size: number; + checksums: { + sha256: string; + }; + metadata: Record; + created_at: string; + expires_at: string | null; + urls: { + get: string; + }; + } + + export interface Hardware { + sku: string; + name: string; + } + export interface Model { url: string; owner: string; name: string; description?: string; - visibility: 'public' | 'private'; + visibility: "public" | "private"; + is_official: boolean; github_url?: string; paper_url?: string; license_url?: string; @@ -32,113 +86,308 @@ 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; - version: string; + model: string; + version: string | "hidden"; input: object; - output?: any; - source: 'api' | 'web'; - error?: any; + 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[]; created_at: string; - updated_at: string; + started_at?: string; completed_at?: string; urls: { 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"; - interface Page { + export interface Page { previous?: string; next?: string; results: T[]; } + export interface ServerSentEvent { + event: string; + data: string; + id?: string; + retry?: number; + } + + export interface WebhookSecret { + key: string; + } + export default class Replicate { - constructor(options: { - auth: string; + constructor(options?: { + auth?: string; userAgent?: string; baseUrl?: string; - fetch?: Function; + fetch?: ( + input: Request | string, + init?: RequestInit + ) => Promise; + fileEncodingStrategy?: FileEncodingStrategy; + useFileOutput?: boolean; }); auth: string; userAgent?: string; baseUrl?: string; - fetch: Function; + fetch: (input: Request | string, init?: RequestInit) => Promise; + fileEncodingStrategy: FileEncodingStrategy; run( - identifier: `${string}/${string}:${string}`, + identifier: `${string}/${string}` | `${string}/${string}:${string}`, options: { input: object; - wait?: { interval?: number; max_attempts?: number }; + wait?: + | { mode: "block"; interval?: number; timeout?: number } + | { mode: "poll"; interval?: number }; webhook?: string; webhook_events_filter?: WebhookEventType[]; signal?: AbortSignal; }, - progress?: (Prediction) => void + progress?: (prediction: Prediction) => void ): Promise; - request(route: string | URL, options: { - method?: string; - headers?: object | Headers; - params?: object; - data?: object; - }): Promise; + stream( + identifier: `${string}/${string}` | `${string}/${string}:${string}`, + options: { + input: object; + webhook?: string; + webhook_events_filter?: WebhookEventType[]; + signal?: AbortSignal; + useFileOutput?: boolean; + } + ): AsyncGenerator; + + request( + route: string | URL, + options: { + method?: string; + 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, - options: { + options?: { interval?: number; - max_attempts?: number; }, - stop?: (Prediction) => Promise + stop?: (prediction: Prediction) => Promise ): Promise; + accounts: { + 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: { + predictions: { + create( + deployment_owner: string, + deployment_name: string, + options: { + input: object; + /** @deprecated */ + stream?: boolean; + webhook?: string; + webhook_events_filter?: WebhookEventType[]; + wait?: number | boolean; + signal?: AbortSignal; + } + ): Promise; + }; + get( + deployment_owner: 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; + 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 } + ), + options?: { signal?: AbortSignal } + ): Promise; + delete( + deployment_owner: string, + deployment_name: string, + options?: { signal?: AbortSignal } + ): Promise; + list(options?: { signal?: AbortSignal }): Promise>; + }; + + files: { + create( + file: Blob | Buffer, + metadata?: Record, + options?: { signal?: AbortSignal } + ): Promise; + list(options?: { signal?: AbortSignal }): Promise>; + get( + file_id: string, + options?: { signal?: AbortSignal } + ): Promise; + delete( + file_id: string, + options?: { signal?: AbortSignal } + ): Promise; + }; + + hardware: { + list(options?: { signal?: AbortSignal }): Promise; }; models: { - get(model_owner: string, model_name: string): Promise; + get( + model_owner: string, + model_name: string, + options?: { signal?: AbortSignal } + ): Promise; + list(options?: { signal?: AbortSignal }): 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; + 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, + options?: { signal?: AbortSignal } + ): Promise>; }; predictions: { - create(options: { - version: string; - input: object; - stream?: boolean; - webhook?: string; - webhook_events_filter?: WebhookEventType[]; - }): Promise; - get(prediction_id: string): Promise; - cancel(prediction_id: string): Promise; - list(): Promise>; + create( + options: { + model?: string; + version?: string; + input: object; + /** @deprecated */ + stream?: boolean; + webhook?: string; + webhook_events_filter?: WebhookEventType[]; + wait?: boolean | number; + signal?: AbortSignal; + } & ({ version: string } | { model: string }) + ): Promise; + get( + prediction_id: string, + options?: { signal?: AbortSignal } + ): Promise; + cancel( + prediction_id: string, + options?: { signal?: AbortSignal } + ): Promise; + list(options?: { signal?: AbortSignal }): Promise>; }; trainings: { @@ -151,11 +400,49 @@ 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(options?: { signal?: AbortSignal }): Promise; + }; + }; }; } + + export function validateWebhook( + 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): { + percentage: number; + current: number; + total: number; + } | null; } diff --git a/index.js b/index.js index b147ea5e..3fc14dd5 100644 --- a/index.js +++ b/index.js @@ -1,11 +1,25 @@ -const ApiError = require('./lib/error'); - -const collections = require('./lib/collections'); -const models = require('./lib/models'); -const predictions = require('./lib/predictions'); -const trainings = require('./lib/trainings'); - -const packageJSON = require('./package.json'); +const ApiError = require("./lib/error"); +const ModelVersionIdentifier = require("./lib/identifier"); +const { createReadableStream, createFileOutput } = require("./lib/stream"); +const { + transform, + withAutomaticRetries, + validateWebhook, + parseProgressFromLogs, + streamAsyncIterator, +} = require("./lib/util"); + +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"); +const trainings = require("./lib/trainings"); +const webhooks = require("./lib/webhooks"); + +const packageJSON = require("./package.json"); /** * Replicate API client library @@ -30,33 +44,64 @@ 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` + * @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) { - if (!options.auth) { - throw new Error('Missing required parameter: auth'); - } - - this.auth = options.auth; + constructor(options = {}) { + 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'; + 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 ? false : true; + + this.accounts = { + current: accounts.current.bind(this), + }; this.collections = { list: collections.list.bind(this), get: collections.get.bind(this), }; + this.deployments = { + 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), + }, + }; + + 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), + }; + 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), }, + search: models.search.bind(this), }; this.predictions = { @@ -72,83 +117,103 @@ class Replicate { cancel: trainings.cancel.bind(this), list: trainings.list.bind(this), }; + + this.webhooks = { + default: { + secret: { + get: webhooks.default.secret.get.bind(this), + }, + }, + }; } /** * 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 - * @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 {{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 * @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) { - 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"' - ); - } + async run(ref, options, progress) { + const { wait = { mode: "block" }, signal, ...data } = options; - const { version } = match.groups; + const identifier = ModelVersionIdentifier.parse(ref); - let prediction = await this.predictions.create({ - ...data, - version, - }); + let prediction; + if (identifier.version) { + prediction = await this.predictions.create({ + ...data, + version: identifier.version, + 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: wait.mode === "block" ? wait.timeout ?? true : false, + }); + } else { + throw new Error("Invalid model version identifier"); + } // Call progress callback with the initial prediction object if (progress) { progress(prediction); } - 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 - } + const isDone = wait.mode === "block" && prediction.status !== "starting"; + if (!isDone) { + prediction = await this.wait( + prediction, + { interval: wait.mode === "poll" ? wait.interval : undefined }, + 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 + } + ); + } - return false; // continue polling - }); + if (signal && signal.aborted) { + prediction = await this.predictions.cancel(prediction.id); + } // 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}`); } - 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; + }); } /** @@ -160,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 */ @@ -171,38 +237,57 @@ 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, signal } = options; - Object.entries(params).forEach(([key, value]) => { + for (const [key, value] of Object.entries(params)) { url.searchParams.append(key, value); - }); + } - const headers = new Headers(); - headers.append('Authorization', `Token ${auth}`); - headers.append('Content-Type', 'application/json'); - headers.append('User-Agent', userAgent); + const headers = { + "Content-Type": "application/json", + "User-Agent": userAgent, + }; + if (auth) { + headers["Authorization"] = `Bearer ${auth}`; + } if (options.headers) { - options.headers.forEach((value, key) => { - headers.append(key, value); - }); + for (const [key, value] of Object.entries(options.headers)) { + headers[key] = value; + } + } + + 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); } const init = { method, headers, - body: data ? JSON.stringify(data) : undefined, + body, + signal, }; - const response = await this.fetch(url, init); + 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, + }); if (!response.ok) { const request = new Request(url, init); @@ -210,13 +295,66 @@ class Replicate { throw new ApiError( `Request to ${url} failed with status ${response.status} ${response.statusText}: ${responseText}.`, request, - response, + response ); } 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, + signal, + useFileOutput = this.useFileOutput, + ...data + } = options; + + const identifier = ModelVersionIdentifier.parse(ref); + + let prediction; + if (identifier.version) { + prediction = await this.predictions.create({ + ...data, + version: identifier.version, + }); + } else if (identifier.owner && identifier.name) { + prediction = await this.predictions.create({ + ...data, + model: `${identifier.owner}/${identifier.name}`, + }); + } else { + throw new Error("Invalid model version identifier"); + } + + if (prediction.urls && prediction.urls.stream) { + const stream = createReadableStream({ + url: prediction.urls.stream, + fetch: this.fetch, + options: { + useFileOutput, + ...(signal ? { signal } : {}), + }, + }); + + yield* streamAsyncIterator(stream); + } else { + throw new Error("Prediction does not support streaming"); + } + } + /** * Paginate through a list of results. * @@ -226,14 +364,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) { - const nextPage = () => this.request(response.next, { method: 'GET' }).then((r) => r.json()); - yield* this.paginate(nextPage); + if (response.next && !(options.signal && options.signal.aborted)) { + const nextPage = () => + this.request(response.next, { + method: "GET", + signal: options.signal, + }).then((r) => r.json()); + yield* this.paginate(nextPage, options); } } @@ -247,8 +391,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 @@ -257,13 +400,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; } @@ -271,35 +414,26 @@ 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 && options.interval) || 500; 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; } - 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 */ } - if (updatedPrediction.status === 'failed') { + if (updatedPrediction.status === "failed") { throw new Error(`Prediction failed: ${updatedPrediction.error}`); } @@ -308,3 +442,5 @@ class Replicate { } module.exports = Replicate; +module.exports.validateWebhook = validateWebhook; +module.exports.parseProgressFromLogs = parseProgressFromLogs; diff --git a/index.test.ts b/index.test.ts index 52e7c252..96f50db7 100644 --- a/index.test.ts +++ b/index.test.ts @@ -1,71 +1,221 @@ -import { expect, jest, test } from '@jest/globals'; -import Replicate, { Prediction } from 'replicate'; -import nock from 'nock'; -import fetch from 'cross-fetch'; +import { expect, jest, test } from "@jest/globals"; +import Replicate, { + ApiError, + FileOutput, + Model, + Prediction, + Training, + validateWebhook, + parseProgressFromLogs, +} 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"; + +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) => + unmatched.push({ req, options, body }); + + beforeEach(() => { + client = new Replicate({ auth: "test-token" }); -describe('Replicate client', () => { - let client: Replicate; + unmatched = []; + nock.emitter.on("no match", handleNoMatch); + }); - const BASE_URL = 'https://api.replicate.com/v1'; + afterEach(() => { + nock.emitter.off("no match", handleNoMatch); + expect(unmatched).toStrictEqual([]); - beforeEach(() => { - client = new Replicate({ auth: 'test-token' }); - client.fetch = fetch; + nock.abortPendingRequests(); + 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('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", () => { + process.env.REPLICATE_API_TOKEN = "test-token"; expect(() => { - new Replicate({ auth: undefined }); - }).toThrow(expected); + const clientWithImplicitAuth = new Replicate(); - expect(() => { - new Replicate({ auth: null }); - }).toThrow(expected); + expect(clientWithImplicitAuth.auth).toBe("test-token"); + }).not.toThrow(); + }); + test("Does not throw error if blank auth token is provided", () => { expect(() => { new Replicate({ auth: "" }); - }).toThrow(expected); + }).not.toThrow(); + }); + }); + + 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('collections.list', () => { - test('Calls the correct API route', async () => { + describe("account.get", () => { + 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) - .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, @@ -78,192 +228,444 @@ 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('predictions.create', () => { - test('Calls the correct API route with the correct payload', async () => { + describe("models.list", () => { + test("Paginates results", async () => { nock(BASE_URL) - .post('/predictions') + .get("/models") .reply(200, { - id: 'ufawqhfynnddngldkgtslldrkq', - version: - '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', - urls: { - get: 'https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq', - cancel: - 'https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel', + 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", () => { + 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", }, - created_at: '2022-04-26T22:13:06.224088Z', - started_at: null, - completed_at: null, - status: 'starting', + }, + }, + ].map((testCase) => ({ + ...testCase, + expectedResponse: { + 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", + }, + 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); + } + ); + + 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; + }); + + const prediction = await client.predictions.create({ + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { - text: 'Alice', + prompt: "Tell me a story", + data, }, - output: null, - error: null, - logs: null, - metrics: {}, }); - const prediction = await client.predictions.create({ - version: - '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', - input: { - text: 'Alice', - }, - webhook: 'http://test.host/webhook', - webhook_events_filter: [ 'output', 'completed' ], - }); - expect(prediction.id).toBe('ufawqhfynnddngldkgtslldrkq'); - }); - test('Passes stream parameter to API endpoint', async () => { - nock(BASE_URL) - .post('/predictions') - .reply(201, (_uri, body) => { - expect(body[ 'stream' ]).toBe(true); - return body - }) + 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", + 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; + return body; + }); - await client.predictions.create({ - version: - '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', - input: { - prompt: 'Tell me a story', - }, - stream: true - }); - }); + await client.predictions.create({ + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + input: { + prompt: "Tell me a story", + data, + }, + }); + + 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, + }, + }); + }).rejects.toThrowError( + expect.objectContaining({ + name: "ApiError", + message: expect.stringContaining("401"), + }) + ); + } + ); - 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.assertions(2); + expect.hasAssertions(); await client.predictions.create({ - version: '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { text: null, }, }); } 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. + }); + + 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', () => { - 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', + 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 () => { + 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"); }); - // Add more tests for error handling, edge cases, etc. }); - 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', + 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, @@ -272,57 +674,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', + 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, }); @@ -331,331 +734,1558 @@ 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: '{version}', - 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', + 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, }); - const results: Prediction[] = []; + const results: Training[] = []; for await (const batch of client.paginate(client.trainings.list)) { results.push(...batch); } expect(results).toEqual([ - { id: 'ufawqhfynnddngldkgtslldrkq' }, - { id: 'rrr4z55ocneqzikepnug6xezpe' }, + { id: "ufawqhfynnddngldkgtslldrkq" }, + { id: "rrr4z55ocneqzikepnug6xezpe" }, ]); // Add more tests for error handling, edge cases, etc. }); }); - describe('run', () => { - test('Calls the correct API routes', async () => { - let firstPollingRequest = true; - + describe("deployments.predictions.create", () => { + test("Calls the correct API route with the correct payload", async () => { nock(BASE_URL) - .post('/predictions') - .reply(201, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'starting', - }) - .get('/predictions/ufawqhfynnddngldkgtslldrkq') - .twice() - .reply(200, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'processing', - }) - .get('/predictions/ufawqhfynnddngldkgtslldrkq') + .post("/deployments/replicate/greeter/predictions") .reply(200, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'succeeded', - output: 'Goodbye!', + id: "mfrgcyzzme2wkmbwgzrgmntcg", + 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-09-10T09:44:22.165836Z", + started_at: null, + completed_at: null, + status: "starting", + input: { + text: "Alice", + }, + output: null, + error: null, + logs: null, + metrics: {}, }); - - const progress = jest.fn(); - - const output = await client.run( - 'owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + const prediction = await client.deployments.predictions.create( + "replicate", + "greeter", { - input: { text: 'Hello, world!' }, - wait: { interval: 1 } - }, - progress + 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. + }); - expect(output).toBe('Goodbye!'); + 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", + min_instances: 1, + max_instances: 5, + }, + }, + }); - expect(progress).toHaveBeenNthCalledWith(1, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'starting', - }); + const deployment = await client.deployments.get( + "acme", + "my-app-image-generator" + ); - expect(progress).toHaveBeenNthCalledWith(2, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'processing', - }); + 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. + }); - expect(progress).toHaveBeenNthCalledWith(3, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'processing', - }); + 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", + min_instances: 1, + max_instances: 5, + }, + }, + }); - expect(progress).toHaveBeenNthCalledWith(4, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'succeeded', - output: 'Goodbye!', + 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(progress).toHaveBeenCalledTimes(4); + 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. + }); - test('Does not throw an error for identifier containing hyphen and full stop', async () => { + describe("deployments.update", () => { + test("Calls the correct API route with the correct payload", async () => { nock(BASE_URL) - .post('/predictions') - .reply(200, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'processing', - }) - .get('/predictions/ufawqhfynnddngldkgtslldrkq') + .patch("/deployments/acme/my-app-image-generator") .reply(200, { - id: 'ufawqhfynnddngldkgtslldrkq', - status: 'succeeded', - output: 'foobar', + 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", + min_instances: 3, + max_instances: 10, + }, + }, }); - 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!' } } - - await expect(client.run('owner/model:invalid', options)).rejects.toThrow(); - + 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.min_instances).toBe(3); + expect(deployment.current_release.configuration.max_instances).toBe(10); + }); + // 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) + .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) + .post("/models/meta/meta-llama-3-70b-instruct/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.predictions.create({ + model: "meta/meta-llama-3-70b-instruct", + 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("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", + content_type: "application/octet-stream", + size: 1024, + checksums: { + 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"); + } + }); + }); + + describe("files.get", () => { + test("Calls the correct API route", async () => { + nock(BASE_URL) + .get("/files/123") + .reply(200, { + id: "123", + content_type: "application/octet-stream", + size: 1024, + checksums: { + 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"); + }); + }); + + describe("files.list", () => { + test("Calls the correct API route", async () => { + nock(BASE_URL) + .get("/files") + .reply(200, { + next: null, + previous: null, + results: [ + { + id: "123", + content_type: "application/octet-stream", + size: 1024, + checksums: { + 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) + .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("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) + .post("/predictions") + .reply(201, { + id: "ufawqhfynnddngldkgtslldrkq", + status: "starting", + logs: null, + }) + .get("/predictions/ufawqhfynnddngldkgtslldrkq") + .reply(200, { + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", + logs: [ + "Using seed: 12345", + "0%| | 0/5 [00:00 { + const progress = parseProgressFromLogs(prediction); + callback(prediction, progress); + } + ); + + expect(output).toBe("Goodbye!"); + + expect(callback).toHaveBeenNthCalledWith( + 1, + { + id: "ufawqhfynnddngldkgtslldrkq", + status: "starting", + logs: null, + }, + null + ); + + expect(callback).toHaveBeenNthCalledWith( + 2, + { + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", + logs: expect.any(String), + }, + { + percentage: 0.4, + current: 2, + total: 5, + } + ); + + expect(callback).toHaveBeenNthCalledWith( + 3, + { + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", + logs: expect.any(String), + }, + { + percentage: 0.8, + current: 4, + total: 5, + } + ); + + expect(callback).toHaveBeenNthCalledWith( + 4, + { + id: "ufawqhfynnddngldkgtslldrkq", + status: "succeeded", + logs: expect.any(String), + output: "Goodbye!", + }, + { + percentage: 1.0, + current: 5, + total: 5, + } + ); + + expect(callback).toHaveBeenCalledTimes(4); + }); + + test("Calls the correct API routes for a model", async () => { + 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: { mode: "poll", 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") + .reply(200, { + id: "ufawqhfynnddngldkgtslldrkq", + status: "processing", + }) + .get("/predictions/ufawqhfynnddngldkgtslldrkq") + .reply(200, { + id: "ufawqhfynnddngldkgtslldrkq", + status: "succeeded", + output: "foobar", + }); + + await expect( + 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 = { + wait: { mode: "poll" } as { mode: "poll" }, + 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", + { + wait: { mode: "poll" }, + 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; + 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', - status: 'processing', + id: "ufawqhfynnddngldkgtslldrkq", + status: "starting", }) .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', + const onProgress = jest.fn(); + const output = await client.run( + "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", { - input: { text: 'Hello, world!' }, + wait: { mode: "poll" }, + input: { text: "Hello, world!" }, signal, - } - ) + }, + onProgress + ); + expect(body).toBeDefined(); + expect(body?.["signal"]).toBeUndefined(); expect(signal.aborted).toBe(true); + expect(output).toBeUndefined(); + + expect(onProgress).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + status: "starting", + }) + ); + expect(onProgress).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + status: "processing", + }) + ); + expect(onProgress).toHaveBeenNthCalledWith( + 3, + expect.objectContaining({ + status: "canceled", + }) + ); 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", + { + wait: { mode: "poll" }, + 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", + { + wait: { mode: "poll" }, + 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", + { + wait: { mode: "poll" }, + 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", + { + wait: { mode: "poll" }, + 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", () => { + 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"; + 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); + } + }); + + 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. }); // Continue with tests for other methods + + describe("createReadableStream", () => { + 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}`); + } + return new Response(body, { status }); + }); + return createReadableStream({ + url: streamEndpoint, + fetch: fetch as any, + options, + }); + } + + 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 () => { + // 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("|"); + 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 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 }); + + // Wait for both promises to resolve. + }); + + test("supports the server writing data in a complete mess", async () => { + 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. + 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("|"); + + 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 }); + }); + + 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 }); + }); + + 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/integration/browser/.npmrc b/integration/browser/.npmrc new file mode 100644 index 00000000..77750405 --- /dev/null +++ b/integration/browser/.npmrc @@ -0,0 +1,3 @@ +package-lock=false +audit=false +fund=false diff --git a/integration/browser/README.md b/integration/browser/README.md new file mode 100644 index 00000000..575d779a --- /dev/null +++ b/integration/browser/README.md @@ -0,0 +1,39 @@ +# Browser integration tests + +Uses [`playwright`](https://playwright.dev/docs) to run a basic integration test against the three most common browser engines, Firefox, Chromium and WebKit. + +It uses the `replicate/canary` model for the moment, which requires a Replicate API token available in the environment under `REPLICATE_API_TOKEN`. + +The entire suite is a single `main()` function that calls a single model exercising the streaming API. + +The test uses `esbuild` within the test generate a browser friendly version of the `index.js` file which is loaded into the given browser and calls the `main()` function asserting the response content. + +## CORS + +The Replicate API doesn't support Cross Origin Resource Sharing at this time. We work around this in Playwright by intercepting the request in a `page.route` handler. We don't modify the request/response, but this seems to work around the restriction. + +## Setup + + npm install + +## Local + +The following command will run the tests across all browsers. + + npm test + +To run against the default browser (chromium) run: + + npm exec playwright test + +Or, specify a browser with: + + npm exec playwright test --browser firefox + +## Debugging + +Running `playwright test` with the `--debug` flag opens a browser window with a debugging interface, and a breakpoint set at the start of the test. It can also be connected directly to VSCode. + + npm exec playwright test --debug + +The browser.js file is injected into the page via a script tag, to be able to set breakpoints in this file you'll need to use a `debugger` statement and open the devtools in the spawned browser window before continuing the test suite. diff --git a/integration/browser/index.js b/integration/browser/index.js new file mode 100644 index 00000000..9c0ae654 --- /dev/null +++ b/integration/browser/index.js @@ -0,0 +1,22 @@ +import Replicate from "replicate"; + +/** + * @param {string} - token the REPLICATE_API_TOKEN + */ +window.main = async (token) => { + const replicate = new Replicate({ auth: token }); + const stream = replicate.stream( + "replicate/canary:30e22229542eb3f79d4f945dacb58d32001b02cc313ae6f54eef27904edf3272", + { + input: { + text: "Betty Browser", + }, + } + ); + + const output = []; + for await (const event of stream) { + output.push(String(event)); + } + return output.join(""); +}; diff --git a/integration/browser/index.test.js b/integration/browser/index.test.js new file mode 100644 index 00000000..380d8138 --- /dev/null +++ b/integration/browser/index.test.js @@ -0,0 +1,34 @@ +import { test, expect } from "@playwright/test"; +import { build } from "esbuild"; + +// Convert the source file from commonjs to a browser script. +const result = await build({ + entryPoints: ["index.js"], + bundle: true, + platform: "browser", + external: ["node:crypto"], + write: false, +}); +const source = new TextDecoder().decode(result.outputFiles[0].contents); + +// https://playwright.dev/docs/network#modify-requests + +test("browser", async ({ page }) => { + // Patch the API endpoint to work around CORS for now. + await page.route( + "https://api.replicate.com/v1/predictions", + async (route) => { + // Fetch original response. + const response = await route.fetch(); + // Add a prefix to the title. + return route.fulfill({ response }); + } + ); + + await page.addScriptTag({ content: source }); + const result = await page.evaluate( + (token) => window.main(token), + [process.env.REPLICATE_API_TOKEN] + ); + expect(result).toBe("hello there, Betty Browser"); +}); diff --git a/integration/browser/package.json b/integration/browser/package.json new file mode 100644 index 00000000..91ba1791 --- /dev/null +++ b/integration/browser/package.json @@ -0,0 +1,19 @@ +{ + "name": "replicate-app-browser", + "private": true, + "version": "0.0.0", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "test": "playwright test --browser all" + }, + "license": "ISC", + "dependencies": { + "replicate": "../../" + }, + "devDependencies": { + "@playwright/test": "^1.42.1", + "esbuild": "^0.20.1" + } +} diff --git a/integration/browser/playwright.config.ts b/integration/browser/playwright.config.ts new file mode 100644 index 00000000..142a1777 --- /dev/null +++ b/integration/browser/playwright.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({}); diff --git a/integration/bun/.gitignore b/integration/bun/.gitignore new file mode 100644 index 00000000..3c3629e6 --- /dev/null +++ b/integration/bun/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/integration/bun/bun.lockb b/integration/bun/bun.lockb new file mode 100755 index 00000000..dd45eaf0 Binary files /dev/null and b/integration/bun/bun.lockb differ diff --git a/integration/bun/index.test.ts b/integration/bun/index.test.ts new file mode 100644 index 00000000..f0665a6d --- /dev/null +++ b/integration/bun/index.test.ts @@ -0,0 +1,23 @@ +import { expect, test } from "bun:test"; +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(); + expect(output).toEqual("hello there, Brünnhilde Bun"); +}); diff --git a/integration/bun/index.ts b/integration/bun/index.ts new file mode 100644 index 00000000..853cc286 --- /dev/null +++ b/integration/bun/index.ts @@ -0,0 +1,25 @@ +import Replicate from "replicate"; + +const replicate = new Replicate({ + auth: process.env.REPLICATE_API_TOKEN, +}); + +export default async function main() { + const model = + "replicate/canary:30e22229542eb3f79d4f945dacb58d32001b02cc313ae6f54eef27904edf3272"; + const options = { + input: { + text: "Brünnhilde Bun", + }, + }; + const output = []; + + for await (const { event, data } of replicate.stream(model, options)) { + console.log({ event, data }); + if (event === "output") { + output.push(data); + } + } + + return output.join("").trim(); +} diff --git a/integration/bun/package.json b/integration/bun/package.json new file mode 100644 index 00000000..f4ba2029 --- /dev/null +++ b/integration/bun/package.json @@ -0,0 +1,14 @@ +{ + "name": "replicate-app-bun", + "version": "0.0.0", + "private": true, + "description": "Bun integration tests", + "main": "index.js", + "type": "module", + "dependencies": { + "replicate": "file:../../" + }, + "devDependencies": { + "@types/bun": "latest" + } +} diff --git a/integration/bun/tsconfig.json b/integration/bun/tsconfig.json new file mode 100644 index 00000000..2ffa937f --- /dev/null +++ b/integration/bun/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "nodenext", + "inlineSourceMap": true, + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "skipDefaultLibCheck": true, + "skipLibCheck": true + } +} diff --git a/integration/cloudflare-worker/.gitignore b/integration/cloudflare-worker/.gitignore new file mode 100644 index 00000000..8394e05f --- /dev/null +++ b/integration/cloudflare-worker/.gitignore @@ -0,0 +1,4 @@ +.wrangler +node_modules +.dev.vars + diff --git a/integration/cloudflare-worker/.npmrc b/integration/cloudflare-worker/.npmrc new file mode 100644 index 00000000..77750405 --- /dev/null +++ b/integration/cloudflare-worker/.npmrc @@ -0,0 +1,3 @@ +package-lock=false +audit=false +fund=false diff --git a/integration/cloudflare-worker/index.js b/integration/cloudflare-worker/index.js new file mode 100644 index 00000000..32ec9fc8 --- /dev/null +++ b/integration/cloudflare-worker/index.js @@ -0,0 +1,33 @@ +import Replicate from "replicate"; + +export default { + async fetch(_request, env, _ctx) { + 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({ + 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); + } catch (err) { + return Response("", { status: 500 }); + } + }, +}; diff --git a/integration/cloudflare-worker/index.test.js b/integration/cloudflare-worker/index.test.js new file mode 100644 index 00000000..932d8f5d --- /dev/null +++ b/integration/cloudflare-worker/index.test.js @@ -0,0 +1,41 @@ +// https://developers.cloudflare.com/workers/wrangler/api/#unstable_dev +import { unstable_dev as dev } from "wrangler"; +import { test, after, before, describe } from "node:test"; +import assert from "node:assert"; + +describe("CloudFlare Worker", () => { + /** @type {import("wrangler").UnstableDevWorker} */ + let worker; + + 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(); + }); + + test("worker streams back a response", { timeout: 5000 }, async () => { + const resp = await worker.fetch(); + const text = await resp.text(); + + 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"), + `expected body to include "Colin CloudFlare" but got ${JSON.stringify( + text + )}` + ); + }); +}); diff --git a/integration/cloudflare-worker/package.json b/integration/cloudflare-worker/package.json new file mode 100644 index 00000000..94ae94c1 --- /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.32.0" + } +} diff --git a/integration/cloudflare-worker/wrangler.toml b/integration/cloudflare-worker/wrangler.toml new file mode 100644 index 00000000..4fbf2093 --- /dev/null +++ b/integration/cloudflare-worker/wrangler.toml @@ -0,0 +1,4 @@ +name = "cloudflare-worker" +main = "index.js" +compatibility_date = "2024-03-04" +compatibility_flags = [ "nodejs_compat" ] diff --git a/integration/commonjs/.npmrc b/integration/commonjs/.npmrc new file mode 100644 index 00000000..77750405 --- /dev/null +++ b/integration/commonjs/.npmrc @@ -0,0 +1,3 @@ +package-lock=false +audit=false +fund=false diff --git a/integration/commonjs/index.js b/integration/commonjs/index.js new file mode 100644 index 00000000..6d8c436b --- /dev/null +++ b/integration/commonjs/index.js @@ -0,0 +1,17 @@ +const Replicate = require("replicate"); + +const replicate = new Replicate({ + auth: process.env.REPLICATE_API_TOKEN, +}); + +module.exports = async function main() { + 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 new file mode 100644 index 00000000..afc3c014 --- /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 there, Claire CommonJS"); +}); diff --git a/integration/commonjs/package.json b/integration/commonjs/package.json new file mode 100644 index 00000000..7fb6fc8a --- /dev/null +++ b/integration/commonjs/package.json @@ -0,0 +1,13 @@ +{ + "name": "replicate-app-commonjs", + "version": "0.0.0", + "private": true, + "description": "CommonJS integration tests", + "main": "index.js", + "scripts": { + "test": "node --test ./index.test.js" + }, + "dependencies": { + "replicate": "file:../../" + } +} \ No newline at end of file diff --git a/integration/deno/deno.json b/integration/deno/deno.json new file mode 100644 index 00000000..6b3e0a05 --- /dev/null +++ b/integration/deno/deno.json @@ -0,0 +1,5 @@ +{ + "imports": { + "replicate": "npm:replicate" + } +} diff --git a/integration/deno/deno.lock b/integration/deno/deno.lock new file mode 100644 index 00000000..057d6122 --- /dev/null +++ b/integration/deno/deno.lock @@ -0,0 +1,88 @@ +{ + "version": "3", + "packages": { + "specifiers": { + "jsr:@std/assert": "jsr:@std/assert@0.226.0", + "jsr:@std/internal@^1.0.0": "jsr:@std/internal@1.0.1", + "npm:replicate": "npm:replicate@0.31.0" + }, + "jsr": { + "@std/assert@0.226.0": { + "integrity": "0dfb5f7c7723c18cec118e080fec76ce15b4c31154b15ad2bd74822603ef75b3", + "dependencies": [ + "jsr:@std/internal@^1.0.0" + ] + }, + "@std/internal@1.0.1": { + "integrity": "6f8c7544d06a11dd256c8d6ba54b11ed870aac6c5aeafff499892662c57673e6" + } + }, + "npm": { + "abort-controller@3.0.0": { + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "event-target-shim@5.0.1" + } + }, + "base64-js@1.5.1": { + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dependencies": {} + }, + "buffer@6.0.3": { + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dependencies": { + "base64-js": "base64-js@1.5.1", + "ieee754": "ieee754@1.2.1" + } + }, + "event-target-shim@5.0.1": { + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dependencies": {} + }, + "events@3.3.0": { + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dependencies": {} + }, + "ieee754@1.2.1": { + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dependencies": {} + }, + "process@0.11.10": { + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dependencies": {} + }, + "readable-stream@4.5.2": { + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "abort-controller@3.0.0", + "buffer": "buffer@6.0.3", + "events": "events@3.3.0", + "process": "process@0.11.10", + "string_decoder": "string_decoder@1.3.0" + } + }, + "replicate@0.31.0": { + "integrity": "sha512-BQl52LqndfY2sLQ384jyspaJI5ia301+IN1zOBbKZa2dB5EnayUxS0ynFueOdwo/4qRfQTR0GKJwpKFK/mb3zw==", + "dependencies": { + "readable-stream": "readable-stream@4.5.2" + } + }, + "safe-buffer@5.2.1": { + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dependencies": {} + }, + "string_decoder@1.3.0": { + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "safe-buffer@5.2.1" + } + } + } + }, + "remote": {}, + "workspace": { + "dependencies": [ + "npm:replicate" + ] + } +} diff --git a/integration/deno/index.test.ts b/integration/deno/index.test.ts new file mode 100644 index 00000000..e944d770 --- /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 there, Deno the dinosaur" }); + }, +}); diff --git a/integration/deno/index.ts b/integration/deno/index.ts new file mode 100644 index 00000000..a235699b --- /dev/null +++ b/integration/deno/index.ts @@ -0,0 +1,17 @@ +import Replicate from "replicate"; + +const replicate = new Replicate({ + auth: Deno.env.get("REPLICATE_API_TOKEN"), +}); + +export default async function main() { + const output = (await replicate.run( + "replicate/canary:30e22229542eb3f79d4f945dacb58d32001b02cc313ae6f54eef27904edf3272", + { + input: { + text: "Deno the dinosaur", + }, + } + )) as string[]; + return output.join("").trim(); +} diff --git a/integration/deno/package.json b/integration/deno/package.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/integration/deno/package.json @@ -0,0 +1 @@ +{} diff --git a/integration/esm/.npmrc b/integration/esm/.npmrc new file mode 100644 index 00000000..77750405 --- /dev/null +++ b/integration/esm/.npmrc @@ -0,0 +1,3 @@ +package-lock=false +audit=false +fund=false diff --git a/integration/esm/index.js b/integration/esm/index.js new file mode 100644 index 00000000..48feccb7 --- /dev/null +++ b/integration/esm/index.js @@ -0,0 +1,17 @@ +import Replicate from "replicate"; + +const replicate = new Replicate({ + auth: process.env.REPLICATE_API_TOKEN, +}); + +export default async function main() { + 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 new file mode 100644 index 00000000..c95ab8b0 --- /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 there, Evelyn ESM"); +}); diff --git a/integration/esm/package.json b/integration/esm/package.json new file mode 100644 index 00000000..51076d79 --- /dev/null +++ b/integration/esm/package.json @@ -0,0 +1,14 @@ +{ + "name": "replicate-app-esm", + "version": "0.0.0", + "private": true, + "description": "ESM (ECMAScript Modules) integration tests", + "main": "index.js", + "type": "module", + "scripts": { + "test": "node --test ./index.test.js" + }, + "dependencies": { + "replicate": "file:../../" + } +} \ No newline at end of file diff --git a/integration/next/.npmrc b/integration/next/.npmrc new file mode 100644 index 00000000..77750405 --- /dev/null +++ b/integration/next/.npmrc @@ -0,0 +1,3 @@ +package-lock=false +audit=false +fund=false diff --git a/integration/next/middleware.ts b/integration/next/middleware.ts new file mode 100644 index 00000000..090adfd4 --- /dev/null +++ b/integration/next/middleware.ts @@ -0,0 +1,17 @@ +// NOTE: This file currently doesn't do anything other than +// validate that `next build` works as expected. We can +// extend it in future to support actual middleware tests. +import { NextRequest } from "next/server"; +import Replicate from "replicate"; + +// Limit the middleware to paths starting with `/api/` +export const config = { + matcher: "/api/:function*", +}; + +const replicate = new Replicate(); + +export function middleware(request: NextRequest) { + const output = replicate.run("foo/bar"); + return Response.json({ output }, 200); +} diff --git a/integration/next/package.json b/integration/next/package.json new file mode 100644 index 00000000..6101a71b --- /dev/null +++ b/integration/next/package.json @@ -0,0 +1,14 @@ +{ + "name": "replicate-next", + "version": "0.0.0", + "private": true, + "scripts": { + "dev": "next", + "build": "rm -rf .next && next build", + "start": "next start" + }, + "dependencies": { + "next": "^14.2.3", + "replicate": "../../" + } +} diff --git a/integration/next/pages/index.js b/integration/next/pages/index.js new file mode 100644 index 00000000..09124388 --- /dev/null +++ b/integration/next/pages/index.js @@ -0,0 +1,5 @@ +export default () => ( +
+

Welcome to Next.js

+
+); diff --git a/integration/typescript/.npmrc b/integration/typescript/.npmrc new file mode 100644 index 00000000..77750405 --- /dev/null +++ b/integration/typescript/.npmrc @@ -0,0 +1,3 @@ +package-lock=false +audit=false +fund=false diff --git a/integration/typescript/index.test.ts b/integration/typescript/index.test.ts new file mode 100644 index 00000000..0f0f356b --- /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 there, Tracy TypeScript"); +}); diff --git a/integration/typescript/index.ts b/integration/typescript/index.ts new file mode 100644 index 00000000..1b276bc9 --- /dev/null +++ b/integration/typescript/index.ts @@ -0,0 +1,17 @@ +import Replicate from "replicate"; + +const replicate = new Replicate({ + auth: process.env.REPLICATE_API_TOKEN, +}); + +export default async function main() { + const output = (await replicate.run( + "replicate/canary:30e22229542eb3f79d4f945dacb58d32001b02cc313ae6f54eef27904edf3272", + { + input: { + text: "Tracy TypeScript", + }, + } + )) as string[]; + return output.join("").trim(); +} diff --git a/integration/typescript/package.json b/integration/typescript/package.json new file mode 100644 index 00000000..4adae998 --- /dev/null +++ b/integration/typescript/package.json @@ -0,0 +1,16 @@ +{ + "name": "replicate-app-typescript", + "version": "0.0.0", + "private": true, + "description": "TypeScript integration tests", + "main": "index.js", + "type": "module", + "scripts": { + "test": "tsc && node --test ./dist/index.test.js" + }, + "dependencies": { + "@types/node": "^20.11.0", + "replicate": "file:../../", + "typescript": "^5.3.3" + } +} \ No newline at end of file diff --git a/integration/typescript/tsconfig.json b/integration/typescript/tsconfig.json new file mode 100644 index 00000000..4b733f2c --- /dev/null +++ b/integration/typescript/tsconfig.json @@ -0,0 +1,109 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2018", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "nodenext", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/jest.config.js b/jest.config.js index 36d66b5b..058816ab 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,11 +1,15 @@ // eslint-disable-next-line jsdoc/valid-types /** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', + preset: "ts-jest", + testEnvironment: "node", + testPathIgnorePatterns: ["integration"], transform: { - '^.+\\.ts?$': ['ts-jest', { - tsconfig: 'tsconfig.json' - }], + "^.+\\.ts?$": [ + "ts-jest", + { + tsconfig: "tsconfig.json", + }, + ], }, }; diff --git a/jsconfig.json b/jsconfig.json index 6ea54689..b83b3f36 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,15 +1,15 @@ { - "compilerOptions": { - "checkJs": true, - "module": "ESNext", - "moduleResolution": "Node", - "target": "ES2020", - "resolveJsonModule": true, - "strictNullChecks": true, - "strictFunctionTypes": true - }, - "exclude": [ - "node_modules", - "**/node_modules/*" - ] + "compilerOptions": { + "checkJs": true, + "module": "ESNext", + "moduleResolution": "Node", + "target": "ES2020", + "resolveJsonModule": true, + "strictNullChecks": true, + "strictFunctionTypes": true + }, + "exclude": [ + "node_modules", + "**/node_modules/*" + ] } diff --git a/lib/accounts.js b/lib/accounts.js new file mode 100644 index 00000000..72a94afa --- /dev/null +++ b/lib/accounts.js @@ -0,0 +1,19 @@ +/** + * Get the current account + * + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal + * @returns {Promise} Resolves with the current account + */ +async function getCurrentAccount({ signal } = {}) { + const response = await this.request("/account", { + method: "GET", + signal, + }); + + return response.json(); +} + +module.exports = { + current: getCurrentAccount, +}; diff --git a/lib/collections.js b/lib/collections.js index b40d0913..7b8e8f11 100644 --- a/lib/collections.js +++ b/lib/collections.js @@ -2,11 +2,14 @@ * Fetch a model collection * * @param {string} collection_slug - Required. The slug of the collection. See http://replicate.com/collections + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} - Resolves with the collection data */ -async function getCollection(collection_slug) { +async function getCollection(collection_slug, { signal } = {}) { const response = await this.request(`/collections/${collection_slug}`, { - method: 'GET', + 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() { - const response = await this.request('/collections', { - method: 'GET', +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 new file mode 100644 index 00000000..d45c4f39 --- /dev/null +++ b/lib/deployments.js @@ -0,0 +1,197 @@ +const { transformFileInputs } = require("./util"); + +/** + * 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 {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, signal, ...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 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"; + } + } + + const response = await this.request( + `/deployments/${deployment_owner}/${deployment_name}/predictions`, + { + method: "POST", + headers, + data: { + ...data, + input: await transformFileInputs( + this, + input, + this.fileEncodingStrategy + ), + }, + signal, + } + ); + + 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 + * @param {object] [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal + * @returns {Promise} Resolves with the deployment data + */ +async function getDeployment( + deployment_owner, + deployment_name, + { signal } = {} +) { + const response = await this.request( + `/deployments/${deployment_owner}/${deployment_name}`, + { + method: "GET", + signal, + } + ); + + 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} 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, { signal } = {}) { + const response = await this.request("/deployments", { + method: "POST", + data: deployment_config, + signal, + }); + + 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. + * @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, + { signal } = {} +) { + const response = await this.request( + `/deployments/${deployment_owner}/${deployment_name}`, + { + method: "PATCH", + data: deployment_config, + signal, + } + ); + + 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 + * @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, + { signal } = {} +) { + const response = await this.request( + `/deployments/${deployment_owner}/${deployment_name}`, + { + method: "DELETE", + signal, + } + ); + + return response.status === 204; +} + +/** + * List all deployments + * + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal + * @returns {Promise} - Resolves with a page of deployments + */ +async function listDeployments({ signal } = {}) { + const response = await this.request("/deployments", { + method: "GET", + signal, + }); + + return response.json(); +} + +module.exports = { + predictions: { + create: createPrediction, + }, + get: getDeployment, + create: createDeployment, + update: updateDeployment, + list: listDeployments, + delete: deleteDeployment, +}; diff --git a/lib/error.js b/lib/error.js index 81dbe473..cf05cd12 100644 --- a/lib/error.js +++ b/lib/error.js @@ -12,7 +12,7 @@ class ApiError extends Error { */ constructor(message, request, response) { super(message); - this.name = 'ApiError'; + this.name = "ApiError"; this.request = request; this.response = response; } diff --git a/lib/files.js b/lib/files.js new file mode 100644 index 00000000..c8101398 --- /dev/null +++ b/lib/files.js @@ -0,0 +1,102 @@ +/** + * Create a file + * + * @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 = {}, { signal } = {}) { + 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()}`; + 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"); + } + + 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", + }, + signal, + }); + + return response.json(); +} + +/** + * List all files + * + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal + * @returns {Promise} - Resolves with the files data + */ +async function listFiles({ signal } = {}) { + const response = await this.request("/files", { + method: "GET", + signal, + }); + + return response.json(); +} + +/** + * 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, { signal } = {}) { + const response = await this.request(`/files/${file_id}`, { + method: "GET", + signal, + }); + + return response.json(); +} + +/** + * 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, { signal } = {}) { + const response = await this.request(`/files/${file_id}`, { + method: "DELETE", + signal, + }); + + return response.status === 204; +} + +module.exports = { + create: createFile, + list: listFiles, + get: getFile, + delete: deleteFile, +}; diff --git a/lib/hardware.js b/lib/hardware.js new file mode 100644 index 00000000..e981b1fa --- /dev/null +++ b/lib/hardware.js @@ -0,0 +1,19 @@ +/** + * List hardware + * + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal + * @returns {Promise} Resolves with the array of hardware + */ +async function listHardware({ signal } = {}) { + const response = await this.request("/hardware", { + method: "GET", + signal, + }); + + return response.json(); +} + +module.exports = { + list: listHardware, +}; diff --git a/lib/identifier.js b/lib/identifier.js new file mode 100644 index 00000000..86e23ee4 --- /dev/null +++ b/lib/identifier.js @@ -0,0 +1,39 @@ +/* + * 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; diff --git a/lib/models.js b/lib/models.js index 373ed230..4a3fcdde 100644 --- a/lib/models.js +++ b/lib/models.js @@ -3,11 +3,14 @@ * * @param {string} model_owner - Required. The name of the user or organization that owns the model * @param {string} model_name - Required. The name of the model + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with the model data */ -async function getModel(model_owner, model_name) { +async function getModel(model_owner, model_name, { signal } = {}) { const response = await this.request(`/models/${model_owner}/${model_name}`, { - method: 'GET', + method: "GET", + signal, }); return response.json(); @@ -18,12 +21,18 @@ 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) { - const response = await this.request(`/models/${model_owner}/${model_name}/versions`, { - method: 'GET', - }); +async function listModelVersions(model_owner, model_name, { signal } = {}) { + const response = await this.request( + `/models/${model_owner}/${model_name}/versions`, + { + method: "GET", + signal, + } + ); return response.json(); } @@ -34,20 +43,97 @@ 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', + method: "GET", + signal, } ); return response.json(); } +/** + * 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({ signal } = {}) { + const response = await this.request("/models", { + method: "GET", + signal, + }); + + 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. + * @param {AbortSignal} [options.signal] - An optional AbortSignal + * @returns {Promise} Resolves with the model version data + */ +async function createModel(model_owner, 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(); +} + +/** + * 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, { signal } = {}) { + const response = await this.request("/models", { + method: "QUERY", + headers: { + "Content-Type": "text/plain", + }, + data: query, + signal, + }); + + return response.json(); +} + module.exports = { get: getModel, + list: listModels, + create: createModel, versions: { list: listModelVersions, get: getModelVersion }, + search, }; diff --git a/lib/predictions.js b/lib/predictions.js index 32b18ade..708d04b3 100644 --- a/lib/predictions.js +++ b/lib/predictions.js @@ -1,30 +1,73 @@ +const { transformFileInputs } = require("./util"); + /** * 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`) - * @param {boolean} [options.stream] - Whether to stream the prediction output. 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 + * @param {AbortSignal} [options.signal] - An optional AbortSignal * @returns {Promise} Resolves with the created prediction */ async function createPrediction(options) { - const { stream, ...data } = options; + const { model, version, input, wait, signal, ...data } = options; if (data.webhook) { try { // 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', - data: { ...data, stream }, - }); + 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( + this, + input, + this.fileEncodingStrategy + ), + version, + }, + signal, + }); + } else if (model) { + response = await this.request(`/models/${model}/predictions`, { + method: "POST", + headers, + data: { + ...data, + input: await transformFileInputs( + this, + input, + this.fileEncodingStrategy + ), + }, + signal, + }); + } else { + throw new Error("Either model or version must be specified"); + } return response.json(); } @@ -33,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', + method: "GET", + signal, }); return response.json(); @@ -47,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', + method: "POST", + signal, }); return response.json(); @@ -60,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() { - const response = await this.request('/predictions', { - method: 'GET', +async function listPredictions({ signal } = {}) { + const response = await this.request("/predictions", { + method: "GET", + signal, }); return response.json(); diff --git a/lib/stream.js b/lib/stream.js new file mode 100644 index 00000000..802a98e1 --- /dev/null +++ b/lib/stream.js @@ -0,0 +1,184 @@ +// 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"); +const { TextDecoderStream } = + typeof globalThis.TextDecoderStream === "undefined" + ? require("../vendor/streams-text-encoding/text-decoder-stream") + : globalThis; + +/** + * 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 ""; + } +} + +/** + * 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. + * @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; + const shouldProcessFileOutput = useFileOutput && isFileStream(url); + + return new ReadableStream({ + async start(controller) { + const init = { + ...initOptions, + headers: { + ...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}: ${text}`, + request, + response + ) + ); + } + + const stream = response.body + .pipeThrough(new TextDecoderStream()) + .pipeThrough(new EventSourceParserStream()); + + for await (const event of streamAsyncIterator(stream)) { + if (event.event === "error") { + controller.error(new Error(event.data)); + break; + } + + let data = event.data; + if ( + event.event === "output" && + shouldProcessFileOutput && + typeof data === "string" + ) { + data = createFileOutput({ url: data, fetch }); + } + controller.enqueue(new ServerSentEvent(event.event, data, event.id)); + + if (event.event === "done") { + break; + } + } + + controller.close(); + }, + }); +} + +/** + * 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 fetch function. + * @returns {ReadableStream} + */ +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); + } + }, + }); +} + +function isFileStream(url) { + try { + return new URL(url).pathname.startsWith("/v1/files/"); + } catch {} + return false; +} + +module.exports = { + createFileOutput, + createReadableStream, + ServerSentEvent, +}; diff --git a/lib/trainings.js b/lib/trainings.js index edb037c4..49640b9e 100644 --- a/lib/trainings.js +++ b/lib/trainings.js @@ -9,24 +9,29 @@ * @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 { // 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, + signal, + } + ); return response.json(); } @@ -35,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', + method: "GET", + signal, }); return response.json(); @@ -49,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', + method: "POST", + signal, }); return response.json(); @@ -62,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() { - const response = await this.request('/trainings', { - method: 'GET', +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 new file mode 100644 index 00000000..09665774 --- /dev/null +++ b/lib/util.js @@ -0,0 +1,476 @@ +const ApiError = require("./error"); +const { create: createFile } = require("./files"); + +/** + * @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. + * @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) + */ + +/** + * @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 + * @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) + */ + +/** + * Validate a webhook signature + * + * @returns {Promise} - True if the signature is valid + * @throws {Error} - If the request is missing required headers, body, or 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") { + // 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; + if (typeof secretOrCrypto !== "string") { + throw new Error( + "Unexpected value for secret passed to validateWebhook, expected a string" + ); + } + + 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) { + try { + body = await new Response(body).text(); + } catch (err) { + throw new Error(`Error reading body: ${err.message}`); + } + } 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"); + } + + if (!id || !timestamp || !signature) { + throw new Error("Missing required webhook headers"); + } + + if (!body) { + throw new Error("Missing required body"); + } + + 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( + secret.split("_").pop(), + signedContent, + crypto + ); + + const expectedSignatures = signature + .split(" ") + .map((sig) => sig.split(",")[1]); + + return expectedSignatures.some( + (expectedSignature) => expectedSignature === computedSignature + ); +} + +/** + * @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, crypto) { + const encoder = new TextEncoder(); + 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. + * + * 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; + } + } 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 {Promise} - 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) { + if ( + error instanceof ApiError && + error.response.status >= 400 && + error.response.status < 500 + ) { + throw 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 {Promise} - 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; + +/** + * Walks the inputs and transforms any binary data found into a + * base64-encoded data URI. + * + * @param {object} inputs - The inputs to transform + * @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; + return 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 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(); + mime = value.type; + } else if (isTypedArray(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 = bytesToBase64(buffer); + mime = mime || "application/octet-stream"; + + return `data:${mime};base64,${data}`; + }); +} + +// Walk a JavaScript object and transform the leaf values. +async function transform(value, mapper) { + if (Array.isArray(value)) { + const copy = []; + for (const val of value) { + const transformed = await transform(val, mapper); + copy.push(transformed); + } + 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); +} + +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) { + 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) + ); +} + +/** + * 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; +} + +/** + * 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 = { + transform, + transformFileInputs, + validateWebhook, + withAutomaticRetries, + parseProgressFromLogs, + streamAsyncIterator, +}; diff --git a/lib/webhooks.js b/lib/webhooks.js new file mode 100644 index 00000000..8da6fdf3 --- /dev/null +++ b/lib/webhooks.js @@ -0,0 +1,23 @@ +/** + * 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({ signal } = {}) { + const response = await this.request("/webhooks/default/secret", { + method: "GET", + signal, + }); + + return response.json(); +} + +module.exports = { + default: { + secret: { + get: getDefaultWebhookSecret, + }, + }, +}; diff --git a/package-lock.json b/package-lock.json index 90f21588..6c869967 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,34 +1,32 @@ { "name": "replicate", - "version": "0.14.0", + "version": "1.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "replicate", - "version": "0.14.0", + "version": "1.4.0", "license": "Apache-2.0", "devDependencies": { - "@types/jest": "^29.5.0", + "@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.5.0", - "nock": "^13.3.0", + "jest": "^29.7.0", + "nock": "^14.0.0-beta.6", + "publint": "^0.2.7", "ts-jest": "^29.1.0", "typescript": "^5.0.2" }, "engines": { "git": ">=2.11.0", - "node": ">=16.6.0", + "node": ">=18.0.0", "npm": ">=7.19.0", "yarn": ">=1.7.0" + }, + "optionalDependencies": { + "readable-stream": ">=4.0.0" } }, "node_modules/@ampproject/remapping": { @@ -45,47 +43,48 @@ } }, "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.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.18.6" + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "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" @@ -95,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.21.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", - "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", + "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.21.3", - "@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": { @@ -126,195 +119,193 @@ } }, "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" } }, "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" } }, "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.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.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.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.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.18.6" + "@babel/types": "^7.24.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.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.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.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.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "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.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/helper-validator-identifier": "^7.24.5", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -392,9 +383,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.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" @@ -464,12 +455,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.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.18.6" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -566,12 +557,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.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.19.0" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -581,34 +572,34 @@ } }, "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.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.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@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.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", - "debug": "^4.1.0", + "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.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.24.5", + "@babel/parser": "^7.24.5", + "@babel/types": "^7.24.5", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -625,13 +616,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.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.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.24.1", + "@babel/helper-validator-identifier": "^7.24.5", "to-fast-properties": "^2.0.0" }, "engines": { @@ -644,18 +635,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": ">=16" + "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": ">=14.*" } }, "node_modules/@eslint-community/eslint-utils": { @@ -687,6 +785,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", @@ -710,6 +809,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" } @@ -719,6 +819,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", @@ -733,6 +834,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" }, @@ -745,7 +847,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", @@ -856,16 +959,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.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0" }, "engines": { @@ -873,37 +976,37 @@ } }, "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.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.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.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.5.0", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", - "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-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.5.0", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, @@ -920,89 +1023,89 @@ } }, "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.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.5.0", - "@jest/types": "^29.5.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.5.0" + "jest-mock": "^29.7.0" }, "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.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, "dependencies": { - "expect": "^29.5.0", - "jest-snapshot": "^29.5.0" + "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.5.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", - "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "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.5.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", - "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "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.5.0", + "@jest/types": "^29.6.3", "@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.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.5.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", - "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", + "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.5.0", - "@jest/expect": "^29.5.0", - "@jest/types": "^29.5.0", - "jest-mock": "^29.5.0" + "@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.5.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", - "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "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.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.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", "collect-v8-coverage": "^1.0.0", @@ -1010,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.5.0", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", + "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", @@ -1034,25 +1137,41 @@ } } }, + "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.4.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", - "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "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.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.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.15", + "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" }, @@ -1061,13 +1180,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.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.5.0", - "@jest/types": "^29.5.0", + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, @@ -1076,14 +1195,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.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.5.0", + "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", + "jest-haste-map": "^29.7.0", "slash": "^3.0.0" }, "engines": { @@ -1091,22 +1210,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.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.5.0", - "@jridgewell/trace-mapping": "^0.3.15", + "@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.5.0", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", + "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", @@ -1117,12 +1236,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.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.4.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -1156,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" @@ -1171,13 +1290,13 @@ "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.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": { @@ -1216,33 +1335,33 @@ } }, "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.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "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": { - "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", @@ -1253,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", @@ -1272,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": "*" @@ -1314,9 +1433,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", @@ -1329,24 +1448,12 @@ "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", "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", @@ -1563,11 +1670,24 @@ "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", "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1580,6 +1700,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" } @@ -1589,6 +1710,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", @@ -1664,52 +1786,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", @@ -1720,64 +1802,16 @@ "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.5.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", - "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "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.5.0", + "@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" @@ -1806,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", @@ -1844,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": { @@ -1865,6 +1899,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", @@ -1876,21 +1930,21 @@ } }, "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" } }, "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": [ { @@ -1900,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" @@ -1936,46 +1994,36 @@ "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", "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", @@ -1995,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": [ { @@ -2007,6 +2055,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ] }, @@ -2051,9 +2103,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.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": { @@ -2081,9 +2133,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": { @@ -2104,36 +2156,42 @@ "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", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, - "node_modules/cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "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", "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", "dev": true, "dependencies": { @@ -2172,16 +2230,25 @@ } }, "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.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" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } }, "node_modules/deep-is": { "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", @@ -2192,22 +2259,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", @@ -2218,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" @@ -2243,6 +2294,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" }, @@ -2251,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": { @@ -2283,98 +2335,10 @@ "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", - "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" @@ -2385,6 +2349,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" }, @@ -2397,6 +2362,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", @@ -2449,266 +2415,6 @@ "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==", - "dev": true, - "dependencies": { - "confusing-browser-globals": "^1.0.10", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5", - "semver": "^6.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.2" - } - }, - "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==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "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==", - "dev": true, - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" - } - }, - "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==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "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==", - "dev": true, - "dependencies": { - "debug": "^3.2.7" - }, - "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", @@ -2722,33 +2428,6 @@ "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", @@ -2763,6 +2442,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", "dev": true, + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -2776,6 +2456,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" } @@ -2785,6 +2466,7 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz", "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==", "dev": true, + "peer": true, "dependencies": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", @@ -2815,6 +2497,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" }, @@ -2827,6 +2510,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" } @@ -2866,10 +2550,29 @@ "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" } }, + "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", @@ -2903,16 +2606,16 @@ } }, "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.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.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0" + "@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" @@ -2922,7 +2625,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", @@ -2962,7 +2666,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", @@ -2987,6 +2692,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" }, @@ -2995,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" @@ -3011,6 +2717,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" @@ -3027,6 +2734,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" @@ -3039,16 +2747,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", @@ -3057,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, @@ -3071,33 +2771,9 @@ } }, "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 - }, - "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==", + "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" @@ -3121,20 +2797,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", @@ -3156,22 +2818,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", @@ -3197,6 +2843,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" }, @@ -3209,6 +2856,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" }, @@ -3219,21 +2867,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", @@ -3254,18 +2887,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", @@ -3278,27 +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-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", @@ -3308,55 +2908,16 @@ "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==", + "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": { - "has-symbols": "^1.0.2" + "function-bind": "^1.1.2" }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/html-escaper": { @@ -3374,6 +2935,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", @@ -3383,11 +2964,45 @@ "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", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, + "peer": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -3443,117 +3058,19 @@ "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", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "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==", + "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-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3598,18 +3115,6 @@ "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", @@ -3619,58 +3124,16 @@ "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, + "peer": 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" - } - }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -3683,67 +3146,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", - "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" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3785,17 +3187,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 +3215,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.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", @@ -3826,15 +3228,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.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "dependencies": { - "@jest/core": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", "import-local": "^3.0.2", - "jest-cli": "^29.5.0" + "jest-cli": "^29.7.0" }, "bin": { "jest": "bin/jest.js" @@ -3852,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": { @@ -3865,28 +3268,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.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.5.0", - "@jest/expect": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", + "@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": "^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.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.5.0", + "pretty-format": "^29.7.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" @@ -3896,22 +3299,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.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.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", + "@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.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "prompts": "^2.0.1", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "yargs": "^17.3.1" }, "bin": { @@ -3930,31 +3332,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.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.5.0", - "@jest/types": "^29.5.0", - "babel-jest": "^29.5.0", + "@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.5.0", - "jest-environment-node": "^29.5.0", - "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-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.5.0", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -3975,24 +3377,24 @@ } }, "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.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.5.0" + "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" @@ -4002,62 +3404,62 @@ } }, "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.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.5.0", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", - "jest-util": "^29.5.0", - "pretty-format": "^29.5.0" + "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.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.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.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.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-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.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.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.5.0", + "@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.5.0", - "jest-worker": "^29.5.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, @@ -4069,46 +3471,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.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.5.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-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.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.5.0", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "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.5.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", - "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "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.5.0", + "@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.5.0", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -4117,14 +3519,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.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.5.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-util": "^29.5.0" + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -4148,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.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.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.5.0", + "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" @@ -4177,43 +3579,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.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.5.0" + "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.5.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", - "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "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.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.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.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-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" }, @@ -4222,31 +3624,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.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.5.0", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", + "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" }, @@ -4255,46 +3657,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.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", "@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.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.5.0", + "expect": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", + "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.5.0", - "semver": "^7.3.5" + "pretty-format": "^29.7.0", + "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.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.5.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -4306,17 +3705,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.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.5.0", + "@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.5.0" + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -4335,18 +3734,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.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.5.0", - "@jest/types": "^29.5.0", + "@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.5.0", + "jest-util": "^29.7.0", "string-length": "^4.0.1" }, "engines": { @@ -4354,13 +3753,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.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.5.0", + "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -4388,6 +3787,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" @@ -4404,6 +3804,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" }, @@ -4411,15 +3812,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", @@ -4442,13 +3834,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", @@ -4491,6 +3885,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" @@ -4510,6 +3905,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" }, @@ -4520,12 +3916,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", @@ -4536,7 +3926,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", @@ -4548,29 +3939,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", @@ -4635,13 +4017,13 @@ "node": "*" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "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, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=4" } }, "node_modules/ms": { @@ -4663,18 +4045,16 @@ "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.6", + "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.0-beta.6.tgz", + "integrity": "sha512-b7lc7qvj1dQzxtbU7TqyTMnKbNKwGQd585xsRtcCZOv3I/yOK9Vwv4nOgnLFxFtX9m1yjhQDRbgqFCqNh9HuEw==", "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": { @@ -4704,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": { @@ -4718,83 +4098,95 @@ "node": ">=0.10.0" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "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": { - "path-key": "^3.0.0" + "npm-normalize-package-bin": "^2.0.0" }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "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==", + "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, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "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==", + "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": ">= 0.4" + "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/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==", + "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": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": ">= 0.4" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, - "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==", + "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": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=10" } }, - "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==", + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "path-key": "^3.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, "node_modules/once": { @@ -4826,6 +4218,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", @@ -4858,6 +4251,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" }, @@ -4882,6 +4276,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" }, @@ -5045,17 +4440,18 @@ "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" } }, "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.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.4.3", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -5075,6 +4471,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", @@ -5097,19 +4502,40 @@ "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", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "dev": true, + "peer": true, "engines": { "node": ">=6" } }, "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.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", "dev": true, "funding": [ { @@ -5148,33 +4574,20 @@ "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, + "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": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, - "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": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/require-directory": { @@ -5187,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" }, @@ -5229,6 +4642,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" } @@ -5257,6 +4671,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" }, @@ -5290,24 +4705,42 @@ "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==", + "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": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" + "mri": "^1.1.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=6" } }, + "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.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" @@ -5358,20 +4791,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", @@ -5412,28 +4831,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", @@ -5461,6 +4858,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", @@ -5488,51 +4894,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", @@ -5617,7 +4978,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", @@ -5695,39 +5057,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", @@ -5754,6 +5083,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" }, @@ -5775,6 +5105,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" }, @@ -5782,20 +5113,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", @@ -5809,25 +5126,10 @@ "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", - "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": [ { @@ -5837,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" @@ -5855,30 +5161,25 @@ "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" } }, "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", @@ -5919,47 +5220,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" } @@ -6016,9 +5282,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 1d72d264..90cde175 100644 --- a/package.json +++ b/package.json @@ -1,35 +1,49 @@ { "name": "replicate", - "version": "0.14.0", + "version": "1.4.0", "description": "JavaScript client for Replicate", "repository": "github:replicate/replicate-javascript", "homepage": "https://github.com/replicate/replicate-javascript#readme", "bugs": "https://github.com/replicate/replicate-javascript/issues", "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", + "vendor/**/*", + "package.json" + ], "engines": { - "node": ">=16.6.0", + "node": ">=18.0.0", "npm": ">=7.19.0", "git": ">=2.11.0", "yarn": ">=1.7.0" }, "scripts": { - "lint": "eslint .", + "check": "tsc", + "format": "biome format . --write", + "lint-biome": "biome lint .", + "lint-publint": "publint", + "lint": "npm run lint-biome && npm run lint-publint", "test": "jest" }, + "optionalDependencies": { + "readable-stream": ">=4.0.0" + }, "devDependencies": { - "@types/jest": "^29.5.0", + "@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.5.0", - "nock": "^13.3.0", + "jest": "^29.7.0", + "nock": "^14.0.0-beta.6", + "publint": "^0.2.7", "ts-jest": "^29.1.0", "typescript": "^5.0.2" } diff --git a/tsconfig.json b/tsconfig.json index 073eb382..d77efdc5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,9 @@ { "compilerOptions": { - "esModuleInterop": true + "esModuleInterop": true, + "noEmit": true, + "strict": true, + "allowJs": true }, - "exclude": [ - "node_modules", - "**/node_modules/*" - ] + "exclude": ["**/node_modules", "integration"] } diff --git a/vendor/eventsource-parser/stream.js b/vendor/eventsource-parser/stream.js new file mode 100644 index 00000000..88465daa --- /dev/null +++ b/vendor/eventsource-parser/stream.js @@ -0,0 +1,198 @@ +// Source: https://github.com/rexxars/eventsource-parser/tree/v1.1.2 +// +// MIT License +// +// Copyright (c) 2024 Espen Hovlandsdal +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if ((from && typeof from === "object") || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { + get: () => from[key], + enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable, + }); + } + return to; +}; +var __toCommonJS = (mod) => + __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// /input.ts +var input_exports = {}; +__export(input_exports, { + EventSourceParserStream: () => EventSourceParserStream, +}); +module.exports = __toCommonJS(input_exports); + +// http-url:https://unpkg.com/eventsource-parser@1.1.2/dist/index.js +function createParser(onParse) { + let isFirstChunk; + let buffer; + let startingPosition; + let startingFieldLength; + let eventId; + let eventName; + let data; + reset(); + return { + feed, + reset, + }; + function reset() { + isFirstChunk = true; + buffer = ""; + startingPosition = 0; + startingFieldLength = -1; + eventId = void 0; + eventName = void 0; + data = ""; + } + function feed(chunk) { + buffer = buffer ? buffer + chunk : chunk; + if (isFirstChunk && hasBom(buffer)) { + buffer = buffer.slice(BOM.length); + } + isFirstChunk = false; + const length = buffer.length; + let position = 0; + let discardTrailingNewline = false; + while (position < length) { + if (discardTrailingNewline) { + if (buffer[position] === "\n") { + ++position; + } + discardTrailingNewline = false; + } + let lineLength = -1; + let fieldLength = startingFieldLength; + let character; + for ( + let index = startingPosition; + lineLength < 0 && index < length; + ++index + ) { + character = buffer[index]; + if (character === ":" && fieldLength < 0) { + fieldLength = index - position; + } else if (character === "\r") { + discardTrailingNewline = true; + lineLength = index - position; + } else if (character === "\n") { + lineLength = index - position; + } + } + if (lineLength < 0) { + startingPosition = length - position; + startingFieldLength = fieldLength; + break; + } else { + startingPosition = 0; + startingFieldLength = -1; + } + parseEventStreamLine(buffer, position, fieldLength, lineLength); + position += lineLength + 1; + } + if (position === length) { + buffer = ""; + } else if (position > 0) { + buffer = buffer.slice(position); + } + } + function parseEventStreamLine(lineBuffer, index, fieldLength, lineLength) { + if (lineLength === 0) { + if (data.length > 0) { + onParse({ + type: "event", + id: eventId, + event: eventName || void 0, + data: data.slice(0, -1), + // remove trailing newline + }); + data = ""; + eventId = void 0; + } + eventName = void 0; + return; + } + const noValue = fieldLength < 0; + const field = lineBuffer.slice( + index, + index + (noValue ? lineLength : fieldLength) + ); + let step = 0; + if (noValue) { + step = lineLength; + } else if (lineBuffer[index + fieldLength + 1] === " ") { + step = fieldLength + 2; + } else { + step = fieldLength + 1; + } + const position = index + step; + const valueLength = lineLength - step; + const value = lineBuffer.slice(position, position + valueLength).toString(); + if (field === "data") { + data += value ? "".concat(value, "\n") : "\n"; + } else if (field === "event") { + eventName = value; + } else if (field === "id" && !value.includes("\0")) { + eventId = value; + } else if (field === "retry") { + const retry = parseInt(value, 10); + if (!Number.isNaN(retry)) { + onParse({ + type: "reconnect-interval", + value: retry, + }); + } + } + } +} +var BOM = [239, 187, 191]; +function hasBom(buffer) { + return BOM.every((charCode, index) => buffer.charCodeAt(index) === charCode); +} + +// http-url:https://unpkg.com/eventsource-parser@1.1.2/dist/stream.js +var EventSourceParserStream = class extends TransformStream { + constructor() { + let parser; + super({ + start(controller) { + parser = createParser((event) => { + if (event.type === "event") { + controller.enqueue(event); + } + }); + }, + transform(chunk) { + parser.feed(chunk); + }, + }); + } +}; diff --git a/vendor/streams-text-encoding/text-decoder-stream.js b/vendor/streams-text-encoding/text-decoder-stream.js new file mode 100644 index 00000000..f400709d --- /dev/null +++ b/vendor/streams-text-encoding/text-decoder-stream.js @@ -0,0 +1,95 @@ +// Adapted from https://github.com/stardazed/sd-streams +// +// MIT License +// +// Copyright (c) 2018-Present @zenmumbler +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// /input.ts +var input_exports = {}; +__export(input_exports, { + TextDecoderStream: () => TextDecoderStream +}); +module.exports = __toCommonJS(input_exports); + +// http-url:https://unpkg.com/@stardazed/streams-text-encoding@1.0.2/dist/sd-streams-text-encoding.esm.js +var decDecoder = Symbol("decDecoder"); +var decTransform = Symbol("decTransform"); +var TextDecodeTransformer = class { + constructor(decoder) { + this.decoder_ = decoder; + } + transform(chunk, controller) { + if (!(chunk instanceof ArrayBuffer || ArrayBuffer.isView(chunk))) { + throw new TypeError("Input data must be a BufferSource"); + } + const text = this.decoder_.decode(chunk, { stream: true }); + if (text.length !== 0) { + controller.enqueue(text); + } + } + flush(controller) { + const text = this.decoder_.decode(); + if (text.length !== 0) { + controller.enqueue(text); + } + } +}; +var TextDecoderStream = class { + constructor(label, options) { + const decoder = new TextDecoder(label || "utf-8", options || {}); + this[decDecoder] = decoder; + this[decTransform] = new TransformStream(new TextDecodeTransformer(decoder)); + } + get encoding() { + return this[decDecoder].encoding; + } + get fatal() { + return this[decDecoder].fatal; + } + get ignoreBOM() { + return this[decDecoder].ignoreBOM; + } + get readable() { + return this[decTransform].readable; + } + get writable() { + return this[decTransform].writable; + } +}; +var encEncoder = Symbol("encEncoder"); +var encTransform = Symbol("encTransform");