Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@ export type {
BaseResponse,
EngineParameters,
GetBySearchIdParameters,
GoogleSearchParameters,
GoogleSearchResponse,
KnowledgeGraph,
LocationsApiParameters,
OrganicResult,
RelatedQuestion,
SearchInformation,
SearchMetadata,
SearchParameters,
} from "./src/types.ts";
export {
getAccount,
Expand Down
133 changes: 133 additions & 0 deletions src/engines/google.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/** Parameters for the Google Search engine. */
export interface GoogleSearchParameters {
engine: "google";
q: string;
api_key?: string;
timeout?: number;

// Geographic Location
location?: string;
uule?: string;
lat?: number;
lon?: number;
radius?: number;

// Localization
google_domain?: string;
gl?: string;
hl?: string;
cr?: string;
lr?: string;

// Search Type
tbm?: "isch" | "lcl" | "vid" | "nws" | "shop" | "pts";

// Pagination
start?: number;
num?: number;

// Advanced Filters
tbs?: string;
safe?: "active" | "off";
nfpr?: 0 | 1;
filter?: 0 | 1;

// Advanced Google Parameters
ludocid?: string;
lsig?: string;
kgmid?: string;
si?: string;

// SerpApi Parameters
device?: "desktop" | "tablet" | "mobile";
no_cache?: boolean;
async?: boolean;

// deno-lint-ignore no-explicit-any
[key: string]: any;
}

/** Metadata returned with every SerpApi response. */
export interface SearchMetadata {
id: string;
status: string;
json_endpoint: string;
created_at: string;
processed_at: string;
google_url: string;
raw_html_file: string;
total_time_taken: number;
}

/** Echoed search parameters in the response. */
export interface SearchParameters {
engine: string;
q: string;
google_domain?: string;
hl?: string;
gl?: string;
device?: string;
location_requested?: string;
location_used?: string;
[key: string]: unknown;
}

/** Search result info. */
export interface SearchInformation {
query_displayed: string;
total_results: number;
time_taken_displayed: number;
organic_results_state: string;
page_number?: number;
}

/** A single organic search result. */
export interface OrganicResult {
position: number;
title: string;
link: string;
redirect_link?: string;
displayed_link: string;
snippet: string;
snippet_highlighted_words?: string[];
date?: string;
sitelinks?: {
inline?: { title: string; link: string }[];
expanded?: { title: string; link: string; snippet: string }[];
};
rich_snippet?: Record<string, unknown>;
source?: string;
}

/** Knowledge graph result. */
export interface KnowledgeGraph {
title?: string;
type?: string;
description?: string;
source?: { name: string; link: string };
[key: string]: unknown;
}

/** "People also ask" question. */
export interface RelatedQuestion {
question: string;
snippet?: string;
title?: string;
link?: string;
}

/** Google Search JSON response. */
export interface GoogleSearchResponse {
search_metadata: SearchMetadata;
search_parameters: SearchParameters;
search_information?: SearchInformation;
organic_results?: OrganicResult[];
knowledge_graph?: KnowledgeGraph;
related_questions?: RelatedQuestion[];
pagination?: {
current: number;
next?: string;
other_pages?: Record<string, string>;
};
[key: string]: unknown;
}
18 changes: 15 additions & 3 deletions src/serpapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
BaseResponse,
EngineParameters,
GetBySearchIdParameters,
GoogleSearchParameters,
GoogleSearchResponse,
LocationsApiParameters,
} from "./types.ts";
import { _internals } from "./utils.ts";
Expand All @@ -14,6 +16,14 @@ const LOCATIONS_PATH = "/locations.json";
const SEARCH_PATH = "/search";
const SEARCH_ARCHIVE_PATH = `/searches`;

/**
* Get typed JSON response for Google Search.
*/
export function getJson(
parameters: GoogleSearchParameters,
callback?: (json: GoogleSearchResponse) => void,
): Promise<GoogleSearchResponse>;

