From 4ea8f7841def3da3109dcb578132b5179c86fd9b Mon Sep 17 00:00:00 2001 From: Mark Pitman Date: Tue, 18 Aug 2026 15:10:56 -0700 Subject: [PATCH] Add TypeScript declarations for the existing public surface Declare the client, the fourteen request classes, and their fluent builders in a single root gettyimages-api.d.ts, and point the "types" field at it. This covers only what the SDK supports today: no response models, no new operations, and no literal-union enums for values the SDK never enumerates. No runtime code changes, so JavaScript consumers are unaffected. The risk that does exist is a declaration too strict for a call the SDK already accepts, which would break TypeScript consumers at compile time. Three cases drove the signatures: - Builders named in the singular, such as withCollectionCode, are called with arrays throughout the tests, so they accept a value or an array. - Identifier builders, such as withKeywordId, are called with numbers as well as strings. - execute() returns Promise, because Promise would force every existing consumer to cast before reading a response field. types-test.ts exercises every builder in the argument forms the AVA tests and the README use. It runs under "npm run test:types" and in CI, so an over-strict signature fails the build rather than reaching consumers. Closes #56 --- .eslintignore | 3 + .github/workflows/lint.yml | 10 + .npmignore | 4 +- README.md | 15 + gettyimages-api.d.ts | 572 ++++++++++++++++++++++++++++++++++++ package-lock.json | 579 ++++++++++++++++++++++++++++++++++++- package.json | 6 +- tsconfig.types.json | 18 ++ types-test.ts | 288 ++++++++++++++++++ 9 files changed, 1492 insertions(+), 3 deletions(-) create mode 100644 gettyimages-api.d.ts create mode 100644 tsconfig.types.json create mode 100644 types-test.ts diff --git a/.eslintignore b/.eslintignore index 96212a3..95dd096 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,4 @@ **/*{.,-}min.js +types-test.ts +tsconfig.types.json +*.d.ts diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c04dba5..41774ee 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,3 +13,13 @@ jobs: node-version: "16" - run: npm install - run: npm run lint + + types: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "22" + - run: npm install + - run: npm run test:types diff --git a/.npmignore b/.npmignore index f0c9cfc..3208588 100644 --- a/.npmignore +++ b/.npmignore @@ -20,4 +20,6 @@ Makefile tests .travis* *.tgz -*.md \ No newline at end of file +*.md +types-test.ts +tsconfig.types.json diff --git a/README.md b/README.md index 330d555..4e950e7 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,21 @@ In order to manage the lifetime of your access token and minimize the number of As calls are asynchronous, use `await` when executing API calls, otherwise the calls will not use the cached token. +### TypeScript + +Type declarations ship with the package. There is no separate `@types` package to install. + +```typescript +import api from "gettyimages-api"; + +const client = new api({ apiKey: "your_api_key", apiSecret: "your_api_secret" }); +const response = await client.searchimages().withPhrase("beach").withPageSize(1).execute(); +``` + +Builders named in the singular, such as `withCollectionCode`, accept either a single value or +an array of values. Responses are typed as `any`, because the API returns different fields +depending on the endpoint, the requested response fields, and your products. + ## Examples Ensure that `"type": "module"` is set in your `package.json` to enable ES Modules and async/await support. diff --git a/gettyimages-api.d.ts b/gettyimages-api.d.ts new file mode 100644 index 0000000..7c6e8d6 --- /dev/null +++ b/gettyimages-api.d.ts @@ -0,0 +1,572 @@ +// Type definitions for gettyimages-api +// Project: https://github.com/gettyimages/gettyimages-api_nodejs + +/** + * A value accepted by a builder that collects a list of parameters. + * + * The builders are named in the singular, however each one accepts either a + * single value or an array of values. Both forms are supported. + */ +type ListValue = string | string[]; + +/** + * A value accepted by a builder that collects a list of identifiers. + * + * Identifiers are accepted as strings or numbers. + */ +type IdListValue = string | number | Array; + +/** The credentials used to construct a client. */ +interface Credentials { + /** Your API key. Required. */ + apiKey: string; + /** Your API secret. Required for all calls that need an access token. */ + apiSecret?: string; + /** The username, when using the resource owner password grant. */ + username?: string; + /** The password, when using the resource owner password grant. */ + password?: string; + /** A refresh token, when refreshing an existing access token. */ + refresh_token?: string; + /** A cached access token, to reuse between instances of the client. */ + token?: AccessToken; +} + +/** + * An access token. + * + * Read a token from the `token` property of the client and pass it back in as + * `Credentials.token` to reuse it between instances of the client. + */ +interface AccessToken { + access_token: string; + token_type: string; + /** The time at which the token expires. */ + expiration: Date; + /** The lifetime of the token in seconds, as returned by the auth service. */ + expires_in?: number | string; + [key: string]: unknown; +} + +/** + * The error thrown when a request is missing a required value. + * + * This type is not an `Error`. It is thrown synchronously from `execute()` and + * from the client constructor. + */ +interface SdkException { + message: string; + toString(): string; +} + +/** The base class for every request. */ +declare class GettyApiRequest { + /** The headers sent with the request. */ + headers: Record; + /** The query parameters sent with the request. */ + params: Record; + /** The host name the request is sent to. */ + hostName: string; + /** Adds a query parameter to the request. */ + addParameter(key: string, value: string | number | boolean | Array): void; + /** Adds a query parameter that the SDK does not otherwise support. */ + withCustomParameter(key: string, value: string | number | boolean | Array): this; + /** Adds a header that the SDK does not otherwise support. */ + withCustomHeader(key: string, value: string): this; +} + +/** Gets metadata for one or more images. */ +declare class Images extends GettyApiRequest { + /** The image ids to get. */ + ids: Array; + /** The response fields to return. */ + fields: string[]; + /** Adds an image id to the request. */ + withId(id: string | number): this; + /** Adds one or more image ids to the request. */ + withIds(ids: IdListValue): this; + /** Adds one or more response fields to the request. */ + withResponseField(field: ListValue): this; + /** Sends the request. Throws when no image id is set. */ + execute(): Promise; +} + +/** Gets metadata for one or more videos. */ +declare class Videos extends GettyApiRequest { + /** The video ids to get. */ + ids: Array; + /** The response fields to return. */ + fields: string[]; + /** Adds a video id to the request. */ + withId(id: string | number): this; + /** Adds one or more video ids to the request. */ + withIds(ids: IdListValue): this; + /** Adds one or more response fields to the request. */ + withResponseField(field: ListValue): this; + /** Sends the request. Throws when no video id is set. */ + execute(): Promise; +} + +/** Gets metadata for one or more events. */ +declare class Events extends GettyApiRequest { + /** The event ids to get. */ + ids: Array; + /** The response fields to return. */ + fields: string[]; + /** Adds an event id to the request. */ + withId(id: string | number): this; + /** Adds one or more event ids to the request. */ + withIds(ids: IdListValue): this; + /** Adds one or more response fields to the request. */ + withResponseField(field: ListValue): this; + /** Sends the request. Throws when no event id is set. */ + execute(): Promise; +} + +/** Gets the collections available to you. */ +declare class Collections extends GettyApiRequest { + /** Sends the request. */ + execute(): Promise; +} + +/** Gets the list of countries. */ +declare class Countries extends GettyApiRequest { + /** Sends the request. */ + execute(): Promise; +} + +/** Downloads an image. */ +declare class ImagesDownloads extends GettyApiRequest { + /** The id of the image to download. */ + id: string | number; + /** The file type to download. */ + fileType: string; + /** The height to download. */ + height: string | number; + /** The id of the product to download against. */ + productId: string | number; + /** The type of the product to download against. */ + productType: string; + /** Sets the file type to download. */ + withFileType(fileType: string): this; + /** Sets the id of the image to download. */ + withId(id: string | number): this; + /** Sets the height to download. */ + withHeight(height: string | number): this; + /** Sets the id of the product to download against. */ + withProductId(productId: string | number): this; + /** Sets the type of the product to download against. */ + withProductType(productType: string): this; + /** Sends the request. Throws when no image id is set. */ + execute(): Promise; +} + +/** Downloads a video. */ +declare class VideoDownloads extends GettyApiRequest { + /** The id of the video to download. */ + id: string | number; + /** The id of the product to download against. */ + productId: string | number; + /** The size to download. */ + size: string; + /** Sets the id of the video to download. */ + withId(id: string | number): this; + /** Sets the id of the product to download against. */ + withProductId(productId: string | number): this; + /** Sets the size to download. */ + withSize(size: string): this; + /** Sends the request. Throws when no video id is set. */ + execute(): Promise; +} + +/** Calls any endpoint of the API. */ +declare class CustomRequest extends GettyApiRequest { + /** The HTTP method to use. */ + method: string | null; + /** The route to call, without the `/v3/` prefix. */ + route: string | null; + /** The request body. */ + body: any; + /** The query parameters to send. */ + queryParameters: Record; + /** Sets the HTTP method to use. */ + withMethod(method: "get" | "post" | "put" | "delete" | string): this; + /** Sets the route to call, for example `search/images`. */ + withRoute(route: string): this; + /** Sets the query parameters to send. */ + withQueryParameters(queryParameters: Record): this; + /** Sets the request body. */ + withBody(body: any): this; + /** Sends the request. Throws when no route or no valid method is set. */ + execute(): Promise; +} + +/** Searches for images across the creative and editorial catalogs. */ +declare class SearchImages extends GettyApiRequest { + ageOfPeople: string[]; + artists: string[]; + collectionCodes: string[]; + collectionsFilterType: string | null; + color: string | null; + compositions: string[]; + embedContentOnly: boolean | null; + ethnicities: string[]; + eventIds: Array; + excludeNudity: boolean | null; + fields: string[]; + fileTypes: string[]; + graphicalStyles: string[]; + keywordIds: Array; + minimumSize: string | null; + numberOfPeople: string[]; + orientations: string[]; + page: number; + pageSize: number; + phrase: string | null; + prestigeContentOnly: boolean | null; + productTypes: string[]; + sortOrder: string | null; + specificPeople: string[]; + /** Sets the `Accept-Language` header. */ + withAcceptLanguage(language: string): this; + withAgeOfPeople(ageOfPeople: ListValue): this; + withArtist(artists: ListValue): this; + withCollectionCode(collectionCode: ListValue): this; + withCollectionsFilterType(collectionsFilterType: string): this; + withColor(color: string): this; + withComposition(composition: ListValue): this; + withEmbedContentOnly(embedContentOnly: boolean): this; + withEthnicity(ethnicity: ListValue): this; + withEventId(eventId: IdListValue): this; + withExcludeNudity(excludeNudity: boolean): this; + withResponseField(field: ListValue): this; + withFileType(fileType: ListValue): this; + withGraphicalStyle(graphicalStyle: ListValue): this; + withKeywordId(keywordId: IdListValue): this; + withMinimumSize(minimumSize: string): this; + withNumberOfPeople(numberOfPeople: ListValue): this; + withOrientation(orientation: ListValue): this; + withPage(page: number): this; + withPageSize(pageSize: number): this; + withPhrase(phrase: string): this; + withPrestigeContentOnly(prestigeContentOnly: boolean): this; + withProductType(productType: ListValue): this; + withSortOrder(sortOrder: string): this; + withSpecificPeople(specificPeople: ListValue): this; + /** Sends the request. */ + execute(): Promise; +} + +/** Searches for images in the creative catalog. */ +declare class SearchImagesCreative extends GettyApiRequest { + ageOfPeople: string[]; + artists: string[]; + collectionCodes: string[]; + collectionsFilterType: string | null; + color: string | null; + compositions: string[]; + embedContentOnly: boolean | null; + ethnicities: string[]; + excludeEditorialUseOnly: boolean | null; + excludeNudity: boolean | null; + fields: string[]; + fileTypes: string[]; + graphicalStyles: string[]; + keywordIds: Array; + minimumSize: string | null; + numberOfPeople: string[]; + orientations: string[]; + page: number; + pageSize: number; + phrase: string | null; + prestigeContentOnly: boolean | null; + productTypes: string[]; + safeSearch: boolean | null; + sortOrder: string | null; + /** Sets the `Accept-Language` header. */ + withAcceptLanguage(language: string): this; + withAgeOfPeople(ageOfPeople: ListValue): this; + withArtist(artist: ListValue): this; + withCollectionCode(collectionCode: ListValue): this; + withCollectionsFilterType(collectionsFilterType: string): this; + withColor(color: string): this; + withComposition(composition: ListValue): this; + withEmbedContentOnly(embedContentOnly: boolean): this; + withEthnicity(ethnicity: ListValue): this; + withExcludeEditorialUseOnly(excludeEditorialUseOnly: boolean): this; + withExcludeNudity(excludeNudity: boolean): this; + withResponseField(field: ListValue): this; + withFileType(fileType: ListValue): this; + withGraphicalStyle(graphicalStyle: ListValue): this; + withKeywordId(keywordId: IdListValue): this; + withMinimumSize(minimumSize: string): this; + withNumberOfPeople(numberOfPeople: ListValue): this; + withOrientation(orientation: ListValue): this; + withPage(page: number): this; + withPageSize(pageSize: number): this; + withPhrase(phrase: string): this; + withPrestigeContentOnly(prestigeContentOnly: boolean): this; + withProductType(productType: ListValue): this; + withSafeSearch(safeSearch: boolean): this; + withSortOrder(sortOrder: string): this; + /** Sends the request. */ + execute(): Promise; +} + +/** Searches for images in the editorial catalog. */ +declare class SearchImagesEditorial extends GettyApiRequest { + ageOfPeople: string[]; + artists: string[]; + collectionCodes: string[]; + collectionsFilterType: string | null; + /** Not initialised by the constructor. Set it with `withColor`. */ + color?: string; + compositions: string[]; + editorialSegments: string[]; + embedContentOnly: boolean | null; + endDate: string | null; + entityUris: Array; + ethnicities: string[]; + eventIds: Array; + excludeNudity: boolean | null; + fields: string[]; + fileTypes: string[]; + graphicalStyles: string[]; + keywordIds: Array; + minimumQualityRank: number; + minimumSize: string | null; + numberOfPeople: string[]; + orientations: string[]; + page: number; + pageSize: number; + phrase: string | null; + productTypes: string[]; + sortOrder: string | null; + specificPeople: string[]; + startDate: string | null; + /** Sets the `Accept-Language` header. */ + withAcceptLanguage(language: string): this; + withAgeOfPeople(ageOfPeople: ListValue): this; + withArtist(artist: ListValue): this; + withCollectionCode(collectionCode: ListValue): this; + withCollectionsFilterType(collectionsFilterType: string): this; + withColor(color: string): this; + withComposition(composition: ListValue): this; + withEditorialSegments(editorialSegments: ListValue): this; + withEmbedContentOnly(embedContentOnly: boolean): this; + /** Sets the latest date to search, for example `2015-04-01`. */ + withEndDate(endDate: string): this; + withEntityUris(entityUris: IdListValue): this; + withEthnicity(ethnicity: ListValue): this; + withEventId(eventId: IdListValue): this; + withExcludeNudity(excludeNudity: boolean): this; + withResponseField(field: ListValue): this; + withFileType(fileType: ListValue): this; + withGraphicalStyle(graphicalStyle: ListValue): this; + withKeywordId(keywordId: IdListValue): this; + withMinimumQualityRank(minimumQualityRank: number): this; + withMinimumSize(minimumSize: string): this; + withNumberOfPeople(numberOfPeople: ListValue): this; + withOrientation(orientation: ListValue): this; + withPage(page: number): this; + withPageSize(pageSize: number): this; + withPhrase(phrase: string): this; + withProductType(productType: ListValue): this; + withSortOrder(sortOrder: string): this; + withSpecificPeople(specificPeople: ListValue): this; + /** Sets the earliest date to search, for example `2015-04-01`. */ + withStartDate(startDate: string): this; + /** Sends the request. */ + execute(): Promise; +} + +/** Searches for videos across the creative and editorial catalogs. */ +declare class SearchVideos extends GettyApiRequest { + ageOfPeople: string[]; + collectionCodes: string[]; + collectionsFilterType: string | null; + editorialVideoTypes: string[]; + excludeNudity: boolean | null; + fields: string[]; + formatAvailable: string | null; + frameRates: string[]; + keywordIds: Array; + licenseModels: string[]; + page: number; + pageSize: number; + phrase: string | null; + productTypes: string[]; + sortOrder: string | null; + specificPeople: string[]; + /** Sets the `Accept-Language` header. */ + withAcceptLanguage(language: string): this; + withAgeOfPeople(ageOfPeople: ListValue): this; + withCollectionCode(collectionCode: ListValue): this; + withCollectionsFilterType(collectionsFilterType: string): this; + withEditorialVideoType(editorialVideoType: ListValue): this; + withExcludeNudity(excludeNudity: boolean): this; + withResponseField(field: ListValue): this; + withFormatAvailable(formatAvailable: string): this; + withFrameRate(frameRate: ListValue): this; + withKeywordId(keywordId: IdListValue): this; + withLicenseModel(licenseModel: ListValue): this; + withPage(page: number): this; + withPageSize(pageSize: number): this; + withPhrase(phrase: string): this; + withProductType(productType: ListValue): this; + withSortOrder(sortOrder: string): this; + withSpecificPeople(specificPeople: ListValue): this; + /** Sends the request. */ + execute(): Promise; +} + +/** Searches for videos in the creative catalog. */ +declare class SearchVideosCreative extends GettyApiRequest { + ageOfPeople: string[]; + collectionCodes: string[]; + collectionsFilterType: string | null; + excludeEditorialUseOnly: boolean | null; + excludeNudity: boolean | null; + fields: string[]; + formatAvailable: string | null; + frameRates: string[]; + keywordIds: Array; + licenseModels: string[]; + minClipLength: number; + orientations: string[]; + page: number; + pageSize: number; + phrase: string | null; + productTypes: string[]; + safeSearch: boolean | null; + sortOrder: string | null; + /** Sets the `Accept-Language` header. */ + withAcceptLanguage(language: string): this; + withAgeOfPeople(ageOfPeople: ListValue): this; + withCollectionCode(collectionCode: ListValue): this; + withCollectionsFilterType(collectionsFilterType: string): this; + withExcludeEditorialUseOnly(excludeEditorialUseOnly: boolean): this; + withExcludeNudity(excludeNudity: boolean): this; + withResponseField(field: ListValue): this; + withFormatAvailable(formatAvailable: string): this; + withFrameRate(frameRate: ListValue): this; + withKeywordId(keywordId: IdListValue): this; + withLicenseModel(licenseModel: ListValue): this; + /** Sets the shortest clip length to search, in seconds. */ + withMinClipLength(minLengthInSeconds: number): this; + withOrientation(orientation: ListValue): this; + withPage(page: number): this; + withPageSize(pageSize: number): this; + withPhrase(phrase: string): this; + withProductType(productType: ListValue): this; + withSafeSearch(safeSearch: boolean): this; + withSortOrder(sortOrder: string): this; + /** Sends the request. */ + execute(): Promise; +} + +/** Searches for videos in the editorial catalog. */ +declare class SearchVideosEditorial extends GettyApiRequest { + ageOfPeople: string[]; + collectionCodes: string[]; + collectionsFilterType: string | null; + editorialVideoTypes: string[]; + endDate: string | null; + entityUris: Array; + excludeNudity: boolean | null; + fields: string[]; + formatAvailable: string | null; + frameRates: string[]; + keywordIds: Array; + orientations: string[]; + page: number; + pageSize: number; + phrase: string | null; + productTypes: string[]; + sortOrder: string | null; + specificPeople: string[]; + startDate: string | null; + /** Sets the `Accept-Language` header. */ + withAcceptLanguage(language: string): this; + withAgeOfPeople(ageOfPeople: ListValue): this; + withCollectionCode(collectionCode: ListValue): this; + withCollectionsFilterType(collectionsFilterType: string): this; + withEditorialVideoType(editorialVideoType: ListValue): this; + /** Sets the latest date to search, for example `2015-04-01`. */ + withEndDate(endDate: string): this; + withEntityUris(entityUris: IdListValue): this; + withExcludeNudity(excludeNudity: boolean): this; + withResponseField(field: ListValue): this; + withFormatAvailable(formatAvailable: string): this; + withFrameRate(frameRate: ListValue): this; + withKeywordId(keywordId: IdListValue): this; + withOrientation(orientation: ListValue): this; + withPage(page: number): this; + withPageSize(pageSize: number): this; + withPhrase(phrase: string): this; + withProductType(productType: ListValue): this; + withSortOrder(sortOrder: string): this; + withSpecificPeople(specificPeople: ListValue): this; + /** Sets the earliest date to search, for example `2015-04-01`. */ + withStartDate(startDate: string): this; + /** Sends the request. */ + execute(): Promise; +} + +/** + * The Getty Images API client. + * + * Create the client once and reuse it for all calls, so that the access token + * is cached and the number of calls to auth is kept to a minimum. + */ +declare class GettyImagesApi { + /** + * @param credentials Your API key and secret. Throws when `apiKey` is missing. + * @param hostName Defaults to `api.gettyimages.com`. + * @param authHostName Defaults to `authentication.gettyimages.com`. + */ + constructor(credentials: Credentials, hostName?: string, authHostName?: string); + /** The credentials the client was created with. */ + credentials: Credentials; + /** The host name requests are sent to. */ + hostName: string; + /** + * The current access token. + * + * Read this after a call to cache the token, as it is refreshed when it + * expires. + */ + readonly token: AccessToken; + /** Gets an access token, refreshing it when a refresh token was supplied. */ + getAccessToken(): Promise; + /** Creates a request for image metadata. */ + images(): Images; + /** Creates a request for video metadata. */ + videos(): Videos; + /** Creates a video search across both catalogs. */ + searchvideos(): SearchVideos; + /** Creates a creative video search. */ + searchvideoscreative(): SearchVideosCreative; + /** Creates an editorial video search. */ + searchvideoseditorial(): SearchVideosEditorial; + /** Creates an image search across both catalogs. */ + searchimages(): SearchImages; + /** Creates a creative image search. */ + searchimagescreative(): SearchImagesCreative; + /** Creates an editorial image search. */ + searchimageseditorial(): SearchImagesEditorial; + /** Creates a request for the collections available to you. */ + collections(): Collections; + /** Creates a request for the list of countries. */ + countries(): Countries; + /** Creates a request for event metadata. */ + events(): Events; + /** Creates a video download request. */ + downloadsvideos(): VideoDownloads; + /** Creates an image download request. */ + downloadsimages(): ImagesDownloads; + /** Creates a request against any endpoint of the API. */ + customrequest(): CustomRequest; +} + +export = GettyImagesApi; diff --git a/package-lock.json b/package-lock.json index de92cae..8791b45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,15 @@ "version": "0.0.0", "license": "MIT", "devDependencies": { + "@types/node": "^26.2.0", "ava": "^4.3.3", "eslint": "^7.32.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.2.0", "nock": "^12.0.1", - "nyc": "^15.0.0" + "nyc": "^15.0.0", + "typescript": "^7.0.2" }, "engines": { "node": ">=14.20.1" @@ -585,6 +587,356 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "node_modules/@types/node": { + "version": "26.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", + "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -4686,6 +5038,41 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typescript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, "node_modules/unbox-primitive": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", @@ -4701,6 +5088,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT" + }, "node_modules/uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -5311,6 +5705,155 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/node": { + "version": "26.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", + "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", + "dev": true, + "requires": { + "undici-types": "~8.3.0" + } + }, + "@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "dev": true, + "optional": true + }, + "@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "dev": true, + "optional": true + }, + "@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "dev": true, + "optional": true + }, + "@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "dev": true, + "optional": true + }, + "@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "dev": true, + "optional": true + }, + "@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "dev": true, + "optional": true + }, + "@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "dev": true, + "optional": true + }, + "@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "dev": true, + "optional": true + }, + "@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "dev": true, + "optional": true + }, + "@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "dev": true, + "optional": true + }, + "@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "dev": true, + "optional": true + }, + "@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "dev": true, + "optional": true + }, + "@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "dev": true, + "optional": true + }, + "@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "dev": true, + "optional": true + }, + "@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "dev": true, + "optional": true + }, + "@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "dev": true, + "optional": true + }, + "@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "dev": true, + "optional": true + }, + "@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "dev": true, + "optional": true + }, + "@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "dev": true, + "optional": true + }, + "@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "dev": true, + "optional": true + }, "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -8281,6 +8824,34 @@ "is-typedarray": "^1.0.0" } }, + "typescript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "dev": true, + "requires": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, "unbox-primitive": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", @@ -8293,6 +8864,12 @@ "which-boxed-primitive": "^1.0.2" } }, + "undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", diff --git a/package.json b/package.json index e78db47..88ff3f1 100644 --- a/package.json +++ b/package.json @@ -22,14 +22,17 @@ "node": ">=14.20.1" }, "main": "gettyimages-api.js", + "types": "gettyimages-api.d.ts", "devDependencies": { + "@types/node": "^26.2.0", "ava": "^4.3.3", "eslint": "^7.32.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.2.0", "nock": "^12.0.1", - "nyc": "^15.0.0" + "nyc": "^15.0.0", + "typescript": "^7.0.2" }, "overrides": { "js-yaml": "^3.15.0" @@ -40,6 +43,7 @@ }, "scripts": { "test": "./node_modules/.bin/nyc ./node_modules/.bin/ava --verbose ./tests/*.js", + "test:types": "./node_modules/.bin/tsc --noEmit -p tsconfig.types.json", "coverage": "./node_modules/.bin/nyc report --reporter=text-lcov > lcov.info", "lint": "./node_modules/.bin/eslint ." } diff --git a/tsconfig.types.json b/tsconfig.types.json new file mode 100644 index 0000000..ac1e178 --- /dev/null +++ b/tsconfig.types.json @@ -0,0 +1,18 @@ +{ + "//": "Type-checks the published declarations against types-test.ts. Not shipped.", + "compilerOptions": { + "strict": true, + "noEmit": true, + "target": "ES2017", + "module": "node16", + "moduleResolution": "node16", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": false, + "types": ["node"] + }, + "files": [ + "gettyimages-api.d.ts", + "types-test.ts" + ] +} diff --git a/types-test.ts b/types-test.ts new file mode 100644 index 0000000..472c1ca --- /dev/null +++ b/types-test.ts @@ -0,0 +1,288 @@ +/* + * Compile-time test for gettyimages-api.d.ts. + * + * This file is never run. It exists so that `tsc --noEmit` fails if a + * declaration rejects a call that the SDK supports today. Every call form below + * is taken from the AVA tests in `tests/` or from the examples in README.md. + * + * Add a case here whenever you change a signature in gettyimages-api.d.ts. + */ + +import api from "./gettyimages-api"; + +const creds = { apiKey: "key", apiSecret: "secret" }; +const client = new api(creds); + +// The constructor overloads, per the README and lib/credentials.js. +new api({ apiKey: "key" }); +new api({ apiKey: "key", apiSecret: "secret" }); +new api({ apiKey: "key", apiSecret: "secret", username: "u", password: "p" }); +new api({ apiKey: "key", apiSecret: "secret", refresh_token: "r" }); +new api(creds, "api.gettyimages.com"); +new api(creds, "api.gettyimages.com", "authentication.gettyimages.com"); + +// The `require` form must keep working alongside the default import. +import assignment = require("./gettyimages-api"); +new assignment(creds); + +// The token cache round-trip from the README: read the token off the client, +// then pass it back in when constructing the next one. +const cachedToken = client.token; +const reusedClient = new api({ apiKey: "key", apiSecret: "secret", token: cachedToken }); +const accessToken: string = cachedToken.access_token; +const tokenType: string = cachedToken.token_type; +const expiration: Date = cachedToken.expiration; + +async function auth(): Promise { + const token = await client.getAccessToken(); + if (token) { + const value: string = token.access_token; + } +} + +// Responses are untyped, so reading an arbitrary field must compile. +async function responses(): Promise { + const response = await client.searchimages().withPhrase("beach").execute(); + console.log(response.images[0].id); + console.log(response.result_count); +} + +// Metadata requests: scalar and array ids, both strings and numbers. +client.images().withId("123").execute(); +client.images().withIds("123").execute(); +client.images().withIds(123).execute(); +client.images().withIds(["456", "789"]).execute(); +client.images().withResponseField("id").withResponseField(["id", "artist"]).execute(); +client.videos().withId("101112").withResponseField(["summary_set", "downloads"]).execute(); +client.videos().withIds([123, 456]).execute(); +client.events().withId("123").withIds(["456", "789"]).withResponseField("id").execute(); + +// Requests with no parameters of their own. +client.collections().execute(); +client.countries().execute(); + +// Downloads. Note the string height and the numeric product id, per the tests. +client.downloadsimages() + .withId("503928206") + .withFileType("jpg") + .withHeight("592") + .withHeight(592) + .withProductId(5678) + .withProductId("5678") + .withProductType("easyaccess") + .execute(); +client.downloadsvideos().withId("123").withSize("hd1").withProductId(5678).execute(); + +// Custom requests, for each supported method. +client.customrequest().withRoute("search/images").withMethod("get") + .withQueryParameters({ phrase: "cat", file_types: "eps" }).execute(); +client.customrequest().withRoute("boards/123").withMethod("post") + .withBody({ name: "this board", description: "some description" }).execute(); +client.customrequest().withRoute("boards/123").withMethod("put").withBody({ name: "n" }).execute(); +client.customrequest().withRoute("boards/123").withMethod("delete").execute(); + +// Image search across both catalogs. Singular builders take arrays. +client.searchimages() + .withAcceptLanguage("en-us") + .withAgeOfPeople(["adult", "newborn", "0-1_months"]) + .withArtist(["roman makhmutov", "Linda Raymond"]) + .withCollectionCode(["WRI", "ARF"]) + .withCollectionsFilterType("exclude") + .withColor("#002244") + .withComposition(["abstract", "headshot"]) + .withEmbedContentOnly(true) + .withEthnicity(["black", "japanese"]) + .withEventId([1234, 5678]) + .withExcludeNudity(true) + .withResponseField(["asset_family", "id"]) + .withFileType(["eps", "jpg"]) + .withGraphicalStyle(["fine_art", "illustration"]) + .withKeywordId([1234, 5678]) + .withMinimumSize("small") + .withNumberOfPeople(["one", "group"]) + .withOrientation(["horizontal", "square"]) + .withPage(3) + .withPageSize(50) + .withPhrase("cat") + .withPrestigeContentOnly(true) + .withProductType(["easyaccess", "editorialsubscription"]) + .withSortOrder("newest") + .withSpecificPeople("reggie jackson") + .execute(); + +// The same builders called with single values rather than arrays. +client.searchimages() + .withAgeOfPeople("adult") + .withArtist("roman makhmutov") + .withCollectionCode("WRI") + .withComposition("headshot") + .withEthnicity("black") + .withEventId(1234) + .withEventId("1234") + .withFileType("jpg") + .withGraphicalStyle("fine_art") + .withKeywordId(1234) + .withNumberOfPeople("one") + .withOrientation("horizontal") + .withProductType("easyaccess") + .withResponseField("id") + .withSpecificPeople(["reggie jackson", "babe ruth"]) + .execute(); + +client.searchimagescreative() + .withAcceptLanguage("en-us") + .withAgeOfPeople(["12-17_months", "mature_adult"]) + .withArtist(["roman makhmutov"]) + .withCollectionCode(["WRI"]) + .withCollectionsFilterType("exclude") + .withColor("#002244") + .withComposition(["abstract"]) + .withEmbedContentOnly(true) + .withEthnicity(["japanese"]) + .withExcludeEditorialUseOnly(true) + .withExcludeNudity(true) + .withResponseField(["id"]) + .withFileType(["eps", "jpg"]) + .withGraphicalStyle(["illustration"]) + .withKeywordId([1234, 5678]) + .withMinimumSize("small") + .withNumberOfPeople(["group"]) + .withOrientation(["horizontal", "vertical"]) + .withPage(3) + .withPageSize(50) + .withPhrase("cat") + .withPrestigeContentOnly(true) + .withProductType(["easyaccess"]) + .withSafeSearch(true) + .withSortOrder("newest") + .execute(); + +client.searchimageseditorial() + .withAcceptLanguage("en-us") + .withAgeOfPeople(["adult"]) + .withArtist(["Linda Raymond"]) + .withCollectionCode(["ARF"]) + .withCollectionsFilterType("exclude") + .withColor("#002244") + .withComposition(["headshot"]) + .withEditorialSegments(["archival", "publicity"]) + .withEmbedContentOnly(true) + .withEndDate("2015-04-01") + .withEntityUris([123, 456]) + .withEntityUris(["123", "456"]) + .withEthnicity(["black"]) + .withEventId([1234, 5678]) + .withExcludeNudity(true) + .withResponseField(["id", "artist"]) + .withFileType(["jpg"]) + .withGraphicalStyle(["fine_art"]) + .withKeywordId([1234]) + .withMinimumQualityRank(2) + .withMinimumSize("small") + .withNumberOfPeople(["one"]) + .withOrientation(["horizontal"]) + .withPage(3) + .withPageSize(50) + .withPhrase("monkey") + .withProductType(["editorialsubscription"]) + .withSortOrder("newest") + .withSpecificPeople("reggie jackson") + .withStartDate("2015-04-01") + .execute(); + +client.searchvideos() + .withAcceptLanguage("en-us") + .withAgeOfPeople(["adult"]) + .withCollectionCode(["WRI"]) + .withCollectionsFilterType("exclude") + .withEditorialVideoType("raw") + .withEditorialVideoType(["raw", "produced"]) + .withExcludeNudity(true) + .withResponseField(["id"]) + .withFormatAvailable("hd") + .withFrameRate(["24", "29.97"]) + .withKeywordId([1234, 5678]) + .withLicenseModel(["rightsmanaged", "royaltyfree"]) + .withPage(3) + .withPageSize(50) + .withPhrase("cat") + .withProductType(["easyaccess"]) + .withSortOrder("newest") + .withSpecificPeople("reggie jackson") + .execute(); + +client.searchvideoscreative() + .withAcceptLanguage("en-us") + .withAgeOfPeople(["adult"]) + .withCollectionCode(["WRI"]) + .withCollectionsFilterType("exclude") + .withExcludeEditorialUseOnly(true) + .withExcludeNudity(true) + .withResponseField(["id"]) + .withFormatAvailable("hd") + .withFrameRate(["24"]) + .withKeywordId([1234]) + .withLicenseModel(["royaltyfree"]) + .withMinClipLength(15) + .withOrientation(["horizontal", "square"]) + .withPage(3) + .withPageSize(50) + .withPhrase("cat") + .withProductType(["easyaccess"]) + .withSafeSearch(true) + .withSortOrder("newest") + .execute(); + +client.searchvideoseditorial() + .withAcceptLanguage("en-us") + .withAgeOfPeople(["adult"]) + .withCollectionCode(["ARF"]) + .withCollectionsFilterType("exclude") + .withEditorialVideoType("raw") + .withEndDate("2023-12-31") + .withEntityUris([123, 456]) + .withExcludeNudity(true) + .withResponseField(["id"]) + .withFormatAvailable("hd") + .withFrameRate(["29.97"]) + .withKeywordId([5678]) + .withOrientation(["horizontal", "vertical"]) + .withPage(3) + .withPageSize(50) + .withPhrase("monkey") + .withProductType(["editorialsubscription"]) + .withSortOrder("newest") + .withSpecificPeople("reggie jackson") + .withStartDate("2023-01-01") + .execute(); + +// Custom parameters and headers come from the base class, so they must be +// available on every request and must preserve the concrete type when chained +// in any order. +client.searchimagescreative() + .withPage(1) + .withCustomParameter("safe_search", "true") + .withCustomHeader("gi-country-code", "CAN") + .withPageSize(1) + .withPhrase("beach") + .execute(); + +client.images().withCustomHeader("gi-country-code", "CAN").withId("123").execute(); +client.customrequest().withCustomParameter("x", "y").withRoute("r").withMethod("get").execute(); + +// The base class members are part of the public surface. +const request = client.searchimages(); +const headers: Record = request.headers; +const params: Record = request.params; +const host: string = request.hostName; +request.addParameter("phrase", "cat"); +request.addParameter("page", 1); +request.addParameter("exclude_nudity", true); +request.addParameter("file_types", ["eps", "jpg"]); + +// The data fields the search classes initialise are externally assignable. +request.page = 2; +request.pageSize = 10; +request.phrase = "cat"; +request.fields = ["id"]; +request.eventIds = [1234];