Skip to content

Commit 7835c70

Browse files
committed
feat: add typed interfaces for Google Search engine params and responses
Replace Record<string, any> with GoogleSearchParameters and GoogleSearchResponse for the Google engine. Adds a typed overload to getJson() so users get autocomplete and validation when passing engine: "google". Generic types preserved for backwards compatibility. Pattern is extensible to other engines (Bing, YouTube, etc.) by adding files under src/engines/. Fixes #33
1 parent 80b51f9 commit 7835c70

File tree

5 files changed

+259
-3
lines changed

5 files changed

+259
-3
lines changed

mod.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,15 @@ export type {
1212
BaseResponse,
1313
EngineParameters,
1414
GetBySearchIdParameters,
15+
GoogleSearchParameters,
16+
GoogleSearchResponse,
17+
KnowledgeGraph,
1518
LocationsApiParameters,
19+
OrganicResult,
20+
RelatedQuestion,
21+
SearchInformation,
22+
SearchMetadata,
23+
SearchParameters,
1624
} from "./src/types.ts";
1725
export {
1826
getAccount,

src/engines/google.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/** Parameters for the Google Search engine. */
2+
export interface GoogleSearchParameters {
3+
engine: "google";
4+
q: string;
5+
api_key?: string;
6+
timeout?: number;
7+
8+
// Geographic Location
9+
location?: string;
10+
uule?: string;
11+
lat?: number;
12+
lon?: number;
13+
radius?: number;
14+
15+
// Localization
16+
google_domain?: string;
17+
gl?: string;
18+
hl?: string;
19+
cr?: string;
20+
lr?: string;
21+
22+
// Search Type
23+
tbm?: "isch" | "lcl" | "vid" | "nws" | "shop" | "pts";
24+
25+
// Pagination
26+
start?: number;
27+
num?: number;
28+
29+
// Advanced Filters
30+
tbs?: string;
31+
safe?: "active" | "off";
32+
nfpr?: 0 | 1;
33+
filter?: 0 | 1;
34+
35+
// Advanced Google Parameters
36+
ludocid?: string;
37+
lsig?: string;
38+
kgmid?: string;
39+
si?: string;
40+
41+
// SerpApi Parameters
42+
device?: "desktop" | "tablet" | "mobile";
43+
no_cache?: boolean;
44+
async?: boolean;
45+
46+
// deno-lint-ignore no-explicit-any
47+
[key: string]: any;
48+
}
49+
50+
/** Metadata returned with every SerpApi response. */
51+
export interface SearchMetadata {
52+
id: string;
53+
status: string;
54+
json_endpoint: string;
55+
created_at: string;
56+
processed_at: string;
57+
google_url: string;
58+
raw_html_file: string;
59+
total_time_taken: number;
60+
}
61+
62+
/** Echoed search parameters in the response. */
63+
export interface SearchParameters {
64+
engine: string;
65+
q: string;
66+
google_domain?: string;
67+
hl?: string;
68+
gl?: string;
69+
device?: string;
70+
location_requested?: string;
71+
location_used?: string;
72+
[key: string]: unknown;
73+
}
74+
75+
/** Search result info. */
76+
export interface SearchInformation {
77+
query_displayed: string;
78+
total_results: number;
79+
time_taken_displayed: number;
80+
organic_results_state: string;
81+
page_number?: number;
82+
}
83+
84+
/** A single organic search result. */
85+
export interface OrganicResult {
86+
position: number;
87+
title: string;
88+
link: string;
89+
redirect_link?: string;
90+
displayed_link: string;
91+
snippet: string;
92+
snippet_highlighted_words?: string[];
93+
date?: string;
94+
sitelinks?: {
95+
inline?: { title: string; link: string }[];
96+
expanded?: { title: string; link: string; snippet: string }[];
97+
};
98+
rich_snippet?: Record<string, unknown>;
99+
source?: string;
100+
}
101+
102+
/** Knowledge graph result. */
103+
export interface KnowledgeGraph {
104+
title?: string;
105+
type?: string;
106+
description?: string;
107+
source?: { name: string; link: string };
108+
[key: string]: unknown;
109+
}
110+
111+
/** "People also ask" question. */
112+
export interface RelatedQuestion {
113+
question: string;
114+
snippet?: string;
115+
title?: string;
116+
link?: string;
117+
}
118+
119+
/** Google Search JSON response. */
120+
export interface GoogleSearchResponse {
121+
search_metadata: SearchMetadata;
122+
search_parameters: SearchParameters;
123+
search_information?: SearchInformation;
124+
organic_results?: OrganicResult[];
125+
knowledge_graph?: KnowledgeGraph;
126+
related_questions?: RelatedQuestion[];
127+
pagination?: {
128+
current: number;
129+
next?: string;
130+
other_pages?: Record<string, string>;
131+
};
132+
[key: string]: unknown;
133+
}

src/serpapi.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {
44
BaseResponse,
55
EngineParameters,
66
GetBySearchIdParameters,
7+
GoogleSearchParameters,
8+
GoogleSearchResponse,
79
LocationsApiParameters,
810
} from "./types.ts";
911
import { _internals } from "./utils.ts";
@@ -14,6 +16,14 @@ const LOCATIONS_PATH = "/locations.json";
1416
const SEARCH_PATH = "/search";
1517
const SEARCH_ARCHIVE_PATH = `/searches`;
1618

19+
/**
20+
* Get typed JSON response for Google Search.
21+
*/
22+
export function getJson(
23+
parameters: GoogleSearchParameters,
24+
callback?: (json: GoogleSearchResponse) => void,
25+
): Promise<GoogleSearchResponse>;
26+
1727
/**
1828
* Get JSON response based on search parameters.
1929
*
@@ -52,13 +62,15 @@ export function getJson(
5262

5363
export function getJson(
5464
...args:
55-
| [parameters: EngineParameters, callback?: (json: BaseResponse) => void]
65+
// deno-lint-ignore no-explicit-any
66+
| [parameters: EngineParameters, callback?: (json: any) => void]
5667
| [
5768
engine: string,
5869
parameters: EngineParameters,
59-
callback?: (json: BaseResponse) => void,
70+
// deno-lint-ignore no-explicit-any
71+
callback?: (json: any) => void,
6072
]
61-
): Promise<BaseResponse> {
73+
): Promise<BaseResponse | GoogleSearchResponse> {
6274
if (typeof args[0] === "string" && typeof args[1] === "object") {
6375
const [engine, parameters, callback] = args;
6476
const newParameters = { ...parameters, engine } as EngineParameters;

src/types.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ export type EngineParameters = Record<string, any>;
44
// deno-lint-ignore no-explicit-any
55
export type BaseResponse = Record<string, any>;
66

7+
export type {
8+
GoogleSearchParameters,
9+
GoogleSearchResponse,
10+
OrganicResult,
11+
KnowledgeGraph,
12+
RelatedQuestion,
13+
SearchInformation,
14+
SearchMetadata,
15+
SearchParameters,
16+
} from "./engines/google.ts";
17+
718
export type GetBySearchIdParameters = {
819
api_key?: string;
920
timeout?: number;

tests/types_test.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* Type-level tests for engine-specific types.
3+
* These verify compile-time behavior — if this file fails to type-check,
4+
* the types are broken.
5+
*/
6+
import type {
7+
EngineParameters,
8+
GoogleSearchParameters,
9+
GoogleSearchResponse,
10+
OrganicResult,
11+
} from "../src/types.ts";
12+
import { getJson } from "../src/serpapi.ts";
13+
import {
14+
describe,
15+
it,
16+
} from "https://deno.land/std@0.170.0/testing/bdd.ts";
17+
import {
18+
assertEquals,
19+
} from "https://deno.land/std@0.170.0/testing/asserts.ts";
20+
21+
describe("GoogleSearchParameters", () => {
22+
it("accepts valid Google Search params", () => {
23+
const params: GoogleSearchParameters = {
24+
engine: "google",
25+
q: "coffee",
26+
location: "Austin, Texas",
27+
gl: "us",
28+
hl: "en",
29+
num: 10,
30+
start: 0,
31+
safe: "active",
32+
device: "desktop",
33+
};
34+
assertEquals(params.engine, "google");
35+
assertEquals(params.q, "coffee");
36+
});
37+
38+
it("allows tbm search type values", () => {
39+
const params: GoogleSearchParameters = {
40+
engine: "google",
41+
q: "coffee",
42+
tbm: "nws",
43+
};
44+
assertEquals(params.tbm, "nws");
45+
});
46+
});
47+
48+
describe("GoogleSearchResponse", () => {
49+
it("has correct shape", () => {
50+
const response: GoogleSearchResponse = {
51+
search_metadata: {
52+
id: "123",
53+
status: "Success",
54+
json_endpoint: "https://serpapi.com/searches/123.json",
55+
created_at: "2025-01-01",
56+
processed_at: "2025-01-01",
57+
google_url: "https://www.google.com/search?q=coffee",
58+
raw_html_file: "https://serpapi.com/searches/123.html",
59+
total_time_taken: 1.5,
60+
},
61+
search_parameters: {
62+
engine: "google",
63+
q: "coffee",
64+
},
65+
organic_results: [
66+
{
67+
position: 1,
68+
title: "Coffee - Wikipedia",
69+
link: "https://en.wikipedia.org/wiki/Coffee",
70+
displayed_link: "en.wikipedia.org",
71+
snippet: "Coffee is a brewed drink...",
72+
},
73+
],
74+
};
75+
assertEquals(response.search_metadata.status, "Success");
76+
77+
const firstResult: OrganicResult = response.organic_results![0];
78+
assertEquals(firstResult.position, 1);
79+
assertEquals(firstResult.title, "Coffee - Wikipedia");
80+
});
81+
});
82+
83+
describe("backwards compatibility", () => {
84+
it("generic EngineParameters still works", () => {
85+
const params: EngineParameters = {
86+
engine: "bing",
87+
q: "anything",
88+
custom_field: 123,
89+
};
90+
assertEquals(params.engine, "bing");
91+
});
92+
});

0 commit comments

Comments
 (0)