From 70b8af7db6a6c0b3f34c6e401471c30b0c46f880 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Wed, 5 Apr 2023 05:39:20 -0700 Subject: [PATCH 1/4] Fix reported return type of listPredictions --- lib/predictions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/predictions.js b/lib/predictions.js index 5d531f52..b688feeb 100644 --- a/lib/predictions.js +++ b/lib/predictions.js @@ -42,7 +42,7 @@ async function getPrediction(prediction_id) { /** * List all predictions * - * @returns {Promise} - Resolves with the list of predictions + * @returns {Promise} - Resolves with a page of predictions */ async function listPredictions() { return this.request('/predictions', { From 14f71d7d585efe33c46bacc6686f6564ba0c0aaf Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Wed, 5 Apr 2023 05:40:31 -0700 Subject: [PATCH 2/4] Refactor TypeScript type definitions Inline option types Fix method signatures Define Page type --- index.d.ts | 81 ++++++++++++++++++++++-------------------------------- 1 file changed, 33 insertions(+), 48 deletions(-) diff --git a/index.d.ts b/index.d.ts index e8ddb700..6b6cae83 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,13 +1,13 @@ type Identifier = `${string}/${string}:${string}`; type WebhookEventType = "start" | "output" | "logs" | "completed"; -declare module "replicate" { - export interface ReplicateOptions { - auth: string; - userAgent?: string; - baseUrl?: string; - } +interface Page { + previous?: string; + next?: string; + results: T[]; +} +declare module "replicate" { export interface Collection { id: string; name: string; @@ -43,39 +43,17 @@ declare module "replicate" { updated: string; } - export interface CollectionsGetOptions { - collection_slug: string; - } - - export interface ModelsGetOptions { - model_owner: string; - model_name: string; - } - - export interface ModelsVersionsListOptions { - model_owner: string; - model_name: string; - } - - export interface ModelsVersionsGetOptions { - model_owner: string; - model_name: string; - id: string; - } + export default class Replicate { + constructor(options: { + auth: string; + userAgent?: string; + baseUrl?: string; + }); - export interface PredictionsCreateOptions { - version: string; - input: any; - webhook?: string; - webhook_events_filter?: WebhookEventType[]; - } - - export interface PredictionsGetOptions { - predictionId: string; - } - - export class Replicate { - constructor(options: ReplicateOptions); + auth: string; + userAgent?: string; + baseUrl?: string; + private instance: any; run( identifier: Identifier, @@ -87,7 +65,7 @@ declare module "replicate" { } ): Promise; request(route: string, parameters: any): Promise; - paginate(endpoint: () => Promise): AsyncGenerator; + paginate(endpoint: () => Promise>): AsyncGenerator<[ T ]>; wait( prediction: Prediction, options: { @@ -97,23 +75,30 @@ declare module "replicate" { ): Promise; collections: { - get(options: CollectionsGetOptions): Promise; + get(collection_slug: string): Promise; }; models: { - get(options: ModelsGetOptions): Promise; + get(model_owner: string, model_name: string): Promise; versions: { - list(options: ModelsVersionsListOptions): Promise; - get(options: ModelsVersionsGetOptions): Promise; + list(model_owner: string, model_name: string): Promise; + get( + model_owner: string, + model_name: string, + version_id: string + ): Promise; }; }; predictions: { - create(options: PredictionsCreateOptions): Promise; - get(options: PredictionsGetOptions): Promise; - list(): Promise; + create(options: { + version: string; + input: any; + webhook?: string; + webhook_events_filter?: WebhookEventType[]; + }): Promise; + get(prediction_id: string): Promise; + list(): Promise>; }; } - - export default Replicate; } From 5211e27d08551f4c2bfe0f7e62f67f71c64f72f9 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Wed, 5 Apr 2023 05:41:28 -0700 Subject: [PATCH 3/4] Run unit tests with TypeScript Fix and refactor unit tests --- index.test.js | 153 ------------------------- index.test.ts | 280 ++++++++++++++++++++++++++++++++++++++++++++++ jest.config.js | 5 + package-lock.json | 139 +++++++++++++++++++++++ package.json | 3 + tsconfig.json | 3 + 6 files changed, 430 insertions(+), 153 deletions(-) delete mode 100644 index.test.js create mode 100644 index.test.ts create mode 100644 jest.config.js create mode 100644 tsconfig.json diff --git a/index.test.js b/index.test.js deleted file mode 100644 index b2699199..00000000 --- a/index.test.js +++ /dev/null @@ -1,153 +0,0 @@ -const Replicate = require('./index'); -require('jest'); - -describe('Replicate client', () => { - let client; - beforeEach(() => { - client = new Replicate({ baseUrl: 'https://api.replicate.com/v1' }); - }); - - test('Constructor sets default baseUrl', () => { - const clientWithoutBaseUrl = new Replicate({}); - expect(clientWithoutBaseUrl.baseUrl).toBe('https://api.replicate.com/v1'); - }); - - describe('collections.get', () => { - test('Calls the correct API route', async () => { - client.request = jest.fn(); - await client.collections.get('text-to-image'); - expect(client.request).toHaveBeenCalledWith( - '/collections/text-to-image', - { - method: 'GET', - } - ); - }); - - // Add more tests for error handling, edge cases, etc. - }); - - describe('models.get', () => { - test('Calls the correct API route', async () => { - client.request = jest.fn(); - await client.models.get('owner', 'name'); - expect(client.request).toHaveBeenCalledWith('/models/owner/name', { - method: 'GET', - }); - }); - - // Add more tests for error handling, edge cases, etc. - }); - - describe('predictions.create', () => { - test('Calls the correct API route with the correct payload', async () => { - client.request = jest.fn(); - const input = { text: 'Hello, world!' }; - await client.predictions.create({ - version: - '632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532', - input, - }); - expect(client.request).toHaveBeenCalledWith('/predictions', { - method: 'POST', - data: { - version: - '632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532', - input: { - text: 'Hello, world!', - }, - }, - }); - }); - - test('Calls the correct API route with the correct payload, webhook URL and webhook filters', async () => { - client.request = jest.fn(); - const input = { text: 'Hello, world!' }; - await client.predictions.create({ - version: - '632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532', - input, - webhook: 'http://test.host/webhook', - webhook_events_filter: ['output', 'completed'], - }); - expect(client.request).toHaveBeenCalledWith('/predictions', { - method: 'POST', - data: { - version: - '632231d0d49d34d5c4633bd838aee3d81d936e59a886fbf28524702003b4c532', - input: { - text: 'Hello, world!', - }, - webhook: 'http://test.host/webhook', - webhook_events_filter: ['output', 'completed'], - }, - }); - }); - - // Add more tests for error handling, edge cases, etc. - }); - - describe('predictions.get', () => { - test('Calls the correct API route with the correct payload', async () => { - client.request = jest.fn(); - await client.predictions.get(123); - expect(client.request).toHaveBeenCalledWith('/predictions/123', { - method: 'GET', - }); - }); - - // Add more tests for error handling, edge cases, etc. - }); - - describe('predictions.list', () => { - test('Calls the correct API route with the correct payload', async () => { - client.request = jest.fn(); - await client.predictions.list(); - expect(client.request).toHaveBeenCalledWith('/predictions', { - method: 'GET', - }); - }); - - test('Paginates results', async () => { - client.request = jest.fn(); - client.request.mockResolvedValueOnce({ - results: [{ id: 1 }, { id: 2 }], - next: 'https://api.replicate.com/v1/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw', - }); - client.request.mockResolvedValueOnce({ - results: [{ id: 3 }], - next: null, - }); - - const results = []; - // eslint-disable-next-line no-restricted-syntax - for await (const page of client.paginate(client.predictions.list)) { - results.push(...page); - } - expect(client.request).toHaveBeenCalledTimes(2); - expect(results).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]); - }); - - // Add more tests for error handling, edge cases, etc. - }); - - describe('run', () => { - test('Calls the correct API routes', async () => { - client.request = jest.fn(); - client.request.mockResolvedValueOnce({ - status: 'processing', - id: 'prediction-id', - }); - client.request.mockResolvedValueOnce({ - status: 'succeeded', - output: 'foobar', - }); - const output = await client.run('owner/model:abc123', { - input: { text: 'Hello, world!' }, - }); - expect(output).toBe('foobar'); - }); - }); - - // Continue with tests for other methods -}); diff --git a/index.test.ts b/index.test.ts new file mode 100644 index 00000000..730d8da8 --- /dev/null +++ b/index.test.ts @@ -0,0 +1,280 @@ +import { expect, jest, test } from '@jest/globals'; + +import Replicate, { Prediction } from "replicate"; + +import axios from 'axios'; + +describe('Replicate client', () => { + let client: Replicate; + + beforeEach(() => { + client = new Replicate({ auth: 'test-token' }); + client[ 'instance' ] = jest.fn(); + }); + + describe('constructor', () => { + test('Sets default baseUrl', () => { + expect(client.baseUrl).toBe('https://api.replicate.com/v1'); + }); + + test('Sets custom baseUrl', () => { + const clientWithCustomBaseUrl = new Replicate({ baseUrl: 'https://example.com/', auth: 'test-token' }); + expect(clientWithCustomBaseUrl.baseUrl).toBe('https://example.com/'); + }); + + test('Sets custom userAgent', () => { + const clientWithCustomUserAgent = new Replicate({ userAgent: 'my-app/1.2.3', auth: 'test-token' }); + expect(clientWithCustomUserAgent.userAgent).toBe('my-app/1.2.3'); + }); + }); + + describe('collections.get', () => { + test('Calls the correct API route', async () => { + client[ 'instance' ].mockResolvedValueOnce({ + data: { + "name": "Super resolution", + "slug": "super-resolution", + "description": "Upscaling models that create high-quality images from low-quality images.", + "models": [] + } + }); + const collection = await client.collections.get('super-resolution'); + expect(client[ 'instance' ]).toHaveBeenCalledWith( + '/collections/super-resolution', + { + method: 'GET', + } + ); + expect(collection.name).toBe('Super resolution'); + }); + + // Add more tests for error handling, edge cases, etc. + }); + + describe('models.get', () => { + test('Calls the correct API route', async () => { + client[ 'instance' ].mockResolvedValueOnce({ + data: { + "url": "https://replicate.com/replicate/hello-world", + "owner": "replicate", + "name": "hello-world", + "description": "A tiny model that says hello", + "visibility": "public", + "github_url": "https://github.com/replicate/cog-examples", + "paper_url": null, + "license_url": null, + "run_count": 12345, + "cover_image_url": "", + "default_example": {}, + "latest_version": {} + } + }); + await client.models.get('replicate', 'hello-world'); + expect(client[ 'instance' ]).toHaveBeenCalledWith('/models/replicate/hello-world', { + method: 'GET', + }); + }); + + // Add more tests for error handling, edge cases, etc. + }); + + describe('predictions.create', () => { + test('Calls the correct API route with the correct payload', async () => { + client[ 'instance' ].mockResolvedValueOnce({ + data: { + "id": "ufawqhfynnddngldkgtslldrkq", + "version": "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + "urls": { + "get": "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", + "cancel": "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel" + }, + "created_at": "2022-04-26T22:13:06.224088Z", + "started_at": null, + "completed_at": null, + "status": "starting", + "input": { + "text": "Alice" + }, + "output": null, + "error": null, + "logs": null, + "metrics": {} + } + }); + + const prediction = await client.predictions.create({ + version: + '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + input: { + text: 'Alice' + }, + webhook: 'http://test.host/webhook', + webhook_events_filter: [ 'output', 'completed' ], + }); + expect(prediction.id).toBe('ufawqhfynnddngldkgtslldrkq'); + + expect(client[ 'instance' ]).toHaveBeenCalledWith('/predictions', { + method: 'POST', + data: { + version: + '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + input: { + text: 'Alice', + }, + webhook: 'http://test.host/webhook', + webhook_events_filter: [ 'output', 'completed' ], + }, + }); + }); + + // Add more tests for error handling, edge cases, etc. + }); + + describe('predictions.get', () => { + test('Calls the correct API route with the correct payload', async () => { + client[ 'instance' ].mockResolvedValueOnce({ + data: { + "id": "rrr4z55ocneqzikepnug6xezpe", + "version": "be04660a5b93ef2aff61e3668dedb4cbeb14941e62a3fd5998364a32d613e35e", + "urls": { + "get": "https://api.replicate.com/v1/predictions/rrr4z55ocneqzikepnug6xezpe", + "cancel": "https://api.replicate.com/v1/predictions/rrr4z55ocneqzikepnug6xezpe/cancel" + }, + "created_at": "2022-09-13T22:54:18.578761Z", + "started_at": "2022-09-13T22:54:19.438525Z", + "completed_at": "2022-09-13T22:54:23.236610Z", + "source": "api", + "status": "succeeded", + "input": { + "prompt": "oak tree with boletus growing on its branches" + }, + "output": [ + "https://replicate.com/api/models/stability-ai/stable-diffusion/files/9c3b6fe4-2d37-4571-a17a-83951b1cb120/out-0.png" + ], + "error": null, + "logs": "Using seed: 36941...", + "metrics": { + "predict_time": 4.484541 + } + } + }); + + const prediction = await client.predictions.get('rrr4z55ocneqzikepnug6xezpe'); + expect(prediction.id).toBe('rrr4z55ocneqzikepnug6xezpe'); + + expect(client[ 'instance' ]).toHaveBeenCalledWith('/predictions/rrr4z55ocneqzikepnug6xezpe', { + method: 'GET', + }); + }); + + // Add more tests for error handling, edge cases, etc. + }); + + describe('predictions.list', () => { + test('Calls the correct API route with the correct payload', async () => { + client[ 'instance' ].mockResolvedValueOnce({ + data: { + next: "https://api.replicate.com/v1/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw", + previous: null, + results: [ + { + "id": "jpzd7hm5gfcapbfyt4mqytarku", + "version": "b21cbe271e65c1718f2999b038c18b45e21e4fba961181fbfae9342fc53b9e05", + "urls": { + "get": "https://api.replicate.com/v1/predictions/jpzd7hm5gfcapbfyt4mqytarku", + "cancel": "https://api.replicate.com/v1/predictions/jpzd7hm5gfcapbfyt4mqytarku/cancel" + }, + "created_at": "2022-04-26T20:00:40.658234Z", + "started_at": "2022-04-26T20:00:84.583803Z", + "completed_at": "2022-04-26T20:02:27.648305Z", + "source": "web", + "status": "succeeded" + }, + ], + } + }); + + const predictions = await client.predictions.list(); + expect(predictions.results.length).toBe(1); + expect(predictions.results[ 0 ].id).toBe('jpzd7hm5gfcapbfyt4mqytarku'); + + expect(client[ 'instance' ]).toHaveBeenCalledWith('/predictions', { + method: 'GET', + }); + }); + + test('Paginates results', async () => { + client[ 'instance' ].mockResolvedValueOnce({ + data: { + results: [ + { id: 'ufawqhfynnddngldkgtslldrkq' }, + ], + next: 'https://api.replicate.com/v1/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw', + } + }); + client[ 'instance' ].mockResolvedValueOnce({ + data: { + results: [ + { id: 'rrr4z55ocneqzikepnug6xezpe' } + ], + next: null, + } + }); + + const results: Prediction[] = []; + // eslint-disable-next-line no-restricted-syntax + for await (const batch of client.paginate(client.predictions.list)) { + results.push(...batch); + } + expect(results).toEqual([ + { id: 'ufawqhfynnddngldkgtslldrkq' }, + { id: 'rrr4z55ocneqzikepnug6xezpe' } + ]); + + expect(client[ 'instance' ]).toHaveBeenCalledWith('/predictions', { + method: 'GET', + }); + expect(client[ 'instance' ]).toHaveBeenCalledWith('https://api.replicate.com/v1/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw', { + method: 'GET', + }); + }); + + // Add more tests for error handling, edge cases, etc. + }); + + describe('run', () => { + test('Calls the correct API routes', async () => { + client[ 'instance' ].mockResolvedValueOnce({ + data: { + id: 'ufawqhfynnddngldkgtslldrkq', + status: 'processing', + } + }); + client[ 'instance' ].mockResolvedValueOnce({ + data: { + id: 'ufawqhfynnddngldkgtslldrkq', + status: 'succeeded', + output: 'foobar', + } + }); + const output = await client.run('owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', { + input: { text: 'Hello, world!' }, + }); + expect(client[ 'instance' ]).toHaveBeenCalledWith('/predictions', { + method: 'POST', + data: { + version: '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + input: { + text: 'Hello, world!', + }, + } + }); + expect(client[ 'instance' ]).toHaveBeenCalledWith('/predictions/ufawqhfynnddngldkgtslldrkq', { + method: 'GET', + }); + expect(output).toBe('foobar'); + }); + }); + + // Continue with tests for other methods +}); diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..3745fc22 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', +}; diff --git a/package-lock.json b/package-lock.json index 78cc347b..3d448519 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,8 @@ "axios": "^1.3.4" }, "devDependencies": { + "@tsconfig/recommended": "^1.0.2", + "@types/jest": "^29.5.0", "@typescript-eslint/eslint-plugin": "^5.56.0", "eslint": "^8.36.0", "eslint-config-airbnb-base": "^15.0.0", @@ -21,6 +23,7 @@ "eslint-plugin-n": "^15.6.1", "eslint-plugin-promise": "^6.1.1", "jest": "^29.5.0", + "ts-jest": "^29.1.0", "typescript": "^5.0.2" } }, @@ -1232,6 +1235,12 @@ "@sinonjs/commons": "^2.0.0" } }, + "node_modules/@tsconfig/recommended": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/recommended/-/recommended-1.0.2.tgz", + "integrity": "sha512-dbHBtbWBOjq0/otpopAE02NT2Cm05Qe2JsEKeCf/wjSYbI2hz8nCqnpnOJWHATgjDz4fd3dchs3Wy1gQGjfN6w==", + "dev": true + }, "node_modules/@types/babel__core": { "version": "7.20.0", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", @@ -1306,6 +1315,16 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -1904,6 +1923,18 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -4504,6 +4535,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -4543,6 +4580,12 @@ "semver": "bin/semver.js" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -5592,6 +5635,49 @@ "node": ">=8.0" } }, + "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==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, "node_modules/tsconfig-paths": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", @@ -6866,6 +6952,12 @@ "@sinonjs/commons": "^2.0.0" } }, + "@tsconfig/recommended": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/recommended/-/recommended-1.0.2.tgz", + "integrity": "sha512-dbHBtbWBOjq0/otpopAE02NT2Cm05Qe2JsEKeCf/wjSYbI2hz8nCqnpnOJWHATgjDz4fd3dchs3Wy1gQGjfN6w==", + "dev": true + }, "@types/babel__core": { "version": "7.20.0", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", @@ -6940,6 +7032,16 @@ "@types/istanbul-lib-report": "*" } }, + "@types/jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==", + "dev": true, + "requires": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -7345,6 +7447,15 @@ "update-browserslist-db": "^1.0.10" } }, + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.x" + } + }, "bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -9248,6 +9359,12 @@ "p-locate": "^5.0.0" } }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -9280,6 +9397,12 @@ } } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -10036,6 +10159,22 @@ "is-number": "^7.0.0" } }, + "ts-jest": { + "version": "29.1.0", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.0.tgz", + "integrity": "sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA==", + "dev": true, + "requires": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "^21.0.1" + } + }, "tsconfig-paths": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", diff --git a/package.json b/package.json index 057e4022..991b49a2 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "axios": "^1.3.4" }, "devDependencies": { + "@tsconfig/recommended": "^1.0.2", + "@types/jest": "^29.5.0", "@typescript-eslint/eslint-plugin": "^5.56.0", "eslint": "^8.36.0", "eslint-config-airbnb-base": "^15.0.0", @@ -24,6 +26,7 @@ "eslint-plugin-n": "^15.6.1", "eslint-plugin-promise": "^6.1.1", "jest": "^29.5.0", + "ts-jest": "^29.1.0", "typescript": "^5.0.2" } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..e776bf13 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "@tsconfig/recommended/tsconfig.json", +} From 7a47e2aeca72b28807be83cb9f3a411fe2b2a334 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Wed, 5 Apr 2023 05:58:24 -0700 Subject: [PATCH 4/4] Formatting --- .editorconfig | 2 +- index.d.ts | 6 +- index.test.ts | 261 +++++++++++++++++++++++++++---------------------- jest.config.js | 1 + tsconfig.json | 2 +- 5 files changed, 150 insertions(+), 122 deletions(-) diff --git a/.editorconfig b/.editorconfig index a5420933..542ba55a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,7 +10,7 @@ end_of_line = lf # editorconfig-tools is unable to ignore longs strings or urls max_line_length = off -[*.js] +[*.{js,ts}] quote_type = single [CHANGELOG.md] diff --git a/index.d.ts b/index.d.ts index 6b6cae83..9b3dbee0 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,5 +1,5 @@ type Identifier = `${string}/${string}:${string}`; -type WebhookEventType = "start" | "output" | "logs" | "completed"; +type WebhookEventType = 'start' | 'output' | 'logs' | 'completed'; interface Page { previous?: string; @@ -7,7 +7,7 @@ interface Page { results: T[]; } -declare module "replicate" { +declare module 'replicate' { export interface Collection { id: string; name: string; @@ -65,7 +65,7 @@ declare module "replicate" { } ): Promise; request(route: string, parameters: any): Promise; - paginate(endpoint: () => Promise>): AsyncGenerator<[ T ]>; + paginate(endpoint: () => Promise>): AsyncGenerator<[T]>; wait( prediction: Prediction, options: { diff --git a/index.test.ts b/index.test.ts index 730d8da8..70e644f8 100644 --- a/index.test.ts +++ b/index.test.ts @@ -1,6 +1,6 @@ import { expect, jest, test } from '@jest/globals'; -import Replicate, { Prediction } from "replicate"; +import Replicate, { Prediction } from 'replicate'; import axios from 'axios'; @@ -9,7 +9,7 @@ describe('Replicate client', () => { beforeEach(() => { client = new Replicate({ auth: 'test-token' }); - client[ 'instance' ] = jest.fn(); + client['instance'] = jest.fn(); }); describe('constructor', () => { @@ -18,28 +18,35 @@ describe('Replicate client', () => { }); test('Sets custom baseUrl', () => { - const clientWithCustomBaseUrl = new Replicate({ baseUrl: 'https://example.com/', auth: 'test-token' }); + const clientWithCustomBaseUrl = new Replicate({ + baseUrl: 'https://example.com/', + auth: 'test-token', + }); expect(clientWithCustomBaseUrl.baseUrl).toBe('https://example.com/'); }); test('Sets custom userAgent', () => { - const clientWithCustomUserAgent = new Replicate({ userAgent: 'my-app/1.2.3', auth: 'test-token' }); + const clientWithCustomUserAgent = new Replicate({ + userAgent: 'my-app/1.2.3', + auth: 'test-token', + }); expect(clientWithCustomUserAgent.userAgent).toBe('my-app/1.2.3'); }); }); describe('collections.get', () => { test('Calls the correct API route', async () => { - client[ 'instance' ].mockResolvedValueOnce({ + client['instance'].mockResolvedValueOnce({ data: { - "name": "Super resolution", - "slug": "super-resolution", - "description": "Upscaling models that create high-quality images from low-quality images.", - "models": [] - } + name: 'Super resolution', + slug: 'super-resolution', + description: + 'Upscaling models that create high-quality images from low-quality images.', + models: [], + }, }); const collection = await client.collections.get('super-resolution'); - expect(client[ 'instance' ]).toHaveBeenCalledWith( + expect(client['instance']).toHaveBeenCalledWith( '/collections/super-resolution', { method: 'GET', @@ -53,26 +60,29 @@ describe('Replicate client', () => { describe('models.get', () => { test('Calls the correct API route', async () => { - client[ 'instance' ].mockResolvedValueOnce({ + client['instance'].mockResolvedValueOnce({ data: { - "url": "https://replicate.com/replicate/hello-world", - "owner": "replicate", - "name": "hello-world", - "description": "A tiny model that says hello", - "visibility": "public", - "github_url": "https://github.com/replicate/cog-examples", - "paper_url": null, - "license_url": null, - "run_count": 12345, - "cover_image_url": "", - "default_example": {}, - "latest_version": {} - } + url: 'https://replicate.com/replicate/hello-world', + owner: 'replicate', + name: 'hello-world', + description: 'A tiny model that says hello', + visibility: 'public', + github_url: 'https://github.com/replicate/cog-examples', + paper_url: null, + license_url: null, + run_count: 12345, + cover_image_url: '', + default_example: {}, + latest_version: {}, + }, }); await client.models.get('replicate', 'hello-world'); - expect(client[ 'instance' ]).toHaveBeenCalledWith('/models/replicate/hello-world', { - method: 'GET', - }); + expect(client['instance']).toHaveBeenCalledWith( + '/models/replicate/hello-world', + { + method: 'GET', + } + ); }); // Add more tests for error handling, edge cases, etc. @@ -80,40 +90,42 @@ describe('Replicate client', () => { describe('predictions.create', () => { test('Calls the correct API route with the correct payload', async () => { - client[ 'instance' ].mockResolvedValueOnce({ + client['instance'].mockResolvedValueOnce({ data: { - "id": "ufawqhfynnddngldkgtslldrkq", - "version": "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", - "urls": { - "get": "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", - "cancel": "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel" + id: 'ufawqhfynnddngldkgtslldrkq', + version: + '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + urls: { + get: 'https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq', + cancel: + 'https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel', }, - "created_at": "2022-04-26T22:13:06.224088Z", - "started_at": null, - "completed_at": null, - "status": "starting", - "input": { - "text": "Alice" + created_at: '2022-04-26T22:13:06.224088Z', + started_at: null, + completed_at: null, + status: 'starting', + input: { + text: 'Alice', }, - "output": null, - "error": null, - "logs": null, - "metrics": {} - } + output: null, + error: null, + logs: null, + metrics: {}, + }, }); const prediction = await client.predictions.create({ version: '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', input: { - text: 'Alice' + text: 'Alice', }, webhook: 'http://test.host/webhook', - webhook_events_filter: [ 'output', 'completed' ], + webhook_events_filter: ['output', 'completed'], }); expect(prediction.id).toBe('ufawqhfynnddngldkgtslldrkq'); - expect(client[ 'instance' ]).toHaveBeenCalledWith('/predictions', { + expect(client['instance']).toHaveBeenCalledWith('/predictions', { method: 'POST', data: { version: @@ -122,7 +134,7 @@ describe('Replicate client', () => { text: 'Alice', }, webhook: 'http://test.host/webhook', - webhook_events_filter: [ 'output', 'completed' ], + webhook_events_filter: ['output', 'completed'], }, }); }); @@ -132,39 +144,46 @@ describe('Replicate client', () => { describe('predictions.get', () => { test('Calls the correct API route with the correct payload', async () => { - client[ 'instance' ].mockResolvedValueOnce({ + client['instance'].mockResolvedValueOnce({ data: { - "id": "rrr4z55ocneqzikepnug6xezpe", - "version": "be04660a5b93ef2aff61e3668dedb4cbeb14941e62a3fd5998364a32d613e35e", - "urls": { - "get": "https://api.replicate.com/v1/predictions/rrr4z55ocneqzikepnug6xezpe", - "cancel": "https://api.replicate.com/v1/predictions/rrr4z55ocneqzikepnug6xezpe/cancel" + id: 'rrr4z55ocneqzikepnug6xezpe', + version: + 'be04660a5b93ef2aff61e3668dedb4cbeb14941e62a3fd5998364a32d613e35e', + urls: { + get: 'https://api.replicate.com/v1/predictions/rrr4z55ocneqzikepnug6xezpe', + cancel: + 'https://api.replicate.com/v1/predictions/rrr4z55ocneqzikepnug6xezpe/cancel', }, - "created_at": "2022-09-13T22:54:18.578761Z", - "started_at": "2022-09-13T22:54:19.438525Z", - "completed_at": "2022-09-13T22:54:23.236610Z", - "source": "api", - "status": "succeeded", - "input": { - "prompt": "oak tree with boletus growing on its branches" + created_at: '2022-09-13T22:54:18.578761Z', + started_at: '2022-09-13T22:54:19.438525Z', + completed_at: '2022-09-13T22:54:23.236610Z', + source: 'api', + status: 'succeeded', + input: { + prompt: 'oak tree with boletus growing on its branches', }, - "output": [ - "https://replicate.com/api/models/stability-ai/stable-diffusion/files/9c3b6fe4-2d37-4571-a17a-83951b1cb120/out-0.png" + output: [ + 'https://replicate.com/api/models/stability-ai/stable-diffusion/files/9c3b6fe4-2d37-4571-a17a-83951b1cb120/out-0.png', ], - "error": null, - "logs": "Using seed: 36941...", - "metrics": { - "predict_time": 4.484541 - } - } + error: null, + logs: 'Using seed: 36941...', + metrics: { + predict_time: 4.484541, + }, + }, }); - const prediction = await client.predictions.get('rrr4z55ocneqzikepnug6xezpe'); + const prediction = await client.predictions.get( + 'rrr4z55ocneqzikepnug6xezpe' + ); expect(prediction.id).toBe('rrr4z55ocneqzikepnug6xezpe'); - expect(client[ 'instance' ]).toHaveBeenCalledWith('/predictions/rrr4z55ocneqzikepnug6xezpe', { - method: 'GET', - }); + expect(client['instance']).toHaveBeenCalledWith( + '/predictions/rrr4z55ocneqzikepnug6xezpe', + { + method: 'GET', + } + ); }); // Add more tests for error handling, edge cases, etc. @@ -172,53 +191,51 @@ describe('Replicate client', () => { describe('predictions.list', () => { test('Calls the correct API route with the correct payload', async () => { - client[ 'instance' ].mockResolvedValueOnce({ + client['instance'].mockResolvedValueOnce({ data: { - next: "https://api.replicate.com/v1/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw", + next: 'https://api.replicate.com/v1/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw', previous: null, results: [ { - "id": "jpzd7hm5gfcapbfyt4mqytarku", - "version": "b21cbe271e65c1718f2999b038c18b45e21e4fba961181fbfae9342fc53b9e05", - "urls": { - "get": "https://api.replicate.com/v1/predictions/jpzd7hm5gfcapbfyt4mqytarku", - "cancel": "https://api.replicate.com/v1/predictions/jpzd7hm5gfcapbfyt4mqytarku/cancel" + id: 'jpzd7hm5gfcapbfyt4mqytarku', + version: + 'b21cbe271e65c1718f2999b038c18b45e21e4fba961181fbfae9342fc53b9e05', + urls: { + get: 'https://api.replicate.com/v1/predictions/jpzd7hm5gfcapbfyt4mqytarku', + cancel: + 'https://api.replicate.com/v1/predictions/jpzd7hm5gfcapbfyt4mqytarku/cancel', }, - "created_at": "2022-04-26T20:00:40.658234Z", - "started_at": "2022-04-26T20:00:84.583803Z", - "completed_at": "2022-04-26T20:02:27.648305Z", - "source": "web", - "status": "succeeded" + created_at: '2022-04-26T20:00:40.658234Z', + started_at: '2022-04-26T20:00:84.583803Z', + completed_at: '2022-04-26T20:02:27.648305Z', + source: 'web', + status: 'succeeded', }, ], - } + }, }); const predictions = await client.predictions.list(); expect(predictions.results.length).toBe(1); - expect(predictions.results[ 0 ].id).toBe('jpzd7hm5gfcapbfyt4mqytarku'); + expect(predictions.results[0].id).toBe('jpzd7hm5gfcapbfyt4mqytarku'); - expect(client[ 'instance' ]).toHaveBeenCalledWith('/predictions', { + expect(client['instance']).toHaveBeenCalledWith('/predictions', { method: 'GET', }); }); test('Paginates results', async () => { - client[ 'instance' ].mockResolvedValueOnce({ + client['instance'].mockResolvedValueOnce({ data: { - results: [ - { id: 'ufawqhfynnddngldkgtslldrkq' }, - ], + results: [{ id: 'ufawqhfynnddngldkgtslldrkq' }], next: 'https://api.replicate.com/v1/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw', - } + }, }); - client[ 'instance' ].mockResolvedValueOnce({ + client['instance'].mockResolvedValueOnce({ data: { - results: [ - { id: 'rrr4z55ocneqzikepnug6xezpe' } - ], + results: [{ id: 'rrr4z55ocneqzikepnug6xezpe' }], next: null, - } + }, }); const results: Prediction[] = []; @@ -228,15 +245,18 @@ describe('Replicate client', () => { } expect(results).toEqual([ { id: 'ufawqhfynnddngldkgtslldrkq' }, - { id: 'rrr4z55ocneqzikepnug6xezpe' } + { id: 'rrr4z55ocneqzikepnug6xezpe' }, ]); - expect(client[ 'instance' ]).toHaveBeenCalledWith('/predictions', { - method: 'GET', - }); - expect(client[ 'instance' ]).toHaveBeenCalledWith('https://api.replicate.com/v1/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw', { + expect(client['instance']).toHaveBeenCalledWith('/predictions', { method: 'GET', }); + expect(client['instance']).toHaveBeenCalledWith( + 'https://api.replicate.com/v1/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw', + { + method: 'GET', + } + ); }); // Add more tests for error handling, edge cases, etc. @@ -244,34 +264,41 @@ describe('Replicate client', () => { describe('run', () => { test('Calls the correct API routes', async () => { - client[ 'instance' ].mockResolvedValueOnce({ + client['instance'].mockResolvedValueOnce({ data: { id: 'ufawqhfynnddngldkgtslldrkq', status: 'processing', - } + }, }); - client[ 'instance' ].mockResolvedValueOnce({ + client['instance'].mockResolvedValueOnce({ data: { id: 'ufawqhfynnddngldkgtslldrkq', status: 'succeeded', output: 'foobar', - } - }); - const output = await client.run('owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', { - input: { text: 'Hello, world!' }, + }, }); - expect(client[ 'instance' ]).toHaveBeenCalledWith('/predictions', { + const output = await client.run( + 'owner/model:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + { + input: { text: 'Hello, world!' }, + } + ); + expect(client['instance']).toHaveBeenCalledWith('/predictions', { method: 'POST', data: { - version: '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + version: + '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', input: { text: 'Hello, world!', }, - } - }); - expect(client[ 'instance' ]).toHaveBeenCalledWith('/predictions/ufawqhfynnddngldkgtslldrkq', { - method: 'GET', + }, }); + expect(client['instance']).toHaveBeenCalledWith( + '/predictions/ufawqhfynnddngldkgtslldrkq', + { + method: 'GET', + } + ); expect(output).toBe('foobar'); }); }); diff --git a/jest.config.js b/jest.config.js index 3745fc22..9ea9e367 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,3 +1,4 @@ +// eslint-disable-next-line jsdoc/valid-types /** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { preset: 'ts-jest', diff --git a/tsconfig.json b/tsconfig.json index e776bf13..97cb33b3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,3 +1,3 @@ { - "extends": "@tsconfig/recommended/tsconfig.json", + "extends": "@tsconfig/recommended/tsconfig.json" }