hacker-news-reel
Version:
A lightweight, typed client for the Hacker News API with validation using Zod
248 lines (247 loc) • 8.23 kB
TypeScript
import { z } from 'zod';
import type { ClientHooks } from './hooks';
import type { ClientOptions } from './types';
/**
* Types of content that can be searched for in Hacker News
*/
export type HackerNewsSearchTag = 'story' | 'comment' | 'poll' | 'pollopt' | 'show_hn' | 'ask_hn' | 'front_page' | string;
/**
* Available numeric fields that can be filtered on
*/
export type HackerNewsNumericField = 'created_at_i' | 'points' | 'num_comments';
/**
* Type of numeric filter operator for search queries
*/
export type NumericFilterOperator = '<' | '<=' | '=' | '>' | '>=';
/**
* Numeric filter configuration for search queries
*/
export interface NumericFilter {
field: HackerNewsNumericField;
operator: NumericFilterOperator;
value: number;
}
/**
* Options for the searchStories function
*/
export interface SearchOptions {
/**
* Tags to filter search results by
*/
tags?: HackerNewsSearchTag[];
/**
* Numeric filters to apply to search results
*/
numericFilters?: NumericFilter[];
/**
* Page number to return (0-based)
*/
page?: number;
/**
* Number of results per page
*/
hitsPerPage?: number;
/**
* By default, sorts by relevance. Set to true to sort by date.
*/
sortByDate?: boolean;
/**
* Restrict search to specific attributes (e.g., 'url', 'title')
*/
restrictSearchableAttributes?: string[];
/**
* Author username to filter by
*/
author?: string;
/**
* Story ID to filter comments by
*/
storyId?: number;
}
/**
* Schema for a search result hit from the Algolia API
*/
declare const HackerNewsSearchHitSchema: z.ZodObject<{
objectID: z.ZodString;
title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
author: z.ZodString;
points: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
story_text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
comment_text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
_tags: z.ZodArray<z.ZodString, "many">;
created_at: z.ZodString;
created_at_i: z.ZodNumber;
num_comments: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
}, "strip", z.ZodTypeAny, {
created_at_i: number;
objectID: string;
author: string;
_tags: string[];
created_at: string;
url?: string | null | undefined;
title?: string | null | undefined;
points?: number | null | undefined;
num_comments?: number | null | undefined;
story_text?: string | null | undefined;
comment_text?: string | null | undefined;
}, {
created_at_i: number;
objectID: string;
author: string;
_tags: string[];
created_at: string;
url?: string | null | undefined;
title?: string | null | undefined;
points?: number | null | undefined;
num_comments?: number | null | undefined;
story_text?: string | null | undefined;
comment_text?: string | null | undefined;
}>;
/**
* Schema for Algolia search response
*/
declare const HackerNewsSearchResponseSchema: z.ZodObject<{
hits: z.ZodArray<z.ZodObject<{
objectID: z.ZodString;
title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
author: z.ZodString;
points: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
story_text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
comment_text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
_tags: z.ZodArray<z.ZodString, "many">;
created_at: z.ZodString;
created_at_i: z.ZodNumber;
num_comments: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
}, "strip", z.ZodTypeAny, {
created_at_i: number;
objectID: string;
author: string;
_tags: string[];
created_at: string;
url?: string | null | undefined;
title?: string | null | undefined;
points?: number | null | undefined;
num_comments?: number | null | undefined;
story_text?: string | null | undefined;
comment_text?: string | null | undefined;
}, {
created_at_i: number;
objectID: string;
author: string;
_tags: string[];
created_at: string;
url?: string | null | undefined;
title?: string | null | undefined;
points?: number | null | undefined;
num_comments?: number | null | undefined;
story_text?: string | null | undefined;
comment_text?: string | null | undefined;
}>, "many">;
page: z.ZodNumber;
nbHits: z.ZodNumber;
nbPages: z.ZodNumber;
hitsPerPage: z.ZodNumber;
processingTimeMS: z.ZodNumber;
query: z.ZodString;
}, "strip", z.ZodTypeAny, {
hits: {
created_at_i: number;
objectID: string;
author: string;
_tags: string[];
created_at: string;
url?: string | null | undefined;
title?: string | null | undefined;
points?: number | null | undefined;
num_comments?: number | null | undefined;
story_text?: string | null | undefined;
comment_text?: string | null | undefined;
}[];
page: number;
nbHits: number;
nbPages: number;
hitsPerPage: number;
processingTimeMS: number;
query: string;
}, {
hits: {
created_at_i: number;
objectID: string;
author: string;
_tags: string[];
created_at: string;
url?: string | null | undefined;
title?: string | null | undefined;
points?: number | null | undefined;
num_comments?: number | null | undefined;
story_text?: string | null | undefined;
comment_text?: string | null | undefined;
}[];
page: number;
nbHits: number;
nbPages: number;
hitsPerPage: number;
processingTimeMS: number;
query: string;
}>;
/**
* Type for a search result hit from the Algolia API
*/
export type HackerNewsSearchHit = z.infer<typeof HackerNewsSearchHitSchema>;
/**
* Type for the Algolia search response
*/
export type HackerNewsSearchResponse = z.infer<typeof HackerNewsSearchResponseSchema>;
/**
* Creates a search client with configurable fetch implementation
* @param options Client options including optional fetch function and AbortSignal
* @returns Object containing all HackerNews search API methods
*
* Example with AbortSignal:
* ```ts
* // Create an AbortController to cancel requests when needed
* const controller = new AbortController();
* const client = createSearchClient({ signal: controller.signal });
*
* // Later, to cancel all in-flight requests:
* controller.abort();
* ```
*/
export declare const createSearchClient: ({ limiter, fetch: _fetch, retry, signal, hooks, }?: ClientOptions) => {
searchStories: (query: string, options?: SearchOptions) => Promise<HackerNewsSearchResponse>;
getFrontPageStories: (options?: Omit<SearchOptions, "tags">) => Promise<HackerNewsSearchResponse>;
invalidateSearchCache: (query: string, options?: SearchOptions) => void;
clearSearchCache: () => void;
/**
* Register hooks with the search client
* @param hooks Object with beforeFetch, afterFetch, and/or onError functions
* @returns The client instance for chaining
*
* Example:
* ```ts
* searchClient.use({
* beforeFetch: (url, opts) => {
* console.log(`Searching ${url}`);
* return { url, options: opts };
* },
* afterFetch: (response) => {
* console.log(`Search returned ${response.status}`);
* return response;
* },
* onError: (err) => {
* console.error(`Error in search: ${err.message}`);
* }
* });
* ```
*/
use: (hooks: ClientHooks) => /*elided*/ any;
/**
* Clear all registered hooks
* @returns The client instance for chaining
*/
clearHooks: () => /*elided*/ any;
};
export declare const searchStories: (query: string, options?: SearchOptions) => Promise<HackerNewsSearchResponse>, getFrontPageStories: (options?: Omit<SearchOptions, "tags">) => Promise<HackerNewsSearchResponse>, invalidateSearchCache: (query: string, options?: SearchOptions) => void, clearSearchCache: () => void;
export {};