From 9d08ffffa5a9b8c8d63c453a6cc5f37f8973e875 Mon Sep 17 00:00:00 2001 From: nya-a-cat <188900516+nya-a-cat@users.noreply.github.com> Date: Sun, 28 Jun 2026 08:26:53 +0800 Subject: [PATCH] Add basic TypeScript coverage for search parameters --- CHANGELOG.md | 2 + README.md | 20 +++++++ mod.ts | 1 + src/serpapi.ts | 21 +++---- src/types.ts | 131 +++++++++++++++++++++++++++++++++++++++++++- src/utils.ts | 6 +- tests/types_test.ts | 78 ++++++++++++++++++++++++++ 7 files changed, 244 insertions(+), 15 deletions(-) create mode 100644 tests/types_test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 1974c63..d72fbe2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to ### Added +- Add basic TypeScript coverage for engine names and core search parameters. +- Expose `EngineName` type. - Expose `EngineParameters` type. - Expose `InvalidArgumentError` error. diff --git a/README.md b/README.md index a7ffff0..45d3c35 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,26 @@ const response = await getJson({ console.log(response); ``` +### TypeScript + +The library exposes basic TypeScript types for engine names and core search +parameters. These types validate the `engine` value while still allowing +engine-specific query parameters that are not modeled yet. + +```ts +import { type EngineParameters, getJson } from "serpapi"; + +const parameters: EngineParameters = { + engine: "google_light", + api_key: API_KEY, + q: "coffee", + timeout: 60000, +}; + +const response = await getJson(parameters); +console.log(response.search_metadata); +``` + ## Features - TypeScript support. diff --git a/mod.ts b/mod.ts index 3eaa3f4..6e43941 100644 --- a/mod.ts +++ b/mod.ts @@ -10,6 +10,7 @@ export { export type { AccountApiParameters, BaseResponse, + EngineName, EngineParameters, GetBySearchIdParameters, LocationsApiParameters, diff --git a/src/serpapi.ts b/src/serpapi.ts index 9422ea7..b9bb5d4 100644 --- a/src/serpapi.ts +++ b/src/serpapi.ts @@ -2,6 +2,7 @@ import { InvalidArgumentError } from "./errors.ts"; import { AccountApiParameters, BaseResponse, + EngineName, EngineParameters, GetBySearchIdParameters, LocationsApiParameters, @@ -45,8 +46,8 @@ export function getJson( * getJson("google", { api_key: API_KEY, q: "coffee" }, console.log); */ export function getJson( - engine: string, - parameters: EngineParameters, + engine: EngineName, + parameters: EngineParameters, callback?: (json: BaseResponse) => void, ): Promise; @@ -54,14 +55,14 @@ export function getJson( ...args: | [parameters: EngineParameters, callback?: (json: BaseResponse) => void] | [ - engine: string, - parameters: EngineParameters, + engine: EngineName, + parameters: EngineParameters, callback?: (json: BaseResponse) => void, ] ): Promise { if (typeof args[0] === "string" && typeof args[1] === "object") { const [engine, parameters, callback] = args; - const newParameters = { ...parameters, engine } as EngineParameters; + const newParameters = { ...parameters, engine }; return _getJson(newParameters, callback); } else if ( typeof args[0] === "object" && @@ -126,8 +127,8 @@ export function getHtml( * getHtml({ engine: "google", api_key: API_KEY, q: "coffee" }, console.log); */ export function getHtml( - engine: string, - parameters: EngineParameters, + engine: EngineName, + parameters: EngineParameters, callback?: (html: string) => void, ): Promise; @@ -135,14 +136,14 @@ export function getHtml( ...args: | [parameters: EngineParameters, callback?: (html: string) => void] | [ - engine: string, - parameters: EngineParameters, + engine: EngineName, + parameters: EngineParameters, callback?: (html: string) => void, ] ): Promise { if (typeof args[0] === "string" && typeof args[1] === "object") { const [engine, parameters, callback] = args; - const newParameters = { ...parameters, engine } as EngineParameters; + const newParameters = { ...parameters, engine }; return _getHtml(newParameters, callback); } else if ( typeof args[0] === "object" && diff --git a/src/types.ts b/src/types.ts index c83e861..8ae83d6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,132 @@ -// deno-lint-ignore no-explicit-any -export type EngineParameters = Record; +import type http from "node:http"; + +// Keep this list at the engine-name level. Per-engine parameter schemas are +// intentionally out of scope for this minimal TypeScript coverage layer. +export type EngineName = + | "amazon" + | "amazon_product" + | "apple_maps" + | "apple_maps_places" + | "baidu" + | "baidu_news" + | "bing" + | "bing_copilot" + | "bing_images" + | "bing_maps" + | "bing_news" + | "bing_product" + | "bing_reverse_image" + | "bing_shopping" + | "bing_videos" + | "brave_ai_mode" + | "duckduckgo" + | "duckduckgo_light" + | "duckduckgo_maps" + | "duckduckgo_news" + | "ebay" + | "ebay_product" + | "facebook_profile" + | "google" + | "google_about_this_result" + | "google_ads" + | "google_ads_transparency_center" + | "google_ads_transparency_center_ad_details" + | "google_ai_mode" + | "google_ai_overview" + | "google_autocomplete" + | "google_events" + | "google_finance" + | "google_finance_markets" + | "google_flights" + | "google_flights_autocomplete" + | "google_flights_deals" + | "google_forums" + | "google_hotels" + | "google_hotels_autocomplete" + | "google_hotels_photos" + | "google_hotels_reviews" + | "google_images" + | "google_images_light" + | "google_images_related_content" + | "google_immersive_product" + | "google_jobs" + | "google_jobs_listing" + | "google_lens" + | "google_light" + | "google_local" + | "google_local_services" + | "google_maps" + | "google_maps_autocomplete" + | "google_maps_contributor_reviews" + | "google_maps_directions" + | "google_maps_photo_meta" + | "google_maps_photos" + | "google_maps_posts" + | "google_maps_reviews" + | "google_news" + | "google_news_light" + | "google_patents" + | "google_patents_details" + | "google_play" + | "google_play_product" + | "google_related_questions" + | "google_scholar" + | "google_scholar_author" + | "google_scholar_case_law" + | "google_scholar_cite" + | "google_scholar_profiles" + | "google_shopping" + | "google_shopping_filters" + | "google_shopping_light" + | "google_short_videos" + | "google_travel_explore" + | "google_trends" + | "google_videos" + | "google_videos_light" + | "home_depot" + | "home_depot_product" + | "instagram_profile" + | "naver" + | "naver_ai_overview" + | "open_table_reviews" + | "tripadvisor" + | "tripadvisor_place" + | "tripadvisor_reviews" + | "walmart" + | "walmart_product" + | "walmart_product_reviews" + | "walmart_product_sellers" + | "yahoo" + | "yahoo_images" + | "yahoo_shopping" + | "yahoo_videos" + | "yandex" + | "yandex_images" + | "yandex_videos" + | "yelp" + | "yelp_place" + | "yelp_reviews" + | "youtube" + | "youtube_video" + | "youtube_video_transcript"; + +type BaseParameters = { + api_key?: string | null; + async?: boolean; + device?: "desktop" | "tablet" | "mobile"; + no_cache?: boolean; + output?: "json" | "html"; + q?: string; + requestOptions?: http.RequestOptions; + timeout?: number; + zero_trace?: boolean; +}; + +export type EngineParameters = + & (EngineRequired extends true ? { engine: EngineName } + : { engine?: EngineName }) + & BaseParameters + & Record; // deno-lint-ignore no-explicit-any export type BaseResponse = Record; diff --git a/src/utils.ts b/src/utils.ts index 8bb21f9..47a4d9c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -44,7 +44,7 @@ export function getSource() { export function buildRequestOptions( path: string, - parameters: qs.ParsedUrlQueryInput, + parameters: Record, ): http.RequestOptions { const clonedParams = { ...parameters }; for (const k in clonedParams) { @@ -58,7 +58,7 @@ export function buildRequestOptions( } const basicOptions = { ..._internals.getHostnameAndPort(), - path: `${path}?${qs.stringify(clonedParams)}`, + path: `${path}?${qs.stringify(clonedParams as qs.ParsedUrlQueryInput)}`, method: "GET", }; @@ -71,7 +71,7 @@ export function buildRequestOptions( export function execute( path: string, - parameters: qs.ParsedUrlQueryInput, + parameters: Record, timeout: number, ): Promise { const options = buildRequestOptions(path, { diff --git a/tests/types_test.ts b/tests/types_test.ts new file mode 100644 index 0000000..9e44aaf --- /dev/null +++ b/tests/types_test.ts @@ -0,0 +1,78 @@ +import { describe, it } from "https://deno.land/std@0.170.0/testing/bdd.ts"; +import { assertEquals } from "https://deno.land/std@0.170.0/testing/asserts.ts"; +import { getHtml, getJson } from "../mod.ts"; +import type { BaseResponse, EngineName, EngineParameters } from "../mod.ts"; + +function expectType(value: T): T { + return value; +} + +describe("types", () => { + it("allows basic typed searches", () => { + const engine = expectType("google_light"); + const parameters = expectType({ + engine, + api_key: "api_key", + q: "coffee", + location: "Austin, Texas", + async: true, + no_cache: true, + timeout: 1000, + requestOptions: { + headers: { + "User-Agent": "serpapi-types-test", + }, + }, + }); + + assertEquals(parameters.engine, "google_light"); + }); + + it("exposes typed overloads for core search functions", () => { + const typedGetJson = expectType<{ + ( + parameters: EngineParameters, + callback?: (json: BaseResponse) => void, + ): Promise; + ( + engine: EngineName, + parameters: EngineParameters, + callback?: (json: BaseResponse) => void, + ): Promise; + }>(getJson); + + const typedGetHtml = expectType<{ + ( + parameters: EngineParameters, + callback?: (html: string) => void, + ): Promise; + ( + engine: EngineName, + parameters: EngineParameters, + callback?: (html: string) => void, + ): Promise; + }>(getHtml); + + assertEquals(typeof typedGetJson, "function"); + assertEquals(typeof typedGetHtml, "function"); + }); + + it("rejects invalid engine names and core parameter types", () => { + // @ts-expect-error "gogle" is not a supported engine name. + const invalidEngine: EngineName = "gogle"; + const invalidParameters: EngineParameters = { + // @ts-expect-error object form requires a valid engine name. + engine: "gogle", + q: "coffee", + }; + // @ts-expect-error object form requires an engine parameter. + const missingEngine: EngineParameters = { q: "coffee" }; + // @ts-expect-error timeout must be a number. + const badTimeout: EngineParameters = { engine: "google", timeout: "1000" }; + + assertEquals( + [invalidEngine, invalidParameters, missingEngine, badTimeout].length, + 4, + ); + }); +});