import {z} from 'zod' export type SearchQuery = string; export type Count = number; export type Freshness = 'day' | 'week' | 'month' | 'year'; export type FreshnessValue = Freshness | string; export type Offset = number; export type Country = 'AR' | 'AU' | 'AT' | 'BE' | 'BR' | 'CA' | 'CL' | 'DK' | 'FI' | 'FR' | 'DE' | 'HK' | 'IN' | 'ID' | 'IT' | 'JP' | 'KR' | 'MY' | 'MX' | 'NL' | 'NZ' | 'NO' | 'CN' | 'PL' | 'PT' | 'PH' | 'RU' | 'SA' | 'ZA' | 'ES' | 'SE' | 'CH' | 'TW' | 'TR' | 'GB' | 'US'; export type Language = 'AR' | 'EU' | 'BN' | 'BG' | 'CA' | 'ZH-HANS' | 'ZH-HANT' | 'HR' | 'CS' | 'DA' | 'NL' | 'EN' | 'EN-GB' | 'ET' | 'FI' | 'FR' | 'GL' | 'DE' | 'EL' | 'GU' | 'HE' | 'HI' | 'HU' | 'IS' | 'IT' | 'JA' | 'KN' | 'KO' | 'LV' | 'LT' | 'MS' | 'ML' | 'MR' | 'NB' | 'PL' | 'PT-BR' | 'PT-PT' | 'PA' | 'RO' | 'RU' | 'SR' | 'SK' | 'SL' | 'ES' | 'SV' | 'TA' | 'TE' | 'TH' | 'TR' | 'UK' | 'VI'; export type SafeSearch = 'off' | 'moderate' | 'strict'; export type LiveCrawl = 'web' | 'all'; export type LiveCrawlFormats = Array<'html' | 'markdown'>; export type GetApiV1SearchArgs = {query: SearchQuery, count?: Count | undefined, freshness?: FreshnessValue | undefined, offset?: Offset | undefined, country?: Country | undefined, language?: Language | undefined, safesearch?: SafeSearch | undefined, livecrawl?: LiveCrawl | undefined, livecrawl_formats?: LiveCrawlFormats | undefined, include_domains?: string | undefined, exclude_domains?: string | undefined, boost_domains?: string | undefined, crawl_timeout?: number | undefined}; export type GetApiV1SearchOptions = {baseUrl?: string | undefined, ApiKeyAuth?: string | undefined, fetch?: typeof fetch | undefined}; export const webContents = z.object({html: z.string().optional(), markdown: z.string().optional()}); export const webResult = z.object({url: z.string().optional(), title: z.string().optional(), description: z.string().optional(), snippets: z.array(z.string()).optional(), thumbnail_url: z.string().optional(), page_age: z.string().optional(), contents: webContents.optional(), favicon_url: z.string().optional()}); export const searchMetadata = z.object({search_uuid: z.string().optional(), query: z.string().optional(), latency: z.number().optional()}); export const searchResponse = z.object({results: z.object({web: z.array(webResult).optional()}).optional(), metadata: searchMetadata.optional()}); export const validateResponse = async ( schema: { readonly '~standard': { readonly validate: (value: unknown) => ({ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }) | Promise<{ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }> } }, value: unknown ): Promise => { const result = await schema['~standard'].validate(value) if (result.issues !== undefined) { const detail = result.issues .map(({ message, path }) => path?.length ? `${path.map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')}: ${message}` : message ) .join('; ') throw new Error(`Response does not match the schema: ${detail}`, { cause: { issues: result.issues } }) } return result.value }; /** * Returns a list of unified search results from web and news sources * * GET /v1/search */ export const getApiV1Search = async (args: GetApiV1SearchArgs, options?: GetApiV1SearchOptions) => { const {baseUrl = "https://ydc-index.io", fetch: fetchFn = fetch, ApiKeyAuth} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('accept', "application/json") if (args.query != null) query.append("query", String(args.query)) if (args.count != null) query.append("count", String(args.count)) if (args.freshness != null) query.append("freshness", String(args.freshness)) if (args.offset != null) query.append("offset", String(args.offset)) if (args.country != null) query.append("country", String(args.country)) if (args.language != null) query.append("language", String(args.language)) if (args.safesearch != null) query.append("safesearch", String(args.safesearch)) if (args.livecrawl != null) query.append("livecrawl", String(args.livecrawl)) for (const item of args.livecrawl_formats ?? []) query.append("livecrawl_formats", String(item)) if (args.include_domains != null) query.append("include_domains", String(args.include_domains)) if (args.exclude_domains != null) query.append("exclude_domains", String(args.exclude_domains)) if (args.boost_domains != null) query.append("boost_domains", String(args.boost_domains)) if (args.crawl_timeout != null) query.append("crawl_timeout", String(args.crawl_timeout)) if (ApiKeyAuth !== undefined) headers.set("X-API-Key", ApiKeyAuth) const url = `${baseUrl.replace(/\/+$/, '')}/v1/search${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "GET", headers}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(searchResponse, await res.json()) };