From 826b6aa1d4d7b64d354e4e771e055b1c6367b490 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Sat, 13 Jan 2024 21:08:40 +0000 Subject: [PATCH 01/11] Export singleton instance of replicate by default This makes the library very quick to get up and running. We use a proxy instance to ensure that the library keeps working in its existing state though with a @deprecated type annotation which will show up in editors. --- index.d.ts | 78 ++++++++- index.js | 403 +++++++---------------------------------------- index.test.ts | 107 +++++++++---- lib/replicate.js | 374 +++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 5 files changed, 586 insertions(+), 378 deletions(-) create mode 100644 lib/replicate.js diff --git a/index.d.ts b/index.d.ts index 5620f3b..4ea4868 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,7 +1,7 @@ declare module "replicate" { - type Status = "starting" | "processing" | "succeeded" | "failed" | "canceled"; - type Visibility = "public" | "private"; - type WebhookEventType = "start" | "output" | "logs" | "completed"; + export type Status = "starting" | "processing" | "succeeded" | "failed" | "canceled"; + export type Visibility = "public" | "private"; + export type WebhookEventType = "start" | "output" | "logs" | "completed"; export interface ApiError extends Error { request: Request; @@ -82,7 +82,7 @@ declare module "replicate" { retry?: number; } - export default class Replicate { + export class Replicate { constructor(options?: { auth?: string; userAgent?: string; @@ -223,4 +223,74 @@ declare module "replicate" { list(): Promise>; }; } + + /** @deprecated */ + class DeprecatedReplicate extends Replicate { + /** @deprecated Use `const Replicate = require("replicate").Replicate` instead */ + constructor(...args: ConstructorParameters); + } + + + /** + * Default instance of the Replicate class that gets the access token + * from the REPLICATE_API_TOKEN environment variable. + * + * Create a new Replicate API client instance. + * + * @example + * + * import replicate from "replicate"; + * + * // Run a model and await the result: + * const model = 'owner/model:version-id' + * const input = {text: 'Hello, world!'} + * const output = await replicate.run(model, { input }); + * + * @remarks + * + * NOTE: Use of this object as a constructor is deprecated and will + * be removed in a future version. Import the Replicate constructor + * instead: + * + * ``` + * const Replicate = require("replicate").Replicate; + * ``` + * + * Or using esm: + * + * ``` + * import replicate from "replicate"; + * const client = new replicate.Replicate({...}); + * ``` + * + * @type { Replicate & typeof DeprecatedReplicate & {ApiError: ApiError, Replicate: Replicate} } + */ + const replicate: Replicate & typeof DeprecatedReplicate & { + /** + * Create a new Replicate API client instance. + * + * @example + * // Create a new Replicate API client instance + * const Replicate = require("replicate"); + * const replicate = new Replicate({ + * // get your token from https://replicate.com/account + * auth: process.env.REPLICATE_API_TOKEN, + * userAgent: "my-app/1.2.3" + * }); + * + * // Run a model and await the result: + * const model = 'owner/model:version-id' + * const input = {text: 'Hello, world!'} + * const output = await replicate.run(model, { input }); + * + * @param {object} options - Configuration options for the client + * @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` + */ + Replicate: typeof Replicate + }; + + export default replicate; } diff --git a/index.js b/index.js index ce407f9..08bd8d0 100644 --- a/index.js +++ b/index.js @@ -1,361 +1,72 @@ +const Replicate = require("./lib/replicate"); const ApiError = require("./lib/error"); -const ModelVersionIdentifier = require("./lib/identifier"); -const { Stream } = require("./lib/stream"); -const { withAutomaticRetries } = require("./lib/util"); -const collections = require("./lib/collections"); -const deployments = require("./lib/deployments"); -const hardware = require("./lib/hardware"); -const models = require("./lib/models"); -const predictions = require("./lib/predictions"); -const trainings = require("./lib/trainings"); +/** + * Placeholder class used to warn of deprecated constructor. + * @deprecated use exported Replicate class instead + */ +class DeprecatedReplicate extends Replicate { + /** @deprecated Use `import { Replicate } from "replicate";` instead */ + // biome-ignore lint/complexity/noUselessConstructor: exists for the tsdoc comment + constructor(...args) { + super(...args); + } +} -const packageJSON = require("./package.json"); +const named = { ApiError, Replicate }; +const singleton = new Replicate(); /** - * Replicate API client library + * Default instance of the Replicate class that gets the access token + * from the REPLICATE_API_TOKEN environment variable. + * + * Create a new Replicate API client instance. * - * @see https://replicate.com/docs/reference/http * @example - * // Create a new Replicate API client instance - * const Replicate = require("replicate"); - * const replicate = new Replicate({ - * // get your token from https://replicate.com/account - * auth: process.env.REPLICATE_API_TOKEN, - * userAgent: "my-app/1.2.3" - * }); + * + * import replicate from "replicate"; * * // Run a model and await the result: * const model = 'owner/model:version-id' * const input = {text: 'Hello, world!'} * const output = await replicate.run(model, { input }); + * + * @remarks + * + * NOTE: Use of this object as a constructor is deprecated and will + * be removed in a future version. Import the Replicate constructor + * instead: + * + * ``` + * const Replicate = require("replicate").Replicate; + * ``` + * + * Or in commonjs: + * + * ``` + * import { Replicate } from "replicate"; + * const client = new Replicate({...}); + * ``` + * + * @type { Replicate & typeof DeprecatedReplicate & {ApiError: ApiError, Replicate: Replicate} } */ -class Replicate { - /** - * Create a new Replicate API client instance. - * - * @param {object} options - Configuration options for the client - * @param {string} options.auth - API access token. Defaults to the `REPLICATE_API_TOKEN` environment variable. - * @param {string} options.userAgent - Identifier of your app - * @param {string} [options.baseUrl] - Defaults to https://api.replicate.com/v1 - * @param {Function} [options.fetch] - Fetch function to use. Defaults to `globalThis.fetch` - */ - constructor(options = {}) { - this.auth = options.auth || process.env.REPLICATE_API_TOKEN; - this.userAgent = - options.userAgent || `replicate-javascript/${packageJSON.version}`; - this.baseUrl = options.baseUrl || "https://api.replicate.com/v1"; - this.fetch = options.fetch || globalThis.fetch; - - this.collections = { - list: collections.list.bind(this), - get: collections.get.bind(this), - }; - - this.deployments = { - predictions: { - create: deployments.predictions.create.bind(this), - }, - }; - - this.hardware = { - list: hardware.list.bind(this), - }; - - this.models = { - get: models.get.bind(this), - list: models.list.bind(this), - create: models.create.bind(this), - versions: { - list: models.versions.list.bind(this), - get: models.versions.get.bind(this), - }, - }; - - this.predictions = { - create: predictions.create.bind(this), - get: predictions.get.bind(this), - cancel: predictions.cancel.bind(this), - list: predictions.list.bind(this), - }; - - this.trainings = { - create: trainings.create.bind(this), - get: trainings.get.bind(this), - cancel: trainings.cancel.bind(this), - list: trainings.list.bind(this), - }; - } - - /** - * Run a model and wait for its output. - * - * @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 500 - * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output - * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) - * @param {AbortSignal} [options.signal] - AbortSignal to cancel the prediction - * @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(ref, options, progress) { - const { wait, ...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"); - } - - // 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 - } - - return false; // continue polling - } - ); - - // Call progress callback with the completed prediction object - if (progress) { - progress(prediction); - } - - if (prediction.status === "failed") { - throw new Error(`Prediction failed: ${prediction.error}`); - } - - return prediction.output; +const replicate = new Proxy(DeprecatedReplicate, { + get(target, prop, receiver) { + // Should mostly behave like the singleton. + if (named[prop]) { + return named[prop]; + } + // Provide Replicate & ApiError constructors. + if (singleton[prop]) { + return singleton[prop]; + } + // Fallback to Replicate constructor properties. + return Reflect.get(target, prop, receiver); + }, + set(_target, prop, newValue, _receiver) { + singleton[prop] = newValue; + return true; } +}); - /** - * Make a request to the Replicate API. - * - * @param {string} route - REST API endpoint path - * @param {object} options - Request parameters - * @param {string} [options.method] - HTTP method. Defaults to GET - * @param {object} [options.params] - Query parameters - * @param {object|Headers} [options.headers] - HTTP headers - * @param {object} [options.data] - Body parameters - * @returns {Promise} - Resolves with the response object - * @throws {ApiError} If the request failed - */ - async request(route, options) { - const { auth, baseUrl, userAgent } = this; - - let url; - if (route instanceof URL) { - url = route; - } else { - url = new URL( - route.startsWith("/") ? route.slice(1) : route, - baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/` - ); - } - - const { method = "GET", params = {}, data } = options; - - for (const [key, value] of Object.entries(params)) { - url.searchParams.append(key, value); - } - - const headers = {}; - if (auth) { - headers["Authorization"] = `Token ${auth}`; - } - headers["Content-Type"] = "application/json"; - headers["User-Agent"] = userAgent; - if (options.headers) { - for (const [key, value] of Object.entries(options.headers)) { - headers[key] = value; - } - } - - const init = { - method, - headers, - body: data ? JSON.stringify(data) : undefined, - }; - - 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); - const responseText = await response.text(); - throw new ApiError( - `Request to ${url} failed with status ${response.status} ${response.statusText}: ${responseText}.`, - request, - 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, ...data } = options; - - const identifier = ModelVersionIdentifier.parse(ref); - - let prediction; - if (identifier.version) { - prediction = await this.predictions.create({ - ...data, - version: identifier.version, - stream: true, - }); - } else if (identifier.owner && identifier.name) { - prediction = await this.predictions.create({ - ...data, - model: `${identifier.owner}/${identifier.name}`, - stream: true, - }); - } else { - throw new Error("Invalid model version identifier"); - } - - if (prediction.urls && prediction.urls.stream) { - const { signal } = options; - const stream = new Stream(prediction.urls.stream, { signal }); - yield* stream; - } else { - throw new Error("Prediction does not support streaming"); - } - } - - /** - * Paginate through a list of results. - * - * @generator - * @example - * for await (const page of replicate.paginate(replicate.predictions.list) { - * console.log(page); - * } - * @param {Function} endpoint - Function that returns a promise for the next page of results - * @yields {object[]} Each page of results - */ - async *paginate(endpoint) { - const response = await endpoint(); - yield response.results; - if (response.next) { - const nextPage = () => - this.request(response.next, { method: "GET" }).then((r) => r.json()); - yield* this.paginate(nextPage); - } - } - - /** - * Wait for a prediction to finish. - * - * If the prediction has already finished, - * this function returns immediately. - * Otherwise, it polls the API until the prediction finishes. - * - * @async - * @param {object} prediction - Prediction object - * @param {object} options - Options - * @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 - * @returns {Promise} Resolves with the completed prediction object - */ - async wait(prediction, options, stop) { - const { id } = prediction; - if (!id) { - throw new Error("Invalid prediction"); - } - - if ( - prediction.status === "succeeded" || - prediction.status === "failed" || - prediction.status === "canceled" - ) { - return prediction; - } - - // eslint-disable-next-line no-promise-executor-return - const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); - - const interval = (options && options.interval) || 500; - - let updatedPrediction = await this.predictions.get(id); - - while ( - updatedPrediction.status !== "succeeded" && - updatedPrediction.status !== "failed" && - updatedPrediction.status !== "canceled" - ) { - /* eslint-disable no-await-in-loop */ - if (stop && (await stop(updatedPrediction)) === true) { - break; - } - - await sleep(interval); - updatedPrediction = await this.predictions.get(prediction.id); - /* eslint-enable no-await-in-loop */ - } - - if (updatedPrediction.status === "failed") { - throw new Error(`Prediction failed: ${updatedPrediction.error}`); - } - - return updatedPrediction; - } -} - -module.exports = Replicate; +module.exports = replicate; diff --git a/index.test.ts b/index.test.ts index 5b5a1dd..514565d 100644 --- a/index.test.ts +++ b/index.test.ts @@ -1,41 +1,42 @@ import { expect, jest, test } from "@jest/globals"; -import Replicate, { ApiError, Model, Prediction } from "replicate"; +import replicate, { ApiError, Model, Prediction, Replicate } from "replicate"; import nock from "nock"; import fetch from "cross-fetch"; +import assert from "node:assert"; + +assert(process.env.REPLICATE_API_TOKEN === "test-token", `set REPLICATE_API_TOKEN to "test-token"`) -let client: Replicate; const BASE_URL = "https://api.replicate.com/v1"; nock.disableNetConnect(); -describe("Replicate client", () => { - let unmatched: any[] = []; - const handleNoMatch = (req: unknown, options: any, body: string) => - unmatched.push({ req, options, body }); - - beforeEach(() => { - client = new Replicate({ auth: "test-token" }); - client.fetch = fetch; - - unmatched = []; - nock.emitter.on("no match", handleNoMatch); - }); +describe(`const replicate = require("replicate");`, () => { + testInstance(() => { + replicate.fetch = fetch; + return replicate; + }) +}); - afterEach(() => { - nock.emitter.off("no match", handleNoMatch); - expect(unmatched).toStrictEqual([]); +describe(`const Replicate = require("replicate"); (deprecated)`, function () { + testConstructor((opts) => new replicate({ auth: "test-token", fetch, ...opts })) + testInstance((opts) => new replicate({ auth: "test-token", fetch, ...opts })) +}); - nock.abortPendingRequests(); - nock.cleanAll(); - }); +describe(`const Replicate = require("replicate").Replicate;`, () => { + testConstructor((opts) => new replicate.Replicate({ auth: "test-token", fetch, ...opts })) + testInstance((opts) => new replicate.Replicate({ auth: "test-token", fetch, ...opts })) +}); +/** Test suite to exercise the Replicate constructor */ +function testConstructor(createClient: (opts?: object) => Replicate) { describe("constructor", () => { test("Sets default baseUrl", () => { + const client = createClient(); expect(client.baseUrl).toBe("https://api.replicate.com/v1"); }); test("Sets custom baseUrl", () => { - const clientWithCustomBaseUrl = new Replicate({ + const clientWithCustomBaseUrl = createClient({ baseUrl: "https://example.com/", auth: "test-token", }); @@ -43,7 +44,7 @@ describe("Replicate client", () => { }); test("Sets custom userAgent", () => { - const clientWithCustomUserAgent = new Replicate({ + const clientWithCustomUserAgent = createClient({ userAgent: "my-app/1.2.3", auth: "test-token", }); @@ -54,7 +55,7 @@ describe("Replicate client", () => { process.env.REPLICATE_API_TOKEN = "test-token"; expect(() => { - const clientWithImplicitAuth = new Replicate(); + const clientWithImplicitAuth = createClient(); expect(clientWithImplicitAuth.auth).toBe("test-token"); }).not.toThrow(); @@ -62,11 +63,32 @@ describe("Replicate client", () => { test("Does not throw error if blank auth token is provided", () => { expect(() => { - new Replicate({ auth: "" }); + createClient({ auth: "" }); }).not.toThrow(); }); }); + } + +/** Test suite to exercise the Replicate instance */ +function testInstance(createClient: (opts?: object) => Replicate) { + let unmatched: any[] = []; + const handleNoMatch = (req: unknown, options: any, body: string) => + unmatched.push({ req, options, body }); + + beforeEach(() => { + unmatched = []; + nock.emitter.on("no match", handleNoMatch); + }); + + afterEach(() => { + nock.emitter.off("no match", handleNoMatch); + expect(unmatched).toStrictEqual([]); + + nock.abortPendingRequests(); + nock.cleanAll(); + }); + describe("collections.list", () => { test("Calls the correct API route", async () => { nock(BASE_URL) @@ -89,6 +111,7 @@ describe("Replicate client", () => { previous: null, }); + const client = createClient(); const collections = await client.collections.list(); expect(collections.results.length).toBe(2); }); @@ -105,6 +128,7 @@ describe("Replicate client", () => { models: [], }); + const client = createClient(); const collection = await client.collections.get("super-resolution"); expect(collection.name).toBe("Super resolution"); }); @@ -128,6 +152,7 @@ describe("Replicate client", () => { latest_version: {}, }); + const client = createClient(); await client.models.get("replicate", "hello-world"); }); // Add more tests for error handling, edge cases, etc. @@ -150,6 +175,7 @@ describe("Replicate client", () => { }); const results: Model[] = []; + const client = createClient(); for await (const batch of client.paginate(client.models.list)) { results.push(...batch); } @@ -188,6 +214,7 @@ describe("Replicate client", () => { logs: null, metrics: {}, }); + const client = createClient(); const prediction = await client.predictions.create({ version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", @@ -208,6 +235,7 @@ describe("Replicate client", () => { return body; }); + const client = createClient(); await client.predictions.create({ version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", @@ -220,6 +248,7 @@ describe("Replicate client", () => { test("Throws an error if webhook URL is invalid", async () => { await expect(async () => { + const client = createClient(); await client.predictions.create({ version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", @@ -244,6 +273,7 @@ describe("Replicate client", () => { try { expect.hasAssertions(); + const client = createClient(); await client.predictions.create({ version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", @@ -271,6 +301,7 @@ describe("Replicate client", () => { .reply(201, { id: "ufawqhfynnddngldkgtslldrkq", }); + const client = createClient(); const prediction = await client.predictions.create({ version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", @@ -290,6 +321,7 @@ describe("Replicate client", () => { { "Content-Type": "application/json" } ); + const client = createClient(); await expect( client.predictions.create({ version: @@ -335,6 +367,7 @@ describe("Replicate client", () => { predict_time: 4.484541, }, }); + const client = createClient(); const prediction = await client.predictions.get( "rrr4z55ocneqzikepnug6xezpe" ); @@ -356,6 +389,7 @@ describe("Replicate client", () => { id: "rrr4z55ocneqzikepnug6xezpe", }); + const client = createClient(); const prediction = await client.predictions.get( "rrr4z55ocneqzikepnug6xezpe" ); @@ -377,6 +411,7 @@ describe("Replicate client", () => { id: "rrr4z55ocneqzikepnug6xezpe", }); + const client = createClient(); const prediction = await client.predictions.get( "rrr4z55ocneqzikepnug6xezpe" ); @@ -411,6 +446,7 @@ describe("Replicate client", () => { metrics: {}, }); + const client = createClient(); const prediction = await client.predictions.cancel( "ufawqhfynnddngldkgtslldrkq" ); @@ -447,6 +483,7 @@ describe("Replicate client", () => { ], }); + const client = createClient(); const predictions = await client.predictions.list(); expect(predictions.results.length).toBe(1); expect(predictions.results[0].id).toBe("jpzd7hm5gfcapbfyt4mqytarku"); @@ -468,6 +505,7 @@ describe("Replicate client", () => { }); const results: Prediction[] = []; + const client = createClient(); for await (const batch of client.paginate(client.predictions.list)) { results.push(...batch); } @@ -502,6 +540,7 @@ describe("Replicate client", () => { completed_at: null, }); + const client = createClient(); const training = await client.trainings.create( "owner", "model", @@ -517,6 +556,7 @@ describe("Replicate client", () => { }); test("Throws an error if webhook is not a valid URL", async () => { + const client = createClient(); await expect( client.trainings.create( "owner", @@ -559,6 +599,7 @@ describe("Replicate client", () => { completed_at: null, }); + const client = createClient(); const training = await client.trainings.get("zz4ibbonubfz7carwiefibzgga"); expect(training.status).toBe("succeeded"); }); @@ -589,6 +630,7 @@ describe("Replicate client", () => { completed_at: null, }); + const client = createClient(); const training = await client.trainings.cancel( "zz4ibbonubfz7carwiefibzgga" ); @@ -625,6 +667,7 @@ describe("Replicate client", () => { ], }); + const client = createClient(); const trainings = await client.trainings.list(); expect(trainings.results.length).toBe(1); expect(trainings.results[0].id).toBe("jpzd7hm5gfcapbfyt4mqytarku"); @@ -646,6 +689,7 @@ describe("Replicate client", () => { }); const results: Prediction[] = []; + const client = createClient(); for await (const batch of client.paginate(client.trainings.list)) { results.push(...batch); } @@ -684,6 +728,7 @@ describe("Replicate client", () => { logs: null, metrics: {}, }); + const client = createClient(); const prediction = await client.deployments.predictions.create( "replicate", "greeter", @@ -721,6 +766,7 @@ describe("Replicate client", () => { get: "https://api.replicate.com/v1/predictions/heat2o3bzn3ahtr6bjfftvbaci", }, }); + const client = createClient(); const prediction = await client.predictions.create({ model: "meta/llama-2-70b-chat", input: { @@ -745,6 +791,7 @@ describe("Replicate client", () => { { name: "Nvidia A40 (Large) GPU", sku: "gpu-a40-large" }, ]); + const client = createClient(); const hardware = await client.hardware.list(); expect(hardware.length).toBe(4); expect(hardware[0].name).toBe("CPU"); @@ -763,6 +810,7 @@ describe("Replicate client", () => { description: "A test model", }); + const client = createClient(); const model = await client.models.create("test-owner", "test-model", { visibility: "public", hardware: "cpu", @@ -779,8 +827,6 @@ describe("Replicate client", () => { describe("run", () => { test("Calls the correct API routes for a version", async () => { - const firstPollingRequest = true; - nock(BASE_URL) .post("/predictions") .reply(201, { @@ -802,6 +848,7 @@ describe("Replicate client", () => { const progress = jest.fn(); + const client = createClient(); const output = await client.run( "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", { @@ -861,6 +908,7 @@ describe("Replicate client", () => { const progress = jest.fn(); + const client = createClient(); const output = await client.run( "replicate/hello-world", { @@ -910,12 +958,14 @@ describe("Replicate client", () => { output: "foobar", }); + const client = createClient(); 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 client = createClient(); const options = { input: { text: "Hello, world!" } }; // @ts-expect-error @@ -928,6 +978,7 @@ describe("Replicate client", () => { }); test("Throws an error if webhook URL is invalid", async () => { + const client = createClient(); await expect(async () => { await client.run( "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", @@ -966,6 +1017,7 @@ describe("Replicate client", () => { status: "canceled", }); + const client = createClient(); await client.run( "owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", { @@ -981,4 +1033,5 @@ describe("Replicate client", () => { }); // Continue with tests for other methods -}); +} + diff --git a/lib/replicate.js b/lib/replicate.js new file mode 100644 index 0000000..1fac1e4 --- /dev/null +++ b/lib/replicate.js @@ -0,0 +1,374 @@ +const ApiError = require("./error"); +const ModelVersionIdentifier = require("./identifier"); +const { Stream } = require("./stream"); +const { withAutomaticRetries } = require("./util"); + +const collections = require("./collections"); +const deployments = require("./deployments"); +const hardware = require("./hardware"); +const models = require("./models"); +const predictions = require("./predictions"); +const trainings = require("./trainings"); + +const packageJSON = require("../package.json"); + +/** + * Replicate API client library + * + * @see https://replicate.com/docs/reference/http + * @example + * + * // Create a new Replicate API client instance + * const Replicate = require("replicate").Replicate; + * const replicate = new Replicate({ + * // get your token from https://replicate.com/account + * auth: process.env.REPLICATE_API_TOKEN, + * userAgent: "my-app/1.2.3" + * }); + * + * // Run a model and await the result: + * const model = 'owner/model:version-id' + * const input = {text: 'Hello, world!'} + * const output = await replicate.run(model, { input }); + */ +module.exports = class Replicate { + /** + * Create a new Replicate API client instance. + * + * @example + * // Create a new Replicate API client instance + * const Replicate = require("replicate"); + * const replicate = new Replicate({ + * // get your token from https://replicate.com/account + * auth: process.env.REPLICATE_API_TOKEN, + * userAgent: "my-app/1.2.3" + * }); + * + * // Run a model and await the result: + * const model = 'owner/model:version-id' + * const input = {text: 'Hello, world!'} + * const output = await replicate.run(model, { input }); + * + * @param {object} options - Configuration options for the client + * @param {string} options.auth - API access token. Defaults to the `REPLICATE_API_TOKEN` environment variable. + * @param {string} options.userAgent - Identifier of your app + * @param {string} [options.baseUrl] - Defaults to https://api.replicate.com/v1 + * @param {Function} [options.fetch] - Fetch function to use. Defaults to `globalThis.fetch` + */ + constructor(options = {}) { + this.auth = options.auth || process.env.REPLICATE_API_TOKEN; + this.userAgent = + options.userAgent || `replicate-javascript/${packageJSON.version}`; + this.baseUrl = options.baseUrl || "https://api.replicate.com/v1"; + this.fetch = options.fetch || globalThis.fetch; + + this.collections = { + list: collections.list.bind(this), + get: collections.get.bind(this), + }; + + this.deployments = { + predictions: { + create: deployments.predictions.create.bind(this), + }, + }; + + this.hardware = { + list: hardware.list.bind(this), + }; + + this.models = { + get: models.get.bind(this), + list: models.list.bind(this), + create: models.create.bind(this), + versions: { + list: models.versions.list.bind(this), + get: models.versions.get.bind(this), + }, + }; + + this.predictions = { + create: predictions.create.bind(this), + get: predictions.get.bind(this), + cancel: predictions.cancel.bind(this), + list: predictions.list.bind(this), + }; + + this.trainings = { + create: trainings.create.bind(this), + get: trainings.get.bind(this), + cancel: trainings.cancel.bind(this), + list: trainings.list.bind(this), + }; + } + + /** + * Run a model and wait for its output. + * + * @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 500 + * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output + * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) + * @param {AbortSignal} [options.signal] - AbortSignal to cancel the prediction + * @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(ref, options, progress) { + const { wait, ...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"); + } + + // 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 + } + + return false; // continue polling + } + ); + + // Call progress callback with the completed prediction object + if (progress) { + progress(prediction); + } + + if (prediction.status === "failed") { + throw new Error(`Prediction failed: ${prediction.error}`); + } + + return prediction.output; + } + + /** + * Make a request to the Replicate API. + * + * @param {string} route - REST API endpoint path + * @param {object} options - Request parameters + * @param {string} [options.method] - HTTP method. Defaults to GET + * @param {object} [options.params] - Query parameters + * @param {object|Headers} [options.headers] - HTTP headers + * @param {object} [options.data] - Body parameters + * @returns {Promise} - Resolves with the response object + * @throws {ApiError} If the request failed + */ + async request(route, options) { + const { auth, baseUrl, userAgent } = this; + + let url; + if (route instanceof URL) { + url = route; + } else { + url = new URL( + route.startsWith("/") ? route.slice(1) : route, + baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/` + ); + } + + const { method = "GET", params = {}, data } = options; + + for (const [key, value] of Object.entries(params)) { + url.searchParams.append(key, value); + } + + const headers = {}; + if (auth) { + headers["Authorization"] = `Token ${auth}`; + } + headers["Content-Type"] = "application/json"; + headers["User-Agent"] = userAgent; + if (options.headers) { + for (const [key, value] of Object.entries(options.headers)) { + headers[key] = value; + } + } + + const init = { + method, + headers, + body: data ? JSON.stringify(data) : undefined, + }; + + 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); + const responseText = await response.text(); + throw new ApiError( + `Request to ${url} failed with status ${response.status} ${response.statusText}: ${responseText}.`, + request, + 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, ...data } = options; + + const identifier = ModelVersionIdentifier.parse(ref); + + let prediction; + if (identifier.version) { + prediction = await this.predictions.create({ + ...data, + version: identifier.version, + stream: true, + }); + } else if (identifier.owner && identifier.name) { + prediction = await this.predictions.create({ + ...data, + model: `${identifier.owner}/${identifier.name}`, + stream: true, + }); + } else { + throw new Error("Invalid model version identifier"); + } + + if (prediction.urls && prediction.urls.stream) { + const { signal } = options; + const stream = new Stream(prediction.urls.stream, { signal }); + yield* stream; + } else { + throw new Error("Prediction does not support streaming"); + } + } + + /** + * Paginate through a list of results. + * + * @generator + * @example + * for await (const page of replicate.paginate(replicate.predictions.list) { + * console.log(page); + * } + * @param {Function} endpoint - Function that returns a promise for the next page of results + * @yields {object[]} Each page of results + */ + async *paginate(endpoint) { + const response = await endpoint(); + yield response.results; + if (response.next) { + const nextPage = () => + this.request(response.next, { method: "GET" }).then((r) => r.json()); + yield* this.paginate(nextPage); + } + } + + /** + * Wait for a prediction to finish. + * + * If the prediction has already finished, + * this function returns immediately. + * Otherwise, it polls the API until the prediction finishes. + * + * @async + * @param {object} prediction - Prediction object + * @param {object} options - Options + * @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 + * @returns {Promise} Resolves with the completed prediction object + */ + async wait(prediction, options, stop) { + const { id } = prediction; + if (!id) { + throw new Error("Invalid prediction"); + } + + if ( + prediction.status === "succeeded" || + prediction.status === "failed" || + prediction.status === "canceled" + ) { + return prediction; + } + + // eslint-disable-next-line no-promise-executor-return + const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + + const interval = (options && options.interval) || 500; + + let updatedPrediction = await this.predictions.get(id); + + while ( + updatedPrediction.status !== "succeeded" && + updatedPrediction.status !== "failed" && + updatedPrediction.status !== "canceled" + ) { + /* eslint-disable no-await-in-loop */ + if (stop && (await stop(updatedPrediction)) === true) { + break; + } + + await sleep(interval); + updatedPrediction = await this.predictions.get(prediction.id); + /* eslint-enable no-await-in-loop */ + } + + if (updatedPrediction.status === "failed") { + throw new Error(`Prediction failed: ${updatedPrediction.error}`); + } + + return updatedPrediction; + } +}; diff --git a/package.json b/package.json index 24a088b..a0219de 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "check": "tsc", "format": "biome format . --write", "lint": "biome lint .", - "test": "jest" + "test": "REPLICATE_API_TOKEN=test-token jest" }, "optionalDependencies": { "readable-stream": ">=4.0.0" From f16e7d96f0419da80688c7adaaec4faf78b6637c Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Sat, 13 Jan 2024 21:08:52 +0000 Subject: [PATCH 02/11] Update README to focus on singleton instance --- README.md | 72 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index bd7e43d..6ec5694 100644 --- a/README.md +++ b/README.md @@ -20,20 +20,18 @@ npm install replicate ## Usage -Create the client: +Set your `REPLICATE_API_TOKEN` in your environment: -```js -import Replicate from "replicate"; - -const replicate = new Replicate({ - // get your token from https://replicate.com/account - auth: "my api token", // defaults to process.env.REPLICATE_API_TOKEN -}); +```sh +# get your token from https://replicate.com/account +export REPLICATE_API_TOKEN="r8_123..." ``` Run a model and await the result: ```js +import replicate from "replicate"; + const model = "stability-ai/stable-diffusion:27b93a2413e7f36cd83da926f3656280b2931564ff050bf9575f1fdf9bcd7478"; const input = { prompt: "a 19th century portrait of a raccoon gentleman wearing a suit", @@ -94,8 +92,12 @@ const output = await replicate.run(model, { input }); ### Constructor +You can create a custom instance of the Replicate client using the `replicate.Replicate` constructor: + ```js -const replicate = new Replicate(options); +import replicate from "replicate"; + +const replicate = new replicate.Replicate(options); ``` | name | type | description | @@ -121,10 +123,10 @@ 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 replicate from "replicate"; import fetch from "cross-fetch"; -const replicate = new Replicate({ fetch }); +const replicate = new replicate.Replicate({ fetch }); ``` You can also use the `fetch` option to add custom behavior to client requests, @@ -778,4 +780,50 @@ You can call this method directly to make other requests to the API. ## TypeScript -The `Replicate` constructor and all `replicate.*` methods are fully typed. +The `Replicate` constructor and all `replicate.*` methods are fully typed. Types are accessible +via the named exports: + +```ts +import type { Model, Prediction } from "replicate"; +``` + +## Deprecated Constructor + +Earlier versions of the Replicate library exported the `Replicate` constructor as the default +export. This will be removed in a future version, to migrate please update your code to use +the following pattern: + +If you don't need to customize your Replicate client you can just remove the constructor code +entirely: + +```js +// Deprecated +import Replicate from "replicate"; + +const replicate = new Replicate(); + +replicate.run(...); + +// Fixed +import replicate from "replicate"; + +replicate.run(...); +``` + +If you need the Replicate construtor it's available on the `replicate` object. + +```js +// Deprecated +import Replicate from "replicate"; + +const replicate = new Replicate({auth: "my-token"}); + +replicate.run(...); + +// Fixed +import replicate from "replicate"; + +replicate = new replicate.Replicate({auth: "my-token"}); + +replicate.run(...); +``` From c907f42751a538c45df4eaf6a54dfcb2639f6f1e Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Sat, 13 Jan 2024 21:09:20 +0000 Subject: [PATCH 03/11] Refactor integration tests to reflect different import styles --- integration/commonjs/constructor.test.js | 21 ++++++++++++++++ integration/commonjs/deprecated.test.js | 21 ++++++++++++++++ integration/commonjs/index.test.js | 8 ------- integration/commonjs/package.json | 2 +- integration/commonjs/singleton.test.js | 19 +++++++++++++++ integration/esm/constructor.test.js | 21 ++++++++++++++++ .../esm/{index.js => deprecated.test.js} | 13 ++++++---- integration/esm/index.test.js | 8 ------- integration/esm/package.json | 2 +- integration/esm/singleton.test.js | 19 +++++++++++++++ integration/typescript/constructor.test.ts | 21 ++++++++++++++++ .../{index.ts => deprecated.test.ts} | 13 ++++++---- integration/typescript/index.test.ts | 24 ------------------- integration/typescript/package.json | 2 +- integration/typescript/singleton.test.ts | 19 +++++++++++++++ 15 files changed, 162 insertions(+), 51 deletions(-) create mode 100644 integration/commonjs/constructor.test.js create mode 100644 integration/commonjs/deprecated.test.js delete mode 100644 integration/commonjs/index.test.js create mode 100644 integration/commonjs/singleton.test.js create mode 100644 integration/esm/constructor.test.js rename integration/esm/{index.js => deprecated.test.js} (50%) delete mode 100644 integration/esm/index.test.js create mode 100644 integration/esm/singleton.test.js create mode 100644 integration/typescript/constructor.test.ts rename integration/typescript/{index.ts => deprecated.test.ts} (50%) delete mode 100644 integration/typescript/index.test.ts create mode 100644 integration/typescript/singleton.test.ts diff --git a/integration/commonjs/constructor.test.js b/integration/commonjs/constructor.test.js new file mode 100644 index 0000000..79048b0 --- /dev/null +++ b/integration/commonjs/constructor.test.js @@ -0,0 +1,21 @@ +const { test } = require('node:test'); +const assert = require('node:assert'); +const Replicate = require('replicate').Replicate; + +const replicate = new Replicate(); + +async function main() { + return await replicate.run( + "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + { + input: { + text: "Claire CommonJS" + } + } + ); +}; + +test('main', async () => { + const output = await main(); + assert.equal(output, "hello Claire CommonJS"); +}); diff --git a/integration/commonjs/deprecated.test.js b/integration/commonjs/deprecated.test.js new file mode 100644 index 0000000..49b5579 --- /dev/null +++ b/integration/commonjs/deprecated.test.js @@ -0,0 +1,21 @@ +const { test } = require('node:test'); +const assert = require('node:assert'); +const Replicate = require('replicate'); + +const replicate = new Replicate(); + +async function main() { + return await replicate.run( + "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + { + input: { + text: "Claire CommonJS" + } + } + ); +}; + +test('main', async () => { + const output = await main(); + assert.equal(output, "hello Claire CommonJS"); +}); diff --git a/integration/commonjs/index.test.js b/integration/commonjs/index.test.js deleted file mode 100644 index 5ef7b63..0000000 --- a/integration/commonjs/index.test.js +++ /dev/null @@ -1,8 +0,0 @@ -const { test } = require('node:test'); -const assert = require('node:assert'); -const main = require('./index'); - -test('main', async () => { - const output = await main(); - assert.equal(output, "hello Claire CommonJS"); -}); diff --git a/integration/commonjs/package.json b/integration/commonjs/package.json index 7fb6fc8..d6e6e47 100644 --- a/integration/commonjs/package.json +++ b/integration/commonjs/package.json @@ -5,7 +5,7 @@ "description": "CommonJS integration tests", "main": "index.js", "scripts": { - "test": "node --test ./index.test.js" + "test": "node --test ./*.test.js" }, "dependencies": { "replicate": "file:../../" diff --git a/integration/commonjs/singleton.test.js b/integration/commonjs/singleton.test.js new file mode 100644 index 0000000..1211080 --- /dev/null +++ b/integration/commonjs/singleton.test.js @@ -0,0 +1,19 @@ +const { test } = require('node:test'); +const assert = require('node:assert'); +const replicate = require('replicate'); + +async function main() { + return await replicate.run( + "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + { + input: { + text: "Claire CommonJS" + } + } + ); +}; + +test('main', async () => { + const output = await main(); + assert.equal(output, "hello Claire CommonJS"); +}); diff --git a/integration/esm/constructor.test.js b/integration/esm/constructor.test.js new file mode 100644 index 0000000..206342f --- /dev/null +++ b/integration/esm/constructor.test.js @@ -0,0 +1,21 @@ +import { test } from 'node:test'; +import assert from 'node:assert'; +import replicate_ from "replicate"; + +const replicate = new replicate_.Replicate(); + +async function main() { + return await replicate.run( + "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + { + input: { + text: "Evelyn ESM" + } + } + ); +}; + +test('main', async () => { + const output = await main(); + assert.equal(output, "hello Evelyn ESM"); +}); diff --git a/integration/esm/index.js b/integration/esm/deprecated.test.js similarity index 50% rename from integration/esm/index.js rename to integration/esm/deprecated.test.js index 547b726..0a5c7ee 100644 --- a/integration/esm/index.js +++ b/integration/esm/deprecated.test.js @@ -1,10 +1,10 @@ +import { test } from 'node:test'; +import assert from 'node:assert'; import Replicate from "replicate"; -const replicate = new Replicate({ - auth: process.env.REPLICATE_API_TOKEN, -}); +const replicate = new Replicate(); -export default async function main() { +async function main() { return await replicate.run( "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", { @@ -14,3 +14,8 @@ export default async function main() { } ); }; + +test('main', async () => { + const output = await main(); + assert.equal(output, "hello Evelyn ESM"); +}); diff --git a/integration/esm/index.test.js b/integration/esm/index.test.js deleted file mode 100644 index 2bd276f..0000000 --- a/integration/esm/index.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import { test } from 'node:test'; -import assert from 'node:assert'; -import main from './index.js'; - -test('main', async () => { - const output = await main(); - assert.equal(output, "hello Evelyn ESM"); -}); diff --git a/integration/esm/package.json b/integration/esm/package.json index 51076d7..ca093ac 100644 --- a/integration/esm/package.json +++ b/integration/esm/package.json @@ -6,7 +6,7 @@ "main": "index.js", "type": "module", "scripts": { - "test": "node --test ./index.test.js" + "test": "node --test ./*.test.js" }, "dependencies": { "replicate": "file:../../" diff --git a/integration/esm/singleton.test.js b/integration/esm/singleton.test.js new file mode 100644 index 0000000..59860ad --- /dev/null +++ b/integration/esm/singleton.test.js @@ -0,0 +1,19 @@ +import { test } from 'node:test'; +import assert from 'node:assert'; +import replicate from "replicate"; + +async function main() { + return await replicate.run( + "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + { + input: { + text: "Evelyn ESM" + } + } + ); +}; + +test('main', async () => { + const output = await main(); + assert.equal(output, "hello Evelyn ESM"); +}); diff --git a/integration/typescript/constructor.test.ts b/integration/typescript/constructor.test.ts new file mode 100644 index 0000000..b8f2b4d --- /dev/null +++ b/integration/typescript/constructor.test.ts @@ -0,0 +1,21 @@ +import { test } from 'node:test'; +import assert from 'node:assert'; +import replicate_ from "replicate"; + +const replicate = new replicate_.Replicate(); + +async function main() { + return await replicate.run( + "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + { + input: { + text: "Tracy TypeScript" + } + } + ); +}; + +test('main', async () => { + const output = await main(); + assert.equal(output, "hello Tracy TypeScript"); +}); diff --git a/integration/typescript/index.ts b/integration/typescript/deprecated.test.ts similarity index 50% rename from integration/typescript/index.ts rename to integration/typescript/deprecated.test.ts index 8e27a3b..c3f85b1 100644 --- a/integration/typescript/index.ts +++ b/integration/typescript/deprecated.test.ts @@ -1,10 +1,10 @@ +import { test } from 'node:test'; +import assert from 'node:assert'; import Replicate from "replicate"; -const replicate = new Replicate({ - auth: process.env.REPLICATE_API_TOKEN, -}); +const replicate = new Replicate(); -export default async function main() { +async function main() { return await replicate.run( "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", { @@ -14,3 +14,8 @@ export default async function main() { } ); }; + +test('main', async () => { + const output = await main(); + assert.equal(output, "hello Tracy TypeScript"); +}); diff --git a/integration/typescript/index.test.ts b/integration/typescript/index.test.ts deleted file mode 100644 index be4ab90..0000000 --- a/integration/typescript/index.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { test } from 'node:test'; -import assert from 'node:assert'; -import main from './index.js'; - -// Verify exported types. -import type { - Status, - Visibility, - WebhookEventType, - ApiError, - Collection, - Hardware, - Model, - ModelVersion, - Prediction, - Training, - Page, - ServerSentEvent, -} from "replicate"; - -test('main', async () => { - const output = await main(); - assert.equal(output, "hello Tracy TypeScript"); -}); diff --git a/integration/typescript/package.json b/integration/typescript/package.json index 4adae99..8dc9a5c 100644 --- a/integration/typescript/package.json +++ b/integration/typescript/package.json @@ -6,7 +6,7 @@ "main": "index.js", "type": "module", "scripts": { - "test": "tsc && node --test ./dist/index.test.js" + "test": "tsc && node --test ./dist/*.test.js" }, "dependencies": { "@types/node": "^20.11.0", diff --git a/integration/typescript/singleton.test.ts b/integration/typescript/singleton.test.ts new file mode 100644 index 0000000..c34f8fb --- /dev/null +++ b/integration/typescript/singleton.test.ts @@ -0,0 +1,19 @@ +import { test } from 'node:test'; +import assert from 'node:assert'; +import replicate from "replicate"; + +async function main() { + return await replicate.run( + "replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + { + input: { + text: "Tracy TypeScript" + } + } + ); +}; + +test('main', async () => { + const output = await main(); + assert.equal(output, "hello Tracy TypeScript"); +}); From 09a117a70e300ec1153dc666babb281f85ed0d1d Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Sun, 14 Jan 2024 18:30:29 +0000 Subject: [PATCH 04/11] fixup lint --- index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.test.ts b/index.test.ts index 514565d..b6c38a8 100644 --- a/index.test.ts +++ b/index.test.ts @@ -17,7 +17,7 @@ describe(`const replicate = require("replicate");`, () => { }) }); -describe(`const Replicate = require("replicate"); (deprecated)`, function () { +describe(`const Replicate = require("replicate"); (deprecated)`, () => { testConstructor((opts) => new replicate({ auth: "test-token", fetch, ...opts })) testInstance((opts) => new replicate({ auth: "test-token", fetch, ...opts })) }); From 49949953ba7c0c558a7df9808aa29adbc412fdd7 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Sun, 14 Jan 2024 22:15:09 +0000 Subject: [PATCH 05/11] Generate TypeScript definitions from source --- index.d.ts | 296 --------------------------- index.js | 34 ++- index.test.ts | 11 +- integration/typescript/types.test.ts | 61 ++++++ lib/collections.js | 4 +- lib/deployments.js | 4 +- lib/hardware.js | 2 +- lib/models.js | 18 +- lib/predictions.js | 14 +- lib/replicate.js | 6 +- lib/trainings.js | 8 +- lib/types.js | 71 +++++++ package.json | 6 +- tsconfig.json | 5 +- 14 files changed, 205 insertions(+), 335 deletions(-) delete mode 100644 index.d.ts create mode 100644 integration/typescript/types.test.ts create mode 100644 lib/types.js diff --git a/index.d.ts b/index.d.ts deleted file mode 100644 index 4ea4868..0000000 --- a/index.d.ts +++ /dev/null @@ -1,296 +0,0 @@ -declare module "replicate" { - export type Status = "starting" | "processing" | "succeeded" | "failed" | "canceled"; - export type Visibility = "public" | "private"; - export type WebhookEventType = "start" | "output" | "logs" | "completed"; - - export interface ApiError extends Error { - request: Request; - response: Response; - } - - export interface Collection { - name: string; - slug: string; - description: string; - models?: Model[]; - } - - export interface Hardware { - sku: string; - name: string; - } - - export interface Model { - url: string; - owner: string; - name: string; - description?: string; - visibility: "public" | "private"; - github_url?: string; - paper_url?: string; - license_url?: string; - run_count: number; - cover_image_url?: string; - default_example?: Prediction; - latest_version?: ModelVersion; - } - - export interface ModelVersion { - id: string; - created_at: string; - cog_version: string; - openapi_schema: object; - } - - export interface Prediction { - id: string; - status: Status; - model: string; - version: string; - input: object; - output?: any; - source: "api" | "web"; - error?: any; - logs?: string; - metrics?: { - predict_time?: number; - }; - webhook?: string; - webhook_events_filter?: WebhookEventType[]; - created_at: string; - started_at?: string; - completed_at?: string; - urls: { - get: string; - cancel: string; - stream?: string; - }; - } - - export type Training = Prediction; - - export interface Page { - previous?: string; - next?: string; - results: T[]; - } - - export interface ServerSentEvent { - event: string; - data: string; - id?: string; - retry?: number; - } - - export class Replicate { - constructor(options?: { - auth?: string; - userAgent?: string; - baseUrl?: string; - fetch?: ( - input: Request | string, - init?: RequestInit - ) => Promise; - }); - - auth: string; - userAgent?: string; - baseUrl?: string; - fetch: (input: Request | string, init?: RequestInit) => Promise; - - run( - identifier: `${string}/${string}` | `${string}/${string}:${string}`, - options: { - input: object; - wait?: { interval?: number }; - webhook?: string; - webhook_events_filter?: WebhookEventType[]; - signal?: AbortSignal; - }, - progress?: (prediction: Prediction) => void - ): Promise; - - stream( - identifier: `${string}/${string}` | `${string}/${string}:${string}`, - options: { - input: object; - webhook?: string; - webhook_events_filter?: WebhookEventType[]; - signal?: AbortSignal; - } - ): AsyncGenerator; - - request( - route: string | URL, - options: { - method?: string; - headers?: object | Headers; - params?: object; - data?: object; - } - ): Promise; - - paginate(endpoint: () => Promise>): AsyncGenerator<[T]>; - - wait( - prediction: Prediction, - options?: { - interval?: number; - }, - stop?: (prediction: Prediction) => Promise - ): Promise; - - collections: { - list(): Promise>; - get(collection_slug: string): Promise; - }; - - deployments: { - predictions: { - create( - deployment_owner: string, - deployment_name: string, - options: { - input: object; - stream?: boolean; - webhook?: string; - webhook_events_filter?: WebhookEventType[]; - } - ): Promise; - }; - }; - - hardware: { - list(): Promise; - }; - - models: { - get(model_owner: string, model_name: string): Promise; - list(): Promise>; - create( - model_owner: string, - model_name: string, - options: { - visibility: Visibility; - hardware: string; - description?: string; - github_url?: string; - paper_url?: string; - license_url?: string; - cover_image_url?: string; - } - ): Promise; - versions: { - list(model_owner: string, model_name: string): Promise; - get( - model_owner: string, - model_name: string, - version_id: string - ): Promise; - }; - }; - - predictions: { - create( - options: { - model?: string; - version?: string; - input: object; - stream?: boolean; - webhook?: string; - webhook_events_filter?: WebhookEventType[]; - } & ({ version: string } | { model: string }) - ): Promise; - get(prediction_id: string): Promise; - cancel(prediction_id: string): Promise; - list(): Promise>; - }; - - trainings: { - create( - model_owner: string, - model_name: string, - version_id: string, - options: { - destination: `${string}/${string}`; - input: object; - webhook?: string; - webhook_events_filter?: WebhookEventType[]; - } - ): Promise; - get(training_id: string): Promise; - cancel(training_id: string): Promise; - list(): Promise>; - }; - } - - /** @deprecated */ - class DeprecatedReplicate extends Replicate { - /** @deprecated Use `const Replicate = require("replicate").Replicate` instead */ - constructor(...args: ConstructorParameters); - } - - - /** - * Default instance of the Replicate class that gets the access token - * from the REPLICATE_API_TOKEN environment variable. - * - * Create a new Replicate API client instance. - * - * @example - * - * import replicate from "replicate"; - * - * // Run a model and await the result: - * const model = 'owner/model:version-id' - * const input = {text: 'Hello, world!'} - * const output = await replicate.run(model, { input }); - * - * @remarks - * - * NOTE: Use of this object as a constructor is deprecated and will - * be removed in a future version. Import the Replicate constructor - * instead: - * - * ``` - * const Replicate = require("replicate").Replicate; - * ``` - * - * Or using esm: - * - * ``` - * import replicate from "replicate"; - * const client = new replicate.Replicate({...}); - * ``` - * - * @type { Replicate & typeof DeprecatedReplicate & {ApiError: ApiError, Replicate: Replicate} } - */ - const replicate: Replicate & typeof DeprecatedReplicate & { - /** - * Create a new Replicate API client instance. - * - * @example - * // Create a new Replicate API client instance - * const Replicate = require("replicate"); - * const replicate = new Replicate({ - * // get your token from https://replicate.com/account - * auth: process.env.REPLICATE_API_TOKEN, - * userAgent: "my-app/1.2.3" - * }); - * - * // Run a model and await the result: - * const model = 'owner/model:version-id' - * const input = {text: 'Hello, world!'} - * const output = await replicate.run(model, { input }); - * - * @param {object} options - Configuration options for the client - * @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` - */ - Replicate: typeof Replicate - }; - - export default replicate; -} diff --git a/index.js b/index.js index 08bd8d0..12fc0bb 100644 --- a/index.js +++ b/index.js @@ -1,11 +1,12 @@ -const Replicate = require("./lib/replicate"); +const ReplicateClass = require("./lib/replicate"); const ApiError = require("./lib/error"); +require("./lib/types"); /** * Placeholder class used to warn of deprecated constructor. * @deprecated use exported Replicate class instead */ -class DeprecatedReplicate extends Replicate { +class DeprecatedReplicate extends ReplicateClass { /** @deprecated Use `import { Replicate } from "replicate";` instead */ // biome-ignore lint/complexity/noUselessConstructor: exists for the tsdoc comment constructor(...args) { @@ -13,8 +14,8 @@ class DeprecatedReplicate extends Replicate { } } -const named = { ApiError, Replicate }; -const singleton = new Replicate(); +const named = { ApiError, Replicate: ReplicateClass }; +const singleton = new ReplicateClass(); /** * Default instance of the Replicate class that gets the access token @@ -48,7 +49,7 @@ const singleton = new Replicate(); * const client = new Replicate({...}); * ``` * - * @type { Replicate & typeof DeprecatedReplicate & {ApiError: ApiError, Replicate: Replicate} } + * @type { Replicate & typeof DeprecatedReplicate & {Replicate: typeof ReplicateClass} } */ const replicate = new Proxy(DeprecatedReplicate, { get(target, prop, receiver) { @@ -70,3 +71,26 @@ const replicate = new Proxy(DeprecatedReplicate, { }); module.exports = replicate; + +// - Type Definitions + +/** + * @typedef {import("./lib/replicate")} Replicate + * @typedef {import("./lib/error")} ApiError + * @typedef {typeof import("./lib/types").Collection} Collection + * @typedef {typeof import("./lib/types").ModelVersion} ModelVersion + * @typedef {typeof import("./lib/types").Hardware} Hardware + * @typedef {typeof import("./lib/types").Model} Model + * @typedef {typeof import("./lib/types").Prediction} Prediction + * @typedef {typeof import("./lib/types").Training} Training + * @typedef {typeof import("./lib/types").ServerSentEvent} ServerSentEvent + * @typedef {typeof import("./lib/types").Status} Status + * @typedef {typeof import("./lib/types").Visibility} Visibility + * @typedef {typeof import("./lib/types").WebhookEventType} WebhookEventType + */ + +/** + * @template T + * @typedef {typeof import("./lib/types").Page} Page + */ + diff --git a/index.test.ts b/index.test.ts index b6c38a8..e1ee29a 100644 --- a/index.test.ts +++ b/index.test.ts @@ -1,5 +1,5 @@ import { expect, jest, test } from "@jest/globals"; -import replicate, { ApiError, Model, Prediction, Replicate } from "replicate"; +import replicate, { ApiError, Model, Prediction, Replicate } from "./"; import nock from "nock"; import fetch from "cross-fetch"; import assert from "node:assert"; @@ -216,6 +216,7 @@ function testInstance(createClient: (opts?: object) => Replicate) { }); const client = createClient(); const prediction = await client.predictions.create({ + model: "foo", version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { @@ -237,6 +238,7 @@ function testInstance(createClient: (opts?: object) => Replicate) { const client = createClient(); await client.predictions.create({ + model: "foo", version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { @@ -250,6 +252,7 @@ function testInstance(createClient: (opts?: object) => Replicate) { await expect(async () => { const client = createClient(); await client.predictions.create({ + model: "foo", version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { @@ -275,6 +278,7 @@ function testInstance(createClient: (opts?: object) => Replicate) { const client = createClient(); await client.predictions.create({ + model: "foo", version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { @@ -303,6 +307,7 @@ function testInstance(createClient: (opts?: object) => Replicate) { }); const client = createClient(); const prediction = await client.predictions.create({ + model: "foo", version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { @@ -324,6 +329,7 @@ function testInstance(createClient: (opts?: object) => Replicate) { const client = createClient(); await expect( client.predictions.create({ + model: "foo", version: "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", input: { @@ -885,7 +891,6 @@ function testInstance(createClient: (opts?: object) => Replicate) { }); test("Calls the correct API routes for a model", async () => { - const firstPollingRequest = true; nock(BASE_URL) .post("/models/replicate/hello-world/predictions") @@ -968,12 +973,10 @@ function testInstance(createClient: (opts?: object) => Replicate) { const client = createClient(); const options = { input: { text: "Hello, world!" } }; - // @ts-expect-error await expect(client.run("owner:abc123", options)).rejects.toThrow(); await expect(client.run("/model:abc123", options)).rejects.toThrow(); - // @ts-expect-error await expect(client.run(":abc123", options)).rejects.toThrow(); }); diff --git a/integration/typescript/types.test.ts b/integration/typescript/types.test.ts new file mode 100644 index 0000000..54f44e2 --- /dev/null +++ b/integration/typescript/types.test.ts @@ -0,0 +1,61 @@ +import { ApiError, Hardware, Model, ModelVersion, Page, Prediction, Status, Training, Visibility, WebhookEventType } from "replicate"; + +// NOTE: We export the constants to avoid unused varaible issues. + +export const collection: Collection = { name: "", slug: "", description: "", models: [] }; +export const status: Status = "starting"; +export const visibility: Visibility = "public"; +export const webhookType: WebhookEventType = "start"; +export const err: ApiError = Object.assign(new Error(), {request: new Request("file://"), response: new Response()}); +export const hardware: Hardware = { sku: "", name: "" }; +export const model: Model = { + url: "", + owner: "", + name: "", + description: "", + visibility: "public", + github_url: "", + paper_url: "", + license_url: "", + run_count: 10, + cover_image_url: "", + default_example: undefined, + latest_version: undefined, +}; +export const version: ModelVersion = { + id: "", + created_at: "", + cog_version: "", + openapi_schema: "", +}; +export const prediction: Prediction = { + id: "", + status: "starting", + model: "", + version: "", + input: {}, + output: {}, + source: "api", + error: undefined, + logs: "", + metrics: { + predict_time: 100, + }, + webhook: "", + webhook_events_filter: [], + created_at: "", + started_at: "", + completed_at: "", + urls: { + get: "", + cancel: "", + stream: "", + }, +}; +export const training: Training = prediction; + +export const page: Page = { + previous: "", + next: "", + results: [version], +}; diff --git a/lib/collections.js b/lib/collections.js index 9332aaa..54fc5df 100644 --- a/lib/collections.js +++ b/lib/collections.js @@ -2,7 +2,7 @@ * Fetch a model collection * * @param {string} collection_slug - Required. The slug of the collection. See http://replicate.com/collections - * @returns {Promise} - Resolves with the collection data + * @returns {Promise} - Resolves with the collection data */ async function getCollection(collection_slug) { const response = await this.request(`/collections/${collection_slug}`, { @@ -15,7 +15,7 @@ async function getCollection(collection_slug) { /** * Fetch a list of model collections * - * @returns {Promise} - Resolves with the collections data + * @returns {Promise>} - Resolves with the collections data */ async function listCollections() { const response = await this.request("/collections", { diff --git a/lib/deployments.js b/lib/deployments.js index 6f32cdb..c558401 100644 --- a/lib/deployments.js +++ b/lib/deployments.js @@ -7,8 +7,8 @@ * @param {object} options.input - Required. An object with the model inputs * @param {boolean} [options.stream] - Whether to stream the prediction output. Defaults to false * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output - * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) - * @returns {Promise} Resolves with the created prediction data + * @param {WebhookEventType[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) + * @returns {Promise} Resolves with the created prediction data */ async function createPrediction(deployment_owner, deployment_name, options) { const { stream, ...data } = options; diff --git a/lib/hardware.js b/lib/hardware.js index d717548..f717035 100644 --- a/lib/hardware.js +++ b/lib/hardware.js @@ -1,7 +1,7 @@ /** * List hardware * - * @returns {Promise} Resolves with the array of hardware + * @returns {Promise} Resolves with the array of hardware */ async function listHardware() { const response = await this.request("/hardware", { diff --git a/lib/models.js b/lib/models.js index c6a02fc..2f30601 100644 --- a/lib/models.js +++ b/lib/models.js @@ -3,7 +3,7 @@ * * @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 - * @returns {Promise} Resolves with the model data + * @returns {Promise} Resolves with the model data */ async function getModel(model_owner, model_name) { const response = await this.request(`/models/${model_owner}/${model_name}`, { @@ -18,7 +18,7 @@ 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 - * @returns {Promise} Resolves with the list of model versions + * @returns {Promise>} Resolves with the list of model versions */ async function listModelVersions(model_owner, model_name) { const response = await this.request( @@ -37,7 +37,7 @@ 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 - * @returns {Promise} Resolves with the model version data + * @returns {Promise} Resolves with the model version data */ async function getModelVersion(model_owner, model_name, version_id) { const response = await this.request( @@ -53,7 +53,7 @@ async function getModelVersion(model_owner, model_name, version_id) { /** * List all public models * - * @returns {Promise} Resolves with the model version data + * @returns {Promise>} Resolves with the model version data */ async function listModels() { const response = await this.request("/models", { @@ -72,11 +72,11 @@ async function listModels() { * @param {("public"|"private")} options.visibility - Required. Whether the model should be public or private. A public model can be viewed and run by anyone, whereas a private model can be viewed and run only by the user or organization members that own the model. * @param {string} options.hardware - Required. The SKU for the hardware used to run the model. Possible values can be found by calling `Replicate.hardware.list()`. * @param {string} options.description - A description of the model. - * @param {string} options.github_url - A URL for the model's source code on GitHub. - * @param {string} options.paper_url - A URL for the model's paper. - * @param {string} options.license_url - A URL for the model's license. - * @param {string} options.cover_image_url - A URL for the model's cover image. This should be an image file. - * @returns {Promise} Resolves with the model version data + * @param {string=} options.github_url - A URL for the model's source code on GitHub. + * @param {string=} options.paper_url - A URL for the model's paper. + * @param {string=} options.license_url - A URL for the model's license. + * @param {string=} options.cover_image_url - A URL for the model's cover image. This should be an image file. + * @returns {Promise} Resolves with the model version data */ async function createModel(model_owner, model_name, options) { const data = { owner: model_owner, name: model_name, ...options }; diff --git a/lib/predictions.js b/lib/predictions.js index 294e8d9..702e66e 100644 --- a/lib/predictions.js +++ b/lib/predictions.js @@ -2,13 +2,13 @@ * Create a new prediction * * @param {object} options - * @param {string} options.model - The model. - * @param {string} options.version - The model version. + * @param {string=} options.model - The model (for official models) + * @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 - * @returns {Promise} Resolves with the created prediction + * @returns {Promise} Resolves with the created prediction */ async function createPrediction(options) { const { model, version, stream, ...data } = options; @@ -43,8 +43,8 @@ async function createPrediction(options) { /** * Fetch a prediction by ID * - * @param {number} prediction_id - Required. The prediction ID - * @returns {Promise} Resolves with the prediction data + * @param {string} prediction_id - Required. The prediction ID + * @returns {Promise} Resolves with the prediction data */ async function getPrediction(prediction_id) { const response = await this.request(`/predictions/${prediction_id}`, { @@ -58,7 +58,7 @@ async function getPrediction(prediction_id) { * Cancel a prediction by ID * * @param {string} prediction_id - Required. The training ID - * @returns {Promise} Resolves with the data for the training + * @returns {Promise} Resolves with the data for the training */ async function cancelPrediction(prediction_id) { const response = await this.request(`/predictions/${prediction_id}/cancel`, { @@ -71,7 +71,7 @@ async function cancelPrediction(prediction_id) { /** * List all predictions * - * @returns {Promise} - Resolves with a page of predictions + * @returns {Promise>} - Resolves with a page of predictions */ async function listPredictions() { const response = await this.request("/predictions", { diff --git a/lib/replicate.js b/lib/replicate.js index 1fac1e4..b1b9f40 100644 --- a/lib/replicate.js +++ b/lib/replicate.js @@ -49,9 +49,9 @@ module.exports = class Replicate { * const input = {text: 'Hello, world!'} * const output = await replicate.run(model, { input }); * - * @param {object} options - Configuration options for the client - * @param {string} options.auth - API access token. Defaults to the `REPLICATE_API_TOKEN` environment variable. - * @param {string} options.userAgent - Identifier of your app + * @param {Object={}} options - Configuration options for the client + * @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` */ diff --git a/lib/trainings.js b/lib/trainings.js index 6b13dca..a66db88 100644 --- a/lib/trainings.js +++ b/lib/trainings.js @@ -9,7 +9,7 @@ * @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`) - * @returns {Promise} Resolves with the data for the created training + * @returns {Promise} Resolves with the data for the created training */ async function createTraining(model_owner, model_name, version_id, options) { const { ...data } = options; @@ -38,7 +38,7 @@ async function createTraining(model_owner, model_name, version_id, options) { * Fetch a training by ID * * @param {string} training_id - Required. The training ID - * @returns {Promise} Resolves with the data for the training + * @returns {Promise} Resolves with the data for the training */ async function getTraining(training_id) { const response = await this.request(`/trainings/${training_id}`, { @@ -52,7 +52,7 @@ async function getTraining(training_id) { * Cancel a training by ID * * @param {string} training_id - Required. The training ID - * @returns {Promise} Resolves with the data for the training + * @returns {Promise} Resolves with the data for the training */ async function cancelTraining(training_id) { const response = await this.request(`/trainings/${training_id}/cancel`, { @@ -65,7 +65,7 @@ async function cancelTraining(training_id) { /** * List all trainings * - * @returns {Promise} - Resolves with a page of trainings + * @returns {Promise>} - Resolves with a page of trainings */ async function listTrainings() { const response = await this.request("/trainings", { diff --git a/lib/types.js b/lib/types.js new file mode 100644 index 0000000..c1cbb0b --- /dev/null +++ b/lib/types.js @@ -0,0 +1,71 @@ +/** + * @typedef {"starting" | "processing" | "succeeded" | "failed" | "canceled"} Status + * @typedef {"public" | "private"} Visibility + * @typedef {"start" | "output" | "logs" | "completed"} WebhookEventType + * + * @typedef {import('./lib/error')} ApiError + * + * @typedef {Object} Collection + * @property {string} name + * @property {string} slug + * @property {string} description + * @property {Model[]=} models + * + * @typedef {Object} Hardware + * @property {string} sku + * @property {string} name + * + * @typedef {Object} Model + * @property {string} url + * @property {string} owner + * @property {string} name + * @property {string=} description + * @property {Visibility} visibility + * @property {string=} github_url + * @property {string=} paper_url + * @property {string=} license_url + * @property {number} run_count + * @property {string=} cover_image_url + * @property {Prediction=} default_example + * @property {ModelVersion=} latest_version + * + * @typedef {Object} ModelVersion + * @property {string} id + * @property {string} created_at + * @property {string} cog_version + * @property {string} openapi_schema + * + * @typedef {Object} Prediction + * @property {string} id + * @property {Status} status + * @property {string=} model + * @property {string} version + * @property {object} input + * @property {unknown=} output + * @property {"api" | "web"} source + * @property {unknown=} error + * @property {string=} logs + * @property {{predict_time?: number}=} metrics + * @property {string=} webhook + * @property {WebhookEventType[]=} webhook_events_filter + * @property {string} created_at + * @property {string=} started_at + * @property {string=} completed_at + * @property {{get: string; cancel: string; stream?: string}} urls + * + * @typedef {Prediction} Training + * + * @property {Object} ServerSentEvent + * @property {string} event + * @property {string} data + * @property {string=} id + * @property {number=} retry + */ + +/** + * @template T + * @typedef {Object} Page + * @property {string=} previous + * @property {string=} next + * @property {T[]} results + */ diff --git a/package.json b/package.json index a0219de..070c141 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "bugs": "https://github.com/replicate/replicate-javascript/issues", "license": "Apache-2.0", "main": "index.js", + "types": "dist/types/index.d.ts", "engines": { "node": ">=18.0.0", "npm": ">=7.19.0", @@ -17,7 +18,10 @@ "check": "tsc", "format": "biome format . --write", "lint": "biome lint .", - "test": "REPLICATE_API_TOKEN=test-token jest" + "test": "REPLICATE_API_TOKEN=test-token jest", + "types": "tsc --target ES2022 --declaration --emitDeclarationOnly --allowJs --types node --outDir ./dist/types index.js ./lib/types.js", + "test:integration": "npm --prefix integration/commonjs test;npm --prefix integration/esm test;npm --prefix integration/typescript test", + "test:all": "npm run test && npm run test:integration" }, "optionalDependencies": { "readable-stream": ">=4.0.0" diff --git a/tsconfig.json b/tsconfig.json index 7a564ee..819ba9b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,9 +2,12 @@ "compilerOptions": { "esModuleInterop": true, "noEmit": true, - "strict": true + "strict": true, + "allowJs": true, }, "exclude": [ + "dist", + "integration", "**/node_modules" ] } From 8d080f88f70ee14d923eaac71bdd3402aac0893a Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Mon, 15 Jan 2024 11:39:22 +0000 Subject: [PATCH 06/11] Ignore types found in devDependencies --- tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig.json b/tsconfig.json index 819ba9b..c7961ea 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "strict": true, "allowJs": true, }, + "types": ["node"], "exclude": [ "dist", "integration", From 6a33a89fe5067109e573c257579669a44e37abce Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Mon, 15 Jan 2024 12:28:37 +0000 Subject: [PATCH 07/11] Tighten up the typings across the board --- index.js | 31 ++++++++++++----------- integration/typescript/types.test.ts | 2 +- lib/collections.js | 6 +++++ lib/deployments.js | 4 ++- lib/hardware.js | 1 + lib/identifier.js | 12 ++++----- lib/models.js | 11 ++++++++- lib/predictions.js | 8 +++++- lib/replicate.js | 37 ++++++++++++++++++++++------ lib/stream.js | 10 +++++++- lib/trainings.js | 8 +++++- lib/types.js | 6 ++--- 12 files changed, 100 insertions(+), 36 deletions(-) diff --git a/index.js b/index.js index 12fc0bb..5ad71ca 100644 --- a/index.js +++ b/index.js @@ -7,10 +7,13 @@ require("./lib/types"); * @deprecated use exported Replicate class instead */ class DeprecatedReplicate extends ReplicateClass { - /** @deprecated Use `import { Replicate } from "replicate";` instead */ + /** + * @deprecated Use `import { Replicate } from "replicate";` instead + * @param {ConstructorParameters[0]=} options + */ // biome-ignore lint/complexity/noUselessConstructor: exists for the tsdoc comment - constructor(...args) { - super(...args); + constructor(options) { + super(options); } } @@ -77,20 +80,20 @@ module.exports = replicate; /** * @typedef {import("./lib/replicate")} Replicate * @typedef {import("./lib/error")} ApiError - * @typedef {typeof import("./lib/types").Collection} Collection - * @typedef {typeof import("./lib/types").ModelVersion} ModelVersion - * @typedef {typeof import("./lib/types").Hardware} Hardware - * @typedef {typeof import("./lib/types").Model} Model - * @typedef {typeof import("./lib/types").Prediction} Prediction - * @typedef {typeof import("./lib/types").Training} Training - * @typedef {typeof import("./lib/types").ServerSentEvent} ServerSentEvent - * @typedef {typeof import("./lib/types").Status} Status - * @typedef {typeof import("./lib/types").Visibility} Visibility - * @typedef {typeof import("./lib/types").WebhookEventType} WebhookEventType + * @typedef {import("./lib/types").Collection} Collection + * @typedef {import("./lib/types").ModelVersion} ModelVersion + * @typedef {import("./lib/types").Hardware} Hardware + * @typedef {import("./lib/types").Model} Model + * @typedef {import("./lib/types").Prediction} Prediction + * @typedef {import("./lib/types").Training} Training + * @typedef {import("./lib/types").ServerSentEvent} ServerSentEvent + * @typedef {import("./lib/types").Status} Status + * @typedef {import("./lib/types").Visibility} Visibility + * @typedef {import("./lib/types").WebhookEventType} WebhookEventType */ /** * @template T - * @typedef {typeof import("./lib/types").Page} Page + * @typedef {import("./lib/types").Page} Page */ diff --git a/integration/typescript/types.test.ts b/integration/typescript/types.test.ts index 54f44e2..71210e4 100644 --- a/integration/typescript/types.test.ts +++ b/integration/typescript/types.test.ts @@ -1,4 +1,4 @@ -import { ApiError, Hardware, Model, ModelVersion, Page, Prediction, Status, Training, Visibility, WebhookEventType } from "replicate"; +import { ApiError, Collection, Hardware, Model, ModelVersion, Page, Prediction, Status, Training, Visibility, WebhookEventType } from "replicate"; // NOTE: We export the constants to avoid unused varaible issues. diff --git a/lib/collections.js b/lib/collections.js index 54fc5df..4175934 100644 --- a/lib/collections.js +++ b/lib/collections.js @@ -1,3 +1,9 @@ +/** @typedef {import("./types").Collection} Collection */ +/** + * @template T + * @typedef {import("./types").Page} Page + */ + /** * Fetch a model collection * diff --git a/lib/deployments.js b/lib/deployments.js index c558401..c071e1d 100644 --- a/lib/deployments.js +++ b/lib/deployments.js @@ -1,10 +1,12 @@ +/** @typedef {import("./types").Prediction} Prediction */ + /** * 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 {unknown} options.input - Required. An object with the model inputs * @param {boolean} [options.stream] - Whether to stream the prediction output. Defaults to false * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output * @param {WebhookEventType[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) diff --git a/lib/hardware.js b/lib/hardware.js index f717035..755bd91 100644 --- a/lib/hardware.js +++ b/lib/hardware.js @@ -1,3 +1,4 @@ +/** @typedef {import("./types").Hardware} Hardware */ /** * List hardware * diff --git a/lib/identifier.js b/lib/identifier.js index 86e23ee..f9e9786 100644 --- a/lib/identifier.js +++ b/lib/identifier.js @@ -2,10 +2,10 @@ * 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. + /** + * @param {string} owner Required. The model owner. + * @param {string} name Required. The model name. + * @param {string | null=} version The model version. */ constructor(owner, name, version = null) { this.owner = owner; @@ -13,10 +13,10 @@ class ModelVersionIdentifier { this.version = version; } - /* + /** * Parse a reference to a model version * - * @param {string} + * @param {string} ref * @returns {ModelVersionIdentifier} * @throws {Error} If the reference is invalid. */ diff --git a/lib/models.js b/lib/models.js index 2f30601..e7cbcd8 100644 --- a/lib/models.js +++ b/lib/models.js @@ -1,3 +1,12 @@ +/** @typedef {import("./types").Model} Model */ +/** @typedef {import("./types").ModelVersion} ModelVersion */ +/** @typedef {import("./types").Prediction} Prediction */ +/** @typedef {import("./types").Visibility} Visibility */ +/** + * @template T + * @typedef {import("./types").Page} Page + */ + /** * Get information about a model * @@ -69,7 +78,7 @@ async function listModels() { * @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 {Visibility} 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. diff --git a/lib/predictions.js b/lib/predictions.js index 702e66e..2143f5b 100644 --- a/lib/predictions.js +++ b/lib/predictions.js @@ -1,10 +1,16 @@ +/** + * @template T + * @typedef {import("./types").Page} Page + */ +/** @typedef {import("./types").Prediction} Prediction */ + /** * Create a new prediction * * @param {object} options * @param {string=} options.model - The model (for official models) * @param {string=} options.version - The model version. - * @param {object} options.input - Required. An object with the model inputs + * @param {unknown} 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 diff --git a/lib/replicate.js b/lib/replicate.js index b1b9f40..281fb49 100644 --- a/lib/replicate.js +++ b/lib/replicate.js @@ -1,3 +1,11 @@ +/** + * @template T + * @typedef {import("./types").Page} Page + */ + +/** @typedef {import("./types").Prediction} Prediction */ +/** @typedef {import("./types").WebhookEventType} WebhookEventType */ + const ApiError = require("./error"); const ModelVersionIdentifier = require("./identifier"); const { Stream } = require("./stream"); @@ -49,34 +57,45 @@ module.exports = class Replicate { * const input = {text: 'Hello, world!'} * const output = await replicate.run(model, { input }); * - * @param {Object={}} options - Configuration options for the client + * @param {Object} [options] - Configuration options for the client * @param {string} [options.auth] - API access token. Defaults to the `REPLICATE_API_TOKEN` environment variable. * @param {string} [options.userAgent] - Identifier of your app * @param {string} [options.baseUrl] - Defaults to https://api.replicate.com/v1 * @param {Function} [options.fetch] - Fetch function to use. Defaults to `globalThis.fetch` */ constructor(options = {}) { + /** @type {string} */ this.auth = options.auth || process.env.REPLICATE_API_TOKEN; + + /** @type {string} */ this.userAgent = options.userAgent || `replicate-javascript/${packageJSON.version}`; + + /** @type {string} */ this.baseUrl = options.baseUrl || "https://api.replicate.com/v1"; + + /** @type {fetch} */ this.fetch = options.fetch || globalThis.fetch; + /** @type {collections} */ this.collections = { list: collections.list.bind(this), get: collections.get.bind(this), }; + /** @type {deployments} */ this.deployments = { predictions: { create: deployments.predictions.create.bind(this), }, }; + /** @type {hardware} */ this.hardware = { list: hardware.list.bind(this), }; + /** @type {models} */ this.models = { get: models.get.bind(this), list: models.list.bind(this), @@ -87,6 +106,7 @@ module.exports = class Replicate { }, }; + /** @type {predictions} */ this.predictions = { create: predictions.create.bind(this), get: predictions.get.bind(this), @@ -94,6 +114,7 @@ module.exports = class Replicate { list: predictions.list.bind(this), }; + /** @type {trainings} */ this.trainings = { create: trainings.create.bind(this), get: trainings.get.bind(this), @@ -111,12 +132,12 @@ module.exports = class Replicate { * @param {object} [options.wait] - Options for waiting for the prediction to finish * @param {number} [options.wait.interval] - Polling interval in milliseconds. Defaults to 500 * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output - * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) + * @param {WebhookEventType[]} [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 + * @returns {Promise} - Resolves with the output of running the model */ async run(ref, options, progress) { const { wait, ...data } = options; @@ -252,7 +273,7 @@ module.exports = class Replicate { /** * Stream 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}:{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 @@ -300,8 +321,10 @@ module.exports = class Replicate { * for await (const page of replicate.paginate(replicate.predictions.list) { * console.log(page); * } - * @param {Function} endpoint - Function that returns a promise for the next page of results - * @yields {object[]} Each page of results + * @template T + * @param {() => Promise>} endpoint - Function that returns a promise for the next page of results + * @yields {T[]} Each page of results + * @returns {AsyncGenerator} */ async *paginate(endpoint) { const response = await endpoint(); @@ -327,7 +350,7 @@ module.exports = class Replicate { * @param {Function} [stop] - Async callback function that is called after each polling attempt. Receives the prediction object as an argument. Return false to cancel polling. * @throws {Error} If the prediction doesn't complete within the maximum number of attempts * @throws {Error} If the prediction failed - * @returns {Promise} Resolves with the completed prediction object + * @returns {Promise} Resolves with the completed prediction object */ async wait(prediction, options, stop) { const { id } = prediction; diff --git a/lib/stream.js b/lib/stream.js index 012d6d0..ca1c38a 100644 --- a/lib/stream.js +++ b/lib/stream.js @@ -1,4 +1,5 @@ // Attempt to use readable-stream if available, attempt to use the built-in stream module. +/** @type {import("stream").Readable} */ let Readable; try { Readable = require("readable-stream").Readable; @@ -49,7 +50,7 @@ class Stream extends Readable { * Create a new stream of server-sent events. * * @param {string} url The URL to connect to. - * @param {object} options The fetch options. + * @param {RequestInit=} options The fetch options. */ constructor(url, options) { if (!Readable) { @@ -63,11 +64,18 @@ class Stream extends Readable { this.options = options; this.event = null; + + /** @type {unknown[]} */ this.data = []; + + /** @type {string | null} */ this.lastEventId = null; + + /** @type {number | null} */ this.retry = null; } + /** @param {string=} line */ decode(line) { if (!line) { if (!this.event && !this.data.length && !this.lastEventId) { diff --git a/lib/trainings.js b/lib/trainings.js index a66db88..e469b96 100644 --- a/lib/trainings.js +++ b/lib/trainings.js @@ -1,3 +1,9 @@ +/** + * @template T + * @typedef {import("./types").Page} Page + */ +/** @typedef {import("./types").Training} Training */ + /** * Create a new training * @@ -6,7 +12,7 @@ * @param {string} version_id - Required. The version ID * @param {object} options * @param {string} options.destination - Required. The destination for the trained version in the form "{username}/{model_name}" - * @param {object} options.input - Required. An object with the model inputs + * @param {unknown} 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`) * @returns {Promise} Resolves with the data for the created training diff --git a/lib/types.js b/lib/types.js index c1cbb0b..fd05845 100644 --- a/lib/types.js +++ b/lib/types.js @@ -3,8 +3,6 @@ * @typedef {"public" | "private"} Visibility * @typedef {"start" | "output" | "logs" | "completed"} WebhookEventType * - * @typedef {import('./lib/error')} ApiError - * * @typedef {Object} Collection * @property {string} name * @property {string} slug @@ -55,7 +53,7 @@ * * @typedef {Prediction} Training * - * @property {Object} ServerSentEvent + * @typedef {Object} ServerSentEvent * @property {string} event * @property {string} data * @property {string=} id @@ -69,3 +67,5 @@ * @property {string=} next * @property {T[]} results */ + +module.exports = {}; From b0530e9acc7797acc3d302ea1c93d7c8fda0a7a3 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Mon, 15 Jan 2024 22:14:11 +0000 Subject: [PATCH 08/11] Add version.js to get package.json version without import --- version.js | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 version.js diff --git a/version.js b/version.js new file mode 100644 index 0000000..fdb8731 --- /dev/null +++ b/version.js @@ -0,0 +1,2 @@ +// Replaced using `npm run build:version` at pack time. +export const version = '0.0.0-dev'; From ac33c62624d45b073683994249ff371d9bd74ed0 Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Mon, 15 Jan 2024 22:14:47 +0000 Subject: [PATCH 09/11] Update dependencies --- package-lock.json | 799 ++++++++++++++++++++++++---------------------- 1 file changed, 416 insertions(+), 383 deletions(-) diff --git a/package-lock.json b/package-lock.json index 19133da..e13d03c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,12 +10,12 @@ "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.4.1", - "@types/jest": "^29.5.3", + "@types/jest": "^29.5.11", "@typescript-eslint/eslint-plugin": "^5.56.0", "cross-fetch": "^3.1.5", - "jest": "^29.6.2", + "jest": "^29.7.0", "nock": "^13.3.0", - "ts-jest": "^29.1.0", + "ts-jest": "^29.1.1", "typescript": "^5.0.2" }, "engines": { @@ -533,9 +533,9 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", - "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -635,9 +635,9 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", - "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1037,16 +1037,16 @@ } }, "node_modules/@jest/console": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.2.tgz", - "integrity": "sha512-0N0yZof5hi44HAR2pPS+ikJ3nzKNoZdVu8FffRf3wy47I7Dm7etk/3KetMdRUqzVd16V4O2m2ISpNTbnIuqy1w==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "dependencies": { - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^29.6.2", - "jest-util": "^29.6.2", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0" }, "engines": { @@ -1054,37 +1054,37 @@ } }, "node_modules/@jest/core": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.2.tgz", - "integrity": "sha512-Oj+5B+sDMiMWLhPFF+4/DvHOf+U10rgvCLGPHP8Xlsy/7QxS51aU/eBngudHlJXnaWD5EohAgJ4js+T6pa+zOg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, "dependencies": { - "@jest/console": "^29.6.2", - "@jest/reporters": "^29.6.2", - "@jest/test-result": "^29.6.2", - "@jest/transform": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.5.0", - "jest-config": "^29.6.2", - "jest-haste-map": "^29.6.2", - "jest-message-util": "^29.6.2", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.6.2", - "jest-resolve-dependencies": "^29.6.2", - "jest-runner": "^29.6.2", - "jest-runtime": "^29.6.2", - "jest-snapshot": "^29.6.2", - "jest-util": "^29.6.2", - "jest-validate": "^29.6.2", - "jest-watcher": "^29.6.2", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", - "pretty-format": "^29.6.2", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, @@ -1101,88 +1101,88 @@ } }, "node_modules/@jest/environment": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.2.tgz", - "integrity": "sha512-AEcW43C7huGd/vogTddNNTDRpO6vQ2zaQNrttvWV18ArBx9Z56h7BIsXkNFJVOO4/kblWEQz30ckw0+L3izc+Q==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, "dependencies": { - "@jest/fake-timers": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.6.2" + "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.2.tgz", - "integrity": "sha512-m6DrEJxVKjkELTVAztTLyS/7C92Y2b0VYqmDROYKLLALHn8T/04yPs70NADUYPrV3ruI+H3J0iUIuhkjp7vkfg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, "dependencies": { - "expect": "^29.6.2", - "jest-snapshot": "^29.6.2" + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.2.tgz", - "integrity": "sha512-6zIhM8go3RV2IG4aIZaZbxwpOzz3ZiM23oxAlkquOIole+G6TrbeXnykxWYlqF7kz2HlBjdKtca20x9atkEQYg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, "dependencies": { - "jest-get-type": "^29.4.3" + "jest-get-type": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.2.tgz", - "integrity": "sha512-euZDmIlWjm1Z0lJ1D0f7a0/y5Kh/koLFMUBE5SUYWrmy8oNhJpbTBDAP6CxKnadcMLDoDf4waRYCe35cH6G6PA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, "dependencies": { - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "jest-message-util": "^29.6.2", - "jest-mock": "^29.6.2", - "jest-util": "^29.6.2" + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/globals": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.2.tgz", - "integrity": "sha512-cjuJmNDjs6aMijCmSa1g2TNG4Lby/AeU7/02VtpW+SLcZXzOLK2GpN2nLqcFjmhy3B3AoPeQVx7BnyOf681bAw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.2", - "@jest/expect": "^29.6.2", - "@jest/types": "^29.6.1", - "jest-mock": "^29.6.2" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/reporters": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.2.tgz", - "integrity": "sha512-sWtijrvIav8LgfJZlrGCdN0nP2EWbakglJY49J1Y5QihcQLfy7ovyxxjJBRXMNltgt4uPtEcFmIMbVshEDfFWw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.6.2", - "@jest/test-result": "^29.6.2", - "@jest/transform": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", @@ -1191,13 +1191,13 @@ "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.6.2", - "jest-util": "^29.6.2", - "jest-worker": "^29.6.2", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", @@ -1215,10 +1215,26 @@ } } }, + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", + "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@jest/schemas": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz", - "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "dependencies": { "@sinclair/typebox": "^0.27.8" @@ -1228,9 +1244,9 @@ } }, "node_modules/@jest/source-map": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.0.tgz", - "integrity": "sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", @@ -1242,13 +1258,13 @@ } }, "node_modules/@jest/test-result": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.2.tgz", - "integrity": "sha512-3VKFXzcV42EYhMCsJQURptSqnyjqCGbtLuX5Xxb6Pm6gUf1wIRIl+mandIRGJyWKgNKYF9cnstti6Ls5ekduqw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, "dependencies": { - "@jest/console": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, @@ -1257,14 +1273,14 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.2.tgz", - "integrity": "sha512-GVYi6PfPwVejO7slw6IDO0qKVum5jtrJ3KoLGbgBWyr2qr4GaxFV6su+ZAjdTX75Sr1DkMFRk09r2ZVa+wtCGw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, "dependencies": { - "@jest/test-result": "^29.6.2", + "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.2", + "jest-haste-map": "^29.7.0", "slash": "^3.0.0" }, "engines": { @@ -1272,22 +1288,22 @@ } }, "node_modules/@jest/transform": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.2.tgz", - "integrity": "sha512-ZqCqEISr58Ce3U+buNFJYUktLJZOggfyvR+bZMaiV1e8B1SIvJbwZMrYz3gx/KAPn9EXmOmN+uB08yLCjWkQQg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.2", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.6.2", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", @@ -1298,12 +1314,12 @@ } }, "node_modules/@jest/types": { - "version": "29.6.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", - "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "dependencies": { - "@jest/schemas": "^29.6.0", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -1421,9 +1437,9 @@ } }, "node_modules/@types/babel__core": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", - "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "dependencies": { "@babel/parser": "^7.20.7", @@ -1434,18 +1450,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", @@ -1453,18 +1469,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": "*" @@ -1495,9 +1511,9 @@ } }, "node_modules/@types/jest": { - "version": "29.5.3", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.3.tgz", - "integrity": "sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA==", + "version": "29.5.11", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.11.tgz", + "integrity": "sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -1865,15 +1881,15 @@ } }, "node_modules/babel-jest": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.2.tgz", - "integrity": "sha512-BYCzImLos6J3BH/+HvUCHG1dTf2MzmAB4jaVxHV+29RZLjR29XuYTmsf2sdDwkrb+FczkGo3kOhE7ga6sI0P4A==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, "dependencies": { - "@jest/transform": "^29.6.2", + "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.5.0", + "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" @@ -1902,9 +1918,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", @@ -1940,12 +1956,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": { @@ -2222,6 +2238,27 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/cross-fetch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", @@ -2302,9 +2339,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" @@ -2639,17 +2676,16 @@ } }, "node_modules/expect": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.2.tgz", - "integrity": "sha512-iAErsLxJ8C+S02QbLAwgSGSezLQK+XXRDt8IuFXFpwCNw2ECmzZSmjKcCaFVp5VRMk+WAvz6h6jokzEzBFZEuA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, "dependencies": { - "@jest/expect-utils": "^29.6.2", - "@types/node": "*", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.6.2", - "jest-message-util": "^29.6.2", - "jest-util": "^29.6.2" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -2791,9 +2827,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, @@ -2805,10 +2841,13 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/gensync": { "version": "1.0.0-beta.2", @@ -2930,18 +2969,6 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2951,6 +2978,18 @@ "node": ">=8" } }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -3063,12 +3102,12 @@ "dev": true }, "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dev": true, "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3226,15 +3265,15 @@ } }, "node_modules/jest": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.2.tgz", - "integrity": "sha512-8eQg2mqFbaP7CwfsTpCxQ+sHzw1WuNWL5UUvjnWP4hx2riGz9fPSzYOaU5q8/GqWn1TfgZIVTqYJygbGbWAANg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "dependencies": { - "@jest/core": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", "import-local": "^3.0.2", - "jest-cli": "^29.6.2" + "jest-cli": "^29.7.0" }, "bin": { "jest": "bin/jest.js" @@ -3252,12 +3291,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": { @@ -3265,28 +3305,28 @@ } }, "node_modules/jest-circus": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.2.tgz", - "integrity": "sha512-G9mN+KOYIUe2sB9kpJkO9Bk18J4dTDArNFPwoZ7WKHKel55eKIS/u2bLthxgojwlf9NLCVQfgzM/WsOVvoC6Fw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.2", - "@jest/expect": "^29.6.2", - "@jest/test-result": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", - "jest-each": "^29.6.2", - "jest-matcher-utils": "^29.6.2", - "jest-message-util": "^29.6.2", - "jest-runtime": "^29.6.2", - "jest-snapshot": "^29.6.2", - "jest-util": "^29.6.2", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "p-limit": "^3.1.0", - "pretty-format": "^29.6.2", + "pretty-format": "^29.7.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" @@ -3296,22 +3336,21 @@ } }, "node_modules/jest-cli": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.2.tgz", - "integrity": "sha512-TT6O247v6dCEX2UGHGyflMpxhnrL0DNqP2fRTKYm3nJJpCTfXX3GCMQPGFjXDoj0i5/Blp3jriKXFgdfmbYB6Q==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, "dependencies": { - "@jest/core": "^29.6.2", - "@jest/test-result": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", + "create-jest": "^29.7.0", "exit": "^0.1.2", - "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^29.6.2", - "jest-util": "^29.6.2", - "jest-validate": "^29.6.2", - "prompts": "^2.0.1", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "yargs": "^17.3.1" }, "bin": { @@ -3330,31 +3369,31 @@ } }, "node_modules/jest-config": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.2.tgz", - "integrity": "sha512-VxwFOC8gkiJbuodG9CPtMRjBUNZEHxwfQXmIudSTzFWxaci3Qub1ddTRbFNQlD/zUeaifLndh/eDccFX4wCMQw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.6.2", - "@jest/types": "^29.6.1", - "babel-jest": "^29.6.2", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^29.6.2", - "jest-environment-node": "^29.6.2", - "jest-get-type": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.6.2", - "jest-runner": "^29.6.2", - "jest-util": "^29.6.2", - "jest-validate": "^29.6.2", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^29.6.2", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -3375,24 +3414,24 @@ } }, "node_modules/jest-diff": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.2.tgz", - "integrity": "sha512-t+ST7CB9GX5F2xKwhwCf0TAR17uNDiaPTZnVymP9lw0lssa9vG+AFyDZoeIHStU3WowFFwT+ky+er0WVl2yGhA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.6.2" + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-docblock": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", - "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, "dependencies": { "detect-newline": "^3.0.0" @@ -3402,62 +3441,62 @@ } }, "node_modules/jest-each": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.2.tgz", - "integrity": "sha512-MsrsqA0Ia99cIpABBc3izS1ZYoYfhIy0NNWqPSE0YXbQjwchyt6B1HD2khzyPe1WiJA7hbxXy77ZoUQxn8UlSw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, "dependencies": { - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", - "jest-util": "^29.6.2", - "pretty-format": "^29.6.2" + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-environment-node": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.2.tgz", - "integrity": "sha512-YGdFeZ3T9a+/612c5mTQIllvWkddPbYcN2v95ZH24oWMbGA4GGS2XdIF92QMhUhvrjjuQWYgUGW2zawOyH63MQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.2", - "@jest/fake-timers": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.6.2", - "jest-util": "^29.6.2" + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-get-type": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", - "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-haste-map": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.2.tgz", - "integrity": "sha512-+51XleTDAAysvU8rT6AnS1ZJ+WHVNqhj1k6nTvN2PYP+HjU3kqlaKQ1Lnw3NYW3bm2r8vq82X0Z1nDDHZMzHVA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, "dependencies": { - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.6.2", - "jest-worker": "^29.6.2", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, @@ -3469,46 +3508,46 @@ } }, "node_modules/jest-leak-detector": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.2.tgz", - "integrity": "sha512-aNqYhfp5uYEO3tdWMb2bfWv6f0b4I0LOxVRpnRLAeque2uqOVVMLh6khnTcE2qJ5wAKop0HcreM1btoysD6bPQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, "dependencies": { - "jest-get-type": "^29.4.3", - "pretty-format": "^29.6.2" + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.2.tgz", - "integrity": "sha512-4LiAk3hSSobtomeIAzFTe+N8kL6z0JtF3n6I4fg29iIW7tt99R7ZcIFW34QkX+DuVrf+CUe6wuVOpm7ZKFJzZQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^29.6.2", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.6.2" + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.2.tgz", - "integrity": "sha512-vnIGYEjoPSuRqV8W9t+Wow95SDp6KPX2Uf7EoeG9G99J2OVh7OSwpS4B6J0NfpEIpfkBNHlBZpA2rblEuEFhZQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.6.2", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -3517,14 +3556,14 @@ } }, "node_modules/jest-mock": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.2.tgz", - "integrity": "sha512-hoSv3lb3byzdKfwqCuT6uTscan471GUECqgNYykg6ob0yiAw3zYc7OrPnI9Qv8Wwoa4lC7AZ9hyS4AiIx5U2zg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, "dependencies": { - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-util": "^29.6.2" + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -3548,26 +3587,26 @@ } }, "node_modules/jest-regex-util": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-resolve": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.2.tgz", - "integrity": "sha512-G/iQUvZWI5e3SMFssc4ug4dH0aZiZpsDq9o1PtXTV1210Ztyb2+w+ZgQkB3iOiC5SmAEzJBOHWz6Hvrd+QnNPw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.2", + "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.6.2", - "jest-validate": "^29.6.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" @@ -3577,43 +3616,43 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.2.tgz", - "integrity": "sha512-LGqjDWxg2fuQQm7ypDxduLu/m4+4Lb4gczc13v51VMZbVP5tSBILqVx8qfWcsdP8f0G7aIqByIALDB0R93yL+w==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, "dependencies": { - "jest-regex-util": "^29.4.3", - "jest-snapshot": "^29.6.2" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-runner": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.2.tgz", - "integrity": "sha512-wXOT/a0EspYgfMiYHxwGLPCZfC0c38MivAlb2lMEAlwHINKemrttu1uSbcGbfDV31sFaPWnWJPmb2qXM8pqZ4w==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, "dependencies": { - "@jest/console": "^29.6.2", - "@jest/environment": "^29.6.2", - "@jest/test-result": "^29.6.2", - "@jest/transform": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", - "jest-docblock": "^29.4.3", - "jest-environment-node": "^29.6.2", - "jest-haste-map": "^29.6.2", - "jest-leak-detector": "^29.6.2", - "jest-message-util": "^29.6.2", - "jest-resolve": "^29.6.2", - "jest-runtime": "^29.6.2", - "jest-util": "^29.6.2", - "jest-watcher": "^29.6.2", - "jest-worker": "^29.6.2", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -3622,31 +3661,31 @@ } }, "node_modules/jest-runtime": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.2.tgz", - "integrity": "sha512-2X9dqK768KufGJyIeLmIzToDmsN0m7Iek8QNxRSI/2+iPFYHF0jTwlO3ftn7gdKd98G/VQw9XJCk77rbTGZnJg==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.6.2", - "@jest/fake-timers": "^29.6.2", - "@jest/globals": "^29.6.2", - "@jest/source-map": "^29.6.0", - "@jest/test-result": "^29.6.2", - "@jest/transform": "^29.6.2", - "@jest/types": "^29.6.1", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.2", - "jest-message-util": "^29.6.2", - "jest-mock": "^29.6.2", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.6.2", - "jest-snapshot": "^29.6.2", - "jest-util": "^29.6.2", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -3655,9 +3694,9 @@ } }, "node_modules/jest-snapshot": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.2.tgz", - "integrity": "sha512-1OdjqvqmRdGNvWXr/YZHuyhh5DeaLp1p/F8Tht/MrMw4Kr1Uu/j4lRG+iKl1DAqUJDWxtQBMk41Lnf/JETYBRA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", @@ -3665,20 +3704,20 @@ "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.6.2", - "@jest/transform": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^29.6.2", + "expect": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-diff": "^29.6.2", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.6.2", - "jest-message-util": "^29.6.2", - "jest-util": "^29.6.2", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "natural-compare": "^1.4.0", - "pretty-format": "^29.6.2", + "pretty-format": "^29.7.0", "semver": "^7.5.3" }, "engines": { @@ -3686,12 +3725,12 @@ } }, "node_modules/jest-util": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.2.tgz", - "integrity": "sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "dependencies": { - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -3703,17 +3742,17 @@ } }, "node_modules/jest-validate": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.2.tgz", - "integrity": "sha512-vGz0yMN5fUFRRbpJDPwxMpgSXW1LDKROHfBopAvDcmD6s+B/s8WJrwi+4bfH4SdInBA5C3P3BI19dBtKzx1Arg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, "dependencies": { - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", + "jest-get-type": "^29.6.3", "leven": "^3.1.0", - "pretty-format": "^29.6.2" + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -3732,18 +3771,18 @@ } }, "node_modules/jest-watcher": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.2.tgz", - "integrity": "sha512-GZitlqkMkhkefjfN/p3SJjrDaxPflqxEAv3/ik10OirZqJGYH5rPiIsgVcfof0Tdqg3shQGdEIxDBx+B4tuLzA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, "dependencies": { - "@jest/test-result": "^29.6.2", - "@jest/types": "^29.6.1", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", - "jest-util": "^29.6.2", + "jest-util": "^29.7.0", "string-length": "^4.0.1" }, "engines": { @@ -3751,13 +3790,13 @@ } }, "node_modules/jest-worker": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.2.tgz", - "integrity": "sha512-l3ccBOabTdkng8I/ORCkADz4eSMKejTYv1vB/Z83UiubqhC1oQ5Li6dWCyqOIvSifGjUBxuvxvlm6KGK2DtuAQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, "dependencies": { "@types/node": "*", - "jest-util": "^29.6.2", + "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -4364,12 +4403,12 @@ } }, "node_modules/pretty-format": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.2.tgz", - "integrity": "sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "dependencies": { - "@jest/schemas": "^29.6.0", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -4431,9 +4470,9 @@ } }, "node_modules/pure-rand": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", - "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", + "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", "dev": true, "funding": [ { @@ -4498,12 +4537,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" }, @@ -4901,9 +4940,9 @@ "dev": true }, "node_modules/ts-jest": { - "version": "29.1.0", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.0.tgz", - "integrity": "sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA==", + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", "dev": true, "dependencies": { "bs-logger": "0.x", @@ -4912,7 +4951,7 @@ "json5": "^2.2.3", "lodash.memoize": "4.x", "make-error": "1.x", - "semver": "7.x", + "semver": "^7.5.3", "yargs-parser": "^21.0.1" }, "bin": { @@ -5049,25 +5088,19 @@ } }, "node_modules/v8-to-istanbul": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" + "convert-source-map": "^2.0.0" }, "engines": { "node": ">=10.12.0" } }, - "node_modules/v8-to-istanbul/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", From 1e0471ecdc50251088a8f2ab26e07237ce7ff86b Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Mon, 15 Jan 2024 22:15:37 +0000 Subject: [PATCH 10/11] Convert codebase into ESM --- index.cjs | 1 + index.js | 11 +++++---- index.test.ts | 2 +- integration/typescript/constructor.test.ts | 2 +- integration/typescript/deprecated.test.ts | 2 +- integration/typescript/singleton.test.ts | 2 +- integration/typescript/tsconfig.json | 2 +- jest.config.js | 22 ++++++++++++------ lib/collections.js | 3 ++- lib/deployments.js | 4 +--- lib/error.js | 2 +- lib/hardware.js | 4 +--- lib/identifier.js | 2 +- lib/models.js | 10 ++++----- lib/predictions.js | 10 ++++----- lib/replicate.js | 26 +++++++++++----------- lib/stream.js | 9 ++------ lib/trainings.js | 10 ++++----- lib/types.js | 3 +-- lib/util.js | 6 ++--- package.json | 15 ++++++++++--- 21 files changed, 74 insertions(+), 74 deletions(-) create mode 100644 index.cjs diff --git a/index.cjs b/index.cjs new file mode 100644 index 0000000..ee42af4 --- /dev/null +++ b/index.cjs @@ -0,0 +1 @@ +module.exports = require('./dist/commonjs').default; diff --git a/index.js b/index.js index 5ad71ca..280a259 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,5 @@ -const ReplicateClass = require("./lib/replicate"); -const ApiError = require("./lib/error"); -require("./lib/types"); +import ReplicateClass from "./lib/replicate.js"; +import ApiError from "./lib/error.js"; /** * Placeholder class used to warn of deprecated constructor. @@ -73,13 +72,13 @@ const replicate = new Proxy(DeprecatedReplicate, { } }); -module.exports = replicate; +export default replicate; // - Type Definitions /** - * @typedef {import("./lib/replicate")} Replicate - * @typedef {import("./lib/error")} ApiError + * @typedef {import("./lib/replicate").default} Replicate + * @typedef {import("./lib/error").default} ApiError * @typedef {import("./lib/types").Collection} Collection * @typedef {import("./lib/types").ModelVersion} ModelVersion * @typedef {import("./lib/types").Hardware} Hardware diff --git a/index.test.ts b/index.test.ts index e1ee29a..3eadf87 100644 --- a/index.test.ts +++ b/index.test.ts @@ -1,5 +1,5 @@ import { expect, jest, test } from "@jest/globals"; -import replicate, { ApiError, Model, Prediction, Replicate } from "./"; +import replicate, { ApiError, Model, Prediction, Replicate } from "./index.js"; import nock from "nock"; import fetch from "cross-fetch"; import assert from "node:assert"; diff --git a/integration/typescript/constructor.test.ts b/integration/typescript/constructor.test.ts index b8f2b4d..80c38d6 100644 --- a/integration/typescript/constructor.test.ts +++ b/integration/typescript/constructor.test.ts @@ -1,5 +1,5 @@ import { test } from 'node:test'; -import assert from 'node:assert'; +import * as assert from 'node:assert'; import replicate_ from "replicate"; const replicate = new replicate_.Replicate(); diff --git a/integration/typescript/deprecated.test.ts b/integration/typescript/deprecated.test.ts index c3f85b1..a3c4059 100644 --- a/integration/typescript/deprecated.test.ts +++ b/integration/typescript/deprecated.test.ts @@ -1,5 +1,5 @@ import { test } from 'node:test'; -import assert from 'node:assert'; +import * as assert from 'node:assert'; import Replicate from "replicate"; const replicate = new Replicate(); diff --git a/integration/typescript/singleton.test.ts b/integration/typescript/singleton.test.ts index c34f8fb..f27a94b 100644 --- a/integration/typescript/singleton.test.ts +++ b/integration/typescript/singleton.test.ts @@ -1,5 +1,5 @@ import { test } from 'node:test'; -import assert from 'node:assert'; +import * as assert from 'node:assert'; import replicate from "replicate"; async function main() { diff --git a/integration/typescript/tsconfig.json b/integration/typescript/tsconfig.json index 4b733f2..70d29a2 100644 --- a/integration/typescript/tsconfig.json +++ b/integration/typescript/tsconfig.json @@ -77,7 +77,7 @@ // "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. */ + "esModuleInterop": false, /* 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. */ diff --git a/jest.config.js b/jest.config.js index 058816a..6691a51 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,15 +1,23 @@ // eslint-disable-next-line jsdoc/valid-types /** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: "ts-jest", - testEnvironment: "node", +export default { + // [...] + preset: 'ts-jest/presets/js-with-ts-esm', // or other ESM presets + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.js$': '$1', + }, testPathIgnorePatterns: ["integration"], + testEnvironment: "node", + extensionsToTreatAsEsm: ['.ts'], + transform: { - "^.+\\.ts?$": [ - "ts-jest", + // '^.+\\.[tj]sx?$' to process js/ts with `ts-jest` + // '^.+\\.m?[tj]sx?$' to process js/ts/mjs/mts with `ts-jest` + '^.+\\.tsx?$': [ + 'ts-jest', { - tsconfig: "tsconfig.json", + useESM: true, }, ], }, -}; +} diff --git a/lib/collections.js b/lib/collections.js index 4175934..f908fbb 100644 --- a/lib/collections.js +++ b/lib/collections.js @@ -31,4 +31,5 @@ async function listCollections() { return response.json(); } -module.exports = { get: getCollection, list: listCollections }; +export const get = getCollection; +export const list = listCollections; diff --git a/lib/deployments.js b/lib/deployments.js index c071e1d..bcf811a 100644 --- a/lib/deployments.js +++ b/lib/deployments.js @@ -35,8 +35,6 @@ async function createPrediction(deployment_owner, deployment_name, options) { return response.json(); } -module.exports = { - predictions: { +export const predictions = { create: createPrediction, - }, }; diff --git a/lib/error.js b/lib/error.js index cf05cd1..e863ec4 100644 --- a/lib/error.js +++ b/lib/error.js @@ -18,4 +18,4 @@ class ApiError extends Error { } } -module.exports = ApiError; +export default ApiError; diff --git a/lib/hardware.js b/lib/hardware.js index 755bd91..8644000 100644 --- a/lib/hardware.js +++ b/lib/hardware.js @@ -12,6 +12,4 @@ async function listHardware() { return response.json(); } -module.exports = { - list: listHardware, -}; +export const list = listHardware; diff --git a/lib/identifier.js b/lib/identifier.js index f9e9786..a054081 100644 --- a/lib/identifier.js +++ b/lib/identifier.js @@ -36,4 +36,4 @@ class ModelVersionIdentifier { } } -module.exports = ModelVersionIdentifier; +export default ModelVersionIdentifier; diff --git a/lib/models.js b/lib/models.js index e7cbcd8..0279892 100644 --- a/lib/models.js +++ b/lib/models.js @@ -98,9 +98,7 @@ async function createModel(model_owner, model_name, options) { return response.json(); } -module.exports = { - get: getModel, - list: listModels, - create: createModel, - versions: { list: listModelVersions, get: getModelVersion }, -}; +export const get = getModel; +export const list = listModels; +export const create = createModel; +export const versions = { list: listModelVersions, get: getModelVersion }; diff --git a/lib/predictions.js b/lib/predictions.js index 2143f5b..9bf3731 100644 --- a/lib/predictions.js +++ b/lib/predictions.js @@ -87,9 +87,7 @@ async function listPredictions() { return response.json(); } -module.exports = { - create: createPrediction, - get: getPrediction, - cancel: cancelPrediction, - list: listPredictions, -}; +export const create = createPrediction; +export const get = getPrediction; +export const cancel = cancelPrediction; +export const list = listPredictions; diff --git a/lib/replicate.js b/lib/replicate.js index 281fb49..d5fb6cd 100644 --- a/lib/replicate.js +++ b/lib/replicate.js @@ -6,19 +6,19 @@ /** @typedef {import("./types").Prediction} Prediction */ /** @typedef {import("./types").WebhookEventType} WebhookEventType */ -const ApiError = require("./error"); -const ModelVersionIdentifier = require("./identifier"); -const { Stream } = require("./stream"); -const { withAutomaticRetries } = require("./util"); +import ApiError from "./error.js"; +import ModelVersionIdentifier from "./identifier.js"; +import { Stream } from "./stream.js"; +import { withAutomaticRetries } from "./util.js"; -const collections = require("./collections"); -const deployments = require("./deployments"); -const hardware = require("./hardware"); -const models = require("./models"); -const predictions = require("./predictions"); -const trainings = require("./trainings"); +import * as collections from "./collections.js" +import * as deployments from "./deployments.js" +import * as hardware from "./hardware.js" +import * as models from "./models.js" +import * as predictions from "./predictions.js" +import * as trainings from "./trainings.js" +import { version } from "../version.js"; -const packageJSON = require("../package.json"); /** * Replicate API client library @@ -39,7 +39,7 @@ const packageJSON = require("../package.json"); * const input = {text: 'Hello, world!'} * const output = await replicate.run(model, { input }); */ -module.exports = class Replicate { +export default class Replicate { /** * Create a new Replicate API client instance. * @@ -69,7 +69,7 @@ module.exports = class Replicate { /** @type {string} */ this.userAgent = - options.userAgent || `replicate-javascript/${packageJSON.version}`; + options.userAgent || `replicate-javascript/${version}`; /** @type {string} */ this.baseUrl = options.baseUrl || "https://api.replicate.com/v1"; diff --git a/lib/stream.js b/lib/stream.js index ca1c38a..8237a72 100644 --- a/lib/stream.js +++ b/lib/stream.js @@ -14,7 +14,7 @@ try { /** * A server-sent event. */ -class ServerSentEvent { +export class ServerSentEvent { /** * Create a new server-sent event. * @@ -45,7 +45,7 @@ class ServerSentEvent { /** * A stream of server-sent events. */ -class Stream extends Readable { +export class Stream extends Readable { /** * Create a new stream of server-sent events. * @@ -140,8 +140,3 @@ class Stream extends Readable { } } } - -module.exports = { - Stream, - ServerSentEvent, -}; diff --git a/lib/trainings.js b/lib/trainings.js index e469b96..1de27ec 100644 --- a/lib/trainings.js +++ b/lib/trainings.js @@ -81,9 +81,7 @@ async function listTrainings() { return response.json(); } -module.exports = { - create: createTraining, - get: getTraining, - cancel: cancelTraining, - list: listTrainings, -}; +export const create = createTraining; +export const get = getTraining; +export const cancel = cancelTraining; +export const list = listTrainings; diff --git a/lib/types.js b/lib/types.js index fd05845..6c91545 100644 --- a/lib/types.js +++ b/lib/types.js @@ -67,5 +67,4 @@ * @property {string=} next * @property {T[]} results */ - -module.exports = {}; +export default {}; diff --git a/lib/util.js b/lib/util.js index 7b12633..c11249d 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,4 +1,4 @@ -const ApiError = require("./error"); +import ApiError from "./error.js"; /** * Automatically retry a request if it fails with an appropriate status code. @@ -19,7 +19,7 @@ const ApiError = require("./error"); * @returns {Promise} - Resolves with the response object * @throws {ApiError} If the request failed */ -async function withAutomaticRetries(request, options = {}) { +export async function withAutomaticRetries(request, options = {}) { const shouldRetry = options.shouldRetry || (() => false); const maxRetries = options.maxRetries || 5; const interval = options.interval || 500; @@ -67,5 +67,3 @@ async function withAutomaticRetries(request, options = {}) { return request(); } - -module.exports = { withAutomaticRetries }; diff --git a/package.json b/package.json index 070c141..3aabda6 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,14 @@ "homepage": "https://github.com/replicate/replicate-javascript#readme", "bugs": "https://github.com/replicate/replicate-javascript/issues", "license": "Apache-2.0", - "main": "index.js", - "types": "dist/types/index.d.ts", + "type": "module", + "exports": { + ".": { + "types": "./dist/types/index.d.ts", + "import": "./index.js", + "require": "./index.cjs" + } + }, "engines": { "node": ">=18.0.0", "npm": ">=7.19.0", @@ -19,7 +25,10 @@ "format": "biome format . --write", "lint": "biome lint .", "test": "REPLICATE_API_TOKEN=test-token jest", - "types": "tsc --target ES2022 --declaration --emitDeclarationOnly --allowJs --types node --outDir ./dist/types index.js ./lib/types.js", + "build": "", + "build:version": "echo \"export const version = '\"$(node -pe 'require(\"./package.json\").version')\"';\" > version.js", + "build:types": "tsc --target ES2022 --declaration --emitDeclarationOnly --allowJs --types node --outDir ./dist/types index.js", + "build:commonjs": "tsc --target ES2022 --allowJs --module commonjs --sourceMap --types node --outDir ./dist/commonjs index.js && echo '{\"type\": \"commonjs\"}' > ./dist/commonjs/package.json", "test:integration": "npm --prefix integration/commonjs test;npm --prefix integration/esm test;npm --prefix integration/typescript test", "test:all": "npm run test && npm run test:integration" }, From 9eb73a785685870ec42e6a689aaa6c854cde3b7d Mon Sep 17 00:00:00 2001 From: Aron Carroll Date: Tue, 16 Jan 2024 09:52:06 +0000 Subject: [PATCH 11/11] Clean up esm imports in integration tests --- index.js | 5 +++-- integration/esm/constructor.test.js | 4 ++-- integration/typescript/constructor.test.ts | 4 ++-- package.json | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index 280a259..45b3c6a 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,9 @@ import ReplicateClass from "./lib/replicate.js"; import ApiError from "./lib/error.js"; +export { default as Replicate } from "./lib/replicate.js"; +export { default as ApiError } from "./lib/error.js"; + /** * Placeholder class used to warn of deprecated constructor. * @deprecated use exported Replicate class instead @@ -77,8 +80,6 @@ export default replicate; // - Type Definitions /** - * @typedef {import("./lib/replicate").default} Replicate - * @typedef {import("./lib/error").default} ApiError * @typedef {import("./lib/types").Collection} Collection * @typedef {import("./lib/types").ModelVersion} ModelVersion * @typedef {import("./lib/types").Hardware} Hardware diff --git a/integration/esm/constructor.test.js b/integration/esm/constructor.test.js index 206342f..3865fca 100644 --- a/integration/esm/constructor.test.js +++ b/integration/esm/constructor.test.js @@ -1,8 +1,8 @@ import { test } from 'node:test'; import assert from 'node:assert'; -import replicate_ from "replicate"; +import { Replicate } from "replicate"; -const replicate = new replicate_.Replicate(); +const replicate = new Replicate(); async function main() { return await replicate.run( diff --git a/integration/typescript/constructor.test.ts b/integration/typescript/constructor.test.ts index 80c38d6..35c68b7 100644 --- a/integration/typescript/constructor.test.ts +++ b/integration/typescript/constructor.test.ts @@ -1,8 +1,8 @@ import { test } from 'node:test'; import * as assert from 'node:assert'; -import replicate_ from "replicate"; +import { Replicate } from "replicate"; -const replicate = new replicate_.Replicate(); +const replicate = new Replicate(); async function main() { return await replicate.run( diff --git a/package.json b/package.json index 3aabda6..ae63527 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "format": "biome format . --write", "lint": "biome lint .", "test": "REPLICATE_API_TOKEN=test-token jest", - "build": "", + "build": "npm run build:version && npm run build:types && npm run build:commonjs", "build:version": "echo \"export const version = '\"$(node -pe 'require(\"./package.json\").version')\"';\" > version.js", "build:types": "tsc --target ES2022 --declaration --emitDeclarationOnly --allowJs --types node --outDir ./dist/types index.js", "build:commonjs": "tsc --target ES2022 --allowJs --module commonjs --sourceMap --types node --outDir ./dist/commonjs index.js && echo '{\"type\": \"commonjs\"}' > ./dist/commonjs/package.json",