Skip to content

Commit 9d08fff

Browse files
committed
Add basic TypeScript coverage for search parameters
1 parent 9e409bd commit 9d08fff

7 files changed

Lines changed: 244 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ and this project adheres to
1010

1111
### Added
1212

13+
- Add basic TypeScript coverage for engine names and core search parameters.
14+
- Expose `EngineName` type.
1315
- Expose `EngineParameters` type.
1416
- Expose `InvalidArgumentError` error.
1517

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,26 @@ const response = await getJson({
8787
console.log(response);
8888
```
8989

90+
### TypeScript
91+
92+
The library exposes basic TypeScript types for engine names and core search
93+
parameters. These types validate the `engine` value while still allowing
94+
engine-specific query parameters that are not modeled yet.
95+
96+
```ts
97+
import { type EngineParameters, getJson } from "serpapi";
98+
99+
const parameters: EngineParameters = {
100+
engine: "google_light",
101+
api_key: API_KEY,
102+
q: "coffee",
103+
timeout: 60000,
104+
};
105+
106+
const response = await getJson(parameters);
107+
console.log(response.search_metadata);
108+
```
109+
90110
## Features
91111

92112
- TypeScript support.

mod.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export {
1010
export type {
1111
AccountApiParameters,
1212
BaseResponse,
13+
EngineName,
1314
EngineParameters,
1415
GetBySearchIdParameters,
1516
LocationsApiParameters,

src/serpapi.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { InvalidArgumentError } from "./errors.ts";
22
import {
33
AccountApiParameters,
44
BaseResponse,
5+
EngineName,
56
EngineParameters,
67
GetBySearchIdParameters,
78
LocationsApiParameters,
@@ -45,23 +46,23 @@ export function getJson(
4546
* getJson("google", { api_key: API_KEY, q: "coffee" }, console.log);
4647
*/
4748
export function getJson(
48-
engine: string,
49-
parameters: EngineParameters,
49+
engine: EngineName,
50+
parameters: EngineParameters<false>,
5051
callback?: (json: BaseResponse) => void,
5152
): Promise<BaseResponse>;
5253

5354
export function getJson(
5455
...args:
5556
| [parameters: EngineParameters, callback?: (json: BaseResponse) => void]
5657
| [
57-
engine: string,
58-
parameters: EngineParameters,
58+
engine: EngineName,
59+
parameters: EngineParameters<false>,
5960
callback?: (json: BaseResponse) => void,
6061
]
6162
): Promise<BaseResponse> {
6263
if (typeof args[0] === "string" && typeof args[1] === "object") {
6364
const [engine, parameters, callback] = args;
64-
const newParameters = { ...parameters, engine } as EngineParameters;
65+
const newParameters = { ...parameters, engine };
6566
return _getJson(newParameters, callback);
6667
} else if (
6768
typeof args[0] === "object" &&
@@ -126,23 +127,23 @@ export function getHtml(
126127
* getHtml({ engine: "google", api_key: API_KEY, q: "coffee" }, console.log);
127128
*/
128129
export function getHtml(
129-
engine: string,
130-
parameters: EngineParameters,
130+
engine: EngineName,
131+
parameters: EngineParameters<false>,
131132
callback?: (html: string) => void,
132133
): Promise<string>;
133134

134135
export function getHtml(
135136
...args:
136137
| [parameters: EngineParameters, callback?: (html: string) => void]
137138
| [
138-
engine: string,
139-
parameters: EngineParameters,
139+
engine: EngineName,
140+
parameters: EngineParameters<false>,
140141
callback?: (html: string) => void,
141142
]
142143
): Promise<string> {
143144
if (typeof args[0] === "string" && typeof args[1] === "object") {
144145
const [engine, parameters, callback] = args;
145-
const newParameters = { ...parameters, engine } as EngineParameters;
146+
const newParameters = { ...parameters, engine };
146147
return _getHtml(newParameters, callback);
147148
} else if (
148149
typeof args[0] === "object" &&

src/types.ts

Lines changed: 129 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,132 @@
1-
// deno-lint-ignore no-explicit-any
2-
export type EngineParameters = Record<string, any>;
1+
import type http from "node:http";
2+
3+
// Keep this list at the engine-name level. Per-engine parameter schemas are
4+
// intentionally out of scope for this minimal TypeScript coverage layer.
5+
export type EngineName =
6+
| "amazon"
7+
| "amazon_product"
8+
| "apple_maps"
9+
| "apple_maps_places"
10+
| "baidu"
11+
| "baidu_news"
12+
| "bing"
13+
| "bing_copilot"
14+
| "bing_images"
15+
| "bing_maps"
16+
| "bing_news"
17+
| "bing_product"
18+
| "bing_reverse_image"
19+
| "bing_shopping"
20+
| "bing_videos"
21+
| "brave_ai_mode"
22+
| "duckduckgo"
23+
| "duckduckgo_light"
24+
| "duckduckgo_maps"
25+
| "duckduckgo_news"
26+
| "ebay"
27+
| "ebay_product"
28+
| "facebook_profile"
29+
| "google"
30+
| "google_about_this_result"
31+
| "google_ads"
32+
| "google_ads_transparency_center"
33+
| "google_ads_transparency_center_ad_details"
34+
| "google_ai_mode"
35+
| "google_ai_overview"
36+
| "google_autocomplete"
37+
| "google_events"
38+
| "google_finance"
39+
| "google_finance_markets"
40+
| "google_flights"
41+
| "google_flights_autocomplete"
42+
| "google_flights_deals"
43+
| "google_forums"
44+
| "google_hotels"
45+
| "google_hotels_autocomplete"
46+
| "google_hotels_photos"
47+
| "google_hotels_reviews"
48+
| "google_images"
49+
| "google_images_light"
50+
| "google_images_related_content"
51+
| "google_immersive_product"
52+
| "google_jobs"
53+
| "google_jobs_listing"
54+
| "google_lens"
55+
| "google_light"
56+
| "google_local"
57+
| "google_local_services"
58+
| "google_maps"
59+
| "google_maps_autocomplete"
60+
| "google_maps_contributor_reviews"
61+
| "google_maps_directions"
62+
| "google_maps_photo_meta"
63+
| "google_maps_photos"
64+
| "google_maps_posts"
65+
| "google_maps_reviews"
66+
| "google_news"
67+
| "google_news_light"
68+
| "google_patents"
69+
| "google_patents_details"
70+
| "google_play"
71+
| "google_play_product"
72+
| "google_related_questions"
73+
| "google_scholar"
74+
| "google_scholar_author"
75+
| "google_scholar_case_law"
76+
| "google_scholar_cite"
77+
| "google_scholar_profiles"
78+
| "google_shopping"
79+
| "google_shopping_filters"
80+
| "google_shopping_light"
81+
| "google_short_videos"
82+
| "google_travel_explore"
83+
| "google_trends"
84+
| "google_videos"
85+
| "google_videos_light"
86+
| "home_depot"
87+
| "home_depot_product"
88+
| "instagram_profile"
89+
| "naver"
90+
| "naver_ai_overview"
91+
| "open_table_reviews"
92+
| "tripadvisor"
93+
| "tripadvisor_place"
94+
| "tripadvisor_reviews"
95+
| "walmart"
96+
| "walmart_product"
97+
| "walmart_product_reviews"
98+
| "walmart_product_sellers"
99+
| "yahoo"
100+
| "yahoo_images"
101+
| "yahoo_shopping"
102+
| "yahoo_videos"
103+
| "yandex"
104+
| "yandex_images"
105+
| "yandex_videos"
106+
| "yelp"
107+
| "yelp_place"
108+
| "yelp_reviews"
109+
| "youtube"
110+
| "youtube_video"
111+
| "youtube_video_transcript";
112+
113+
type BaseParameters = {
114+
api_key?: string | null;
115+
async?: boolean;
116+
device?: "desktop" | "tablet" | "mobile";
117+
no_cache?: boolean;
118+
output?: "json" | "html";
119+
q?: string;
120+
requestOptions?: http.RequestOptions;
121+
timeout?: number;
122+
zero_trace?: boolean;
123+
};
124+
125+
export type EngineParameters<EngineRequired = true> =
126+
& (EngineRequired extends true ? { engine: EngineName }
127+
: { engine?: EngineName })
128+
& BaseParameters
129+
& Record<string, unknown>;
3130

4131
// deno-lint-ignore no-explicit-any
5132
export type BaseResponse = Record<string, any>;

src/utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export function getSource() {
4444

4545
export function buildRequestOptions(
4646
path: string,
47-
parameters: qs.ParsedUrlQueryInput,
47+
parameters: Record<string, unknown>,
4848
): http.RequestOptions {
4949
const clonedParams = { ...parameters };
5050
for (const k in clonedParams) {
@@ -58,7 +58,7 @@ export function buildRequestOptions(
5858
}
5959
const basicOptions = {
6060
..._internals.getHostnameAndPort(),
61-
path: `${path}?${qs.stringify(clonedParams)}`,
61+
path: `${path}?${qs.stringify(clonedParams as qs.ParsedUrlQueryInput)}`,
6262
method: "GET",
6363
};
6464

@@ -71,7 +71,7 @@ export function buildRequestOptions(
7171

7272
export function execute(
7373
path: string,
74-
parameters: qs.ParsedUrlQueryInput,
74+
parameters: Record<string, unknown>,
7575
timeout: number,
7676
): Promise<string> {
7777
const options = buildRequestOptions(path, {

tests/types_test.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { describe, it } from "https://deno.land/std@0.170.0/testing/bdd.ts";
2+
import { assertEquals } from "https://deno.land/std@0.170.0/testing/asserts.ts";
3+
import { getHtml, getJson } from "../mod.ts";
4+
import type { BaseResponse, EngineName, EngineParameters } from "../mod.ts";
5+
6+
function expectType<T>(value: T): T {
7+
return value;
8+
}
9+
10+
describe("types", () => {
11+
it("allows basic typed searches", () => {
12+
const engine = expectType<EngineName>("google_light");
13+
const parameters = expectType<EngineParameters>({
14+
engine,
15+
api_key: "api_key",
16+
q: "coffee",
17+
location: "Austin, Texas",
18+
async: true,
19+
no_cache: true,
20+
timeout: 1000,
21+
requestOptions: {
22+
headers: {
23+
"User-Agent": "serpapi-types-test",
24+
},
25+
},
26+
});
27+
28+
assertEquals(parameters.engine, "google_light");
29+
});
30+
31+
it("exposes typed overloads for core search functions", () => {
32+
const typedGetJson = expectType<{
33+
(
34+
parameters: EngineParameters,
35+
callback?: (json: BaseResponse) => void,
36+
): Promise<BaseResponse>;
37+
(
38+
engine: EngineName,
39+
parameters: EngineParameters<false>,
40+
callback?: (json: BaseResponse) => void,
41+
): Promise<BaseResponse>;
42+
}>(getJson);
43+
44+
const typedGetHtml = expectType<{
45+
(
46+
parameters: EngineParameters,
47+
callback?: (html: string) => void,
48+
): Promise<string>;
49+
(
50+
engine: EngineName,
51+
parameters: EngineParameters<false>,
52+
callback?: (html: string) => void,
53+
): Promise<string>;
54+
}>(getHtml);
55+
56+
assertEquals(typeof typedGetJson, "function");
57+
assertEquals(typeof typedGetHtml, "function");
58+
});
59+
60+
it("rejects invalid engine names and core parameter types", () => {
61+
// @ts-expect-error "gogle" is not a supported engine name.
62+
const invalidEngine: EngineName = "gogle";
63+
const invalidParameters: EngineParameters = {
64+
// @ts-expect-error object form requires a valid engine name.
65+
engine: "gogle",
66+
q: "coffee",
67+
};
68+
// @ts-expect-error object form requires an engine parameter.
69+
const missingEngine: EngineParameters = { q: "coffee" };
70+
// @ts-expect-error timeout must be a number.
71+
const badTimeout: EngineParameters = { engine: "google", timeout: "1000" };
72+
73+
assertEquals(
74+
[invalidEngine, invalidParameters, missingEngine, badTimeout].length,
75+
4,
76+
);
77+
});
78+
});

0 commit comments

Comments
 (0)