/**
* Get JSON response based on search parameters.
*
Expand Down Expand Up @@ -52,13 +62,15 @@ export function getJson(

export function getJson(
...args:
| [parameters: EngineParameters, callback?: (json: BaseResponse) => void]
// deno-lint-ignore no-explicit-any
| [parameters: EngineParameters, callback?: (json: any) => void]
| [
engine: string,
parameters: EngineParameters,
callback?: (json: BaseResponse) => void,
// deno-lint-ignore no-explicit-any
callback?: (json: any) => void,
]
): Promise<BaseResponse> {
): Promise<BaseResponse | GoogleSearchResponse> {
Comment on lines 63 to +73
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overload implementation signature uses any for the callback parameter types. You can keep the overloads typed without falling back to any by using a union (e.g., BaseResponse | GoogleSearchResponse) or making _getJson generic over the response type. This keeps the typed Google overload aligned with the goal of improved type-safety.

Copilot uses AI. Check for mistakes.
if (typeof args[0] === "string" && typeof args[1] === "object") {
const [engine, parameters, callback] = args;
const newParameters = { ...parameters, engine } as EngineParameters;
Expand Down
11 changes: 11 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ export type EngineParameters = Record<string, any>;
// deno-lint-ignore no-explicit-any
export type BaseResponse = Record<string, any>;

export type {
GoogleSearchParameters,
GoogleSearchResponse,
OrganicResult,
KnowledgeGraph,
RelatedQuestion,
SearchInformation,
SearchMetadata,
SearchParameters,
} from "./engines/google.ts";

export type GetBySearchIdParameters = {
api_key?: string;
timeout?: number;
Expand Down
92 changes: 92 additions & 0 deletions tests/types_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* Type-level tests for engine-specific types.
* These verify compile-time behavior — if this file fails to type-check,
* the types are broken.
*/
import type {
EngineParameters,
GoogleSearchParameters,
GoogleSearchResponse,
OrganicResult,
} from "../src/types.ts";
import { getJson } from "../src/serpapi.ts";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getJson is imported but I don't see it being used for any tests

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";

describe("GoogleSearchParameters", () => {
it("accepts valid Google Search params", () => {
const params: GoogleSearchParameters = {
engine: "google",
q: "coffee",
location: "Austin, Texas",
gl: "us",
hl: "en",
num: 10,
start: 0,
safe: "active",
device: "desktop",
};
assertEquals(params.engine, "google");
assertEquals(params.q, "coffee");
});

it("allows tbm search type values", () => {
const params: GoogleSearchParameters = {
engine: "google",
q: "coffee",
tbm: "nws",
};
assertEquals(params.tbm, "nws");
});
});

describe("GoogleSearchResponse", () => {
it("has correct shape", () => {
const response: GoogleSearchResponse = {
search_metadata: {
id: "123",
status: "Success",
json_endpoint: "https://serpapi.com/searches/123.json",
created_at: "2025-01-01",
processed_at: "2025-01-01",
google_url: "https://www.google.com/search?q=coffee",
raw_html_file: "https://serpapi.com/searches/123.html",
total_time_taken: 1.5,
},
search_parameters: {
engine: "google",
q: "coffee",
},
organic_results: [
{
position: 1,
title: "Coffee - Wikipedia",
link: "https://en.wikipedia.org/wiki/Coffee",
displayed_link: "en.wikipedia.org",
snippet: "Coffee is a brewed drink...",
},
],
};
assertEquals(response.search_metadata.status, "Success");

const firstResult: OrganicResult = response.organic_results![0];
assertEquals(firstResult.position, 1);
assertEquals(firstResult.title, "Coffee - Wikipedia");
});
});

describe("backwards compatibility", () => {
it("generic EngineParameters still works", () => {
const params: EngineParameters = {
engine: "bing",
q: "anything",
custom_field: 123,
};
assertEquals(params.engine, "bing");
});
});
Loading