UNPKG

hacker-news-reel

Version:

A lightweight, typed client for the Hacker News API with validation using Zod

192 lines (149 loc) 8.36 kB
# Hacker News Reel [![License][license-badge]][license] [![npm version](https://img.shields.io/npm/v/hacker-news-reel.svg)](https://www.npmjs.com/package/hacker-news-reel) [![CI](https://github.com/stevekinney/hacker-news-reel/actions/workflows/ci.yml/badge.svg)](https://github.com/stevekinney/hacker-news-reel/actions/workflows/ci.yml) A lightning-fast, type-safe Hacker News API client with built-in Zod validation—because guessing is overrated. Built for modern TypeScript, Node.js, Deno, and Bun. ## 🚀 Features - **Type-safe**: Zod schemas validate everything at compile time - **Promise-based** with optional parallel fetching - **In-memory caching** (`stale-while-revalidate`) to keep your app snappy - **Automatic rate-limiting** & retry with exponential backoff - **Recursive comment-tree fetching** with configurable depth & concurrency - **Algolia-powered search** (front page & full-text) - **Extensible**: swap out `fetch` for any environment - **Bundled Types**: Full TS types and schemas exported for your IDE ## 💾 Installation ```bash npm install hacker-news-reel # or yarn add hacker-news-reel # or bun add hacker-news-reel ``` ## ⚡ Quick Start ```ts import { createClient } from 'hacker-news-reel'; const client = createClient({ retry: { maxRetries: 5, initialBackoff: 500 }, }); // Get top stories const topIds = await client.getTopStories(); const story = await client.getItemWithComments(topIds[0], { maxDepth: 2 }); console.log(`1. ${story.title} — ${story.url}`); story.comments.forEach((c, i) => { console.log(`${i + 1}. ${c.by}: ${c.text}`); }); ``` ## 🧰 API Reference ### Client Creation ```ts import { createClient } from 'hacker-news-reel'; const client = createClient({ fetch: myFetchImpl, // defaults to global fetch retry: false | { maxRetries: number; initialBackoff: number }, }); ``` ### Fetching Stories & Comments | Method | Description | |:-------------------------------|:-----------------------------------------------------| | `getTopStories()` | IDs of top stories | | `getNewStories()` | IDs of newest stories | | `getBestStories()` | IDs of best-ranked stories | | `getAskStories()` | IDs of Ask HN stories | | `getShowStories()` | IDs of Show HN stories | | `getJobStories()` | IDs of job listings | | `getItem(id)` | Fetch a story/comment/job by ID | | `getItems(ids, concurrency?)` | Batch fetch (optional concurrency limit) | | `getUser(username)` | Fetch a user profile | | `getItemWithComments(id, opts?)`| Story + nested comments (`maxDepth`, `concurrency`) | ### Search API ```ts import { createSearchClient } from 'hacker-news-reel'; const search = createSearchClient({ limiter: { /* Bottleneck options */ }, fetch: customFetch, retry: { maxRetries: 3, initialBackoff: 300 }, }); const results = await search.searchStories('typescript', { hitsPerPage: 10 }); console.log(`Found ${results.nbHits} hits.`); ``` ### Error Handling All rate limits throw a `RateLimitError`: ```ts import { RateLimitError } from 'hacker-news-reel'; try { await client.getNewStories(); } catch (err) { if (err instanceof RateLimitError) { console.warn(`Rate limited! Retry after ${err.retryAfterSeconds}s.`); } else { throw err; } } ``` ## 🛡️ Caching & Rate Limiting | Resource | Fresh (maxAge) | Stale (`staleWhileRevalidate`) | Max Entries | |:---------|:-------------:|:----------------------------:|:-----------:| | Items | `5 min` | `1 h` | 2000 | | Lists | `30 s` | `2 min` | 20 | | Users | `5 min` | `30 min` | 500 | | Search | `30 s` | `2 min` | 500 | Uses LRU eviction and [Bottleneck](https://npm.im/bottleneck) to throttle Algolia (~2.7 req/s) in order to make sure we don't run into the 10,000/hour rate limit. ## 🧠 Advanced Usage ### Cache Invalidation Manually solve one of the hardest problems in computer science. ```ts client.invalidateItemCache(id); client.clearAllCaches(); ``` ### Hooks Add custom hooks for request/response lifecycle: ```ts client.use({ beforeFetch: (url, opts) => { // Add headers, log request, start timer console.log(`Fetching: ${url}`); const headers = { ...opts?.headers, 'x-custom-header': 'value' }; return { url, options: { ...opts, headers } }; }, afterFetch: (response) => { // Record metrics, inspect responses console.log(`Response: ${response.status} from ${response.url}`); return response; }, onError: (err) => { // Handle or transform errors console.error(`Error: ${err.message}`); // Return a new error to replace the original return new Error(`Wrapped: ${err.message}`); } }); ``` ## 📊 API Reference ### Zod Schemas | Schema Name | Description | |:---------------------------|:--------------------------------------------------------------------------------------------| | `HackerNewsIdSchema` | Validates a Hacker News item ID (number, integer, non-negative) | | `HackerNewsUsernameSchema` | Validates a Hacker News username (non-empty string) | | `HackerNewsIdListSchema` | Validates an array of Hacker News item IDs | | `HackerNewsItemTypeSchema` | Validates item types (`'job'`, `'story'`, `'comment'`, `'poll'`, `'pollopt'`) | | `HackerNewsItemSchema` | Validates complete Hacker News items (stories, comments, etc.) | | `HackerNewsUserSchema` | Validates Hacker News user profiles | | `HackerNewsUpdatesSchema` | Validates the updates endpoint response (changed items and profiles) | ### TypeScript Types | Type Name | Description | |:------------------------|:------------------------------------------------------------------------------------------------------| | `FetchType` | Type alias for the Fetch API | | `FetchParameters` | Parameters type for fetch function | | `RequestInfo` | Type for fetch request info (URL or string) | | `RequestInit` | Type for fetch request init options | | `HackerNewsId` | Type alias for a Hacker News item ID (number) | | `HackerNewsUsername` | Type alias for a Hacker News username (string) | | `HackerNewsIdList` | Type alias for an array of Hacker News item IDs | | `HackerNewsItemType` | Union type for item types (`'job'`, `'story'`, `'comment'`, `'poll'`, `'pollopt'`) | | `HackerNewsItem` | Interface for Hacker News items (stories, comments, etc.) | | `HackerNewsCommentTree` | Extended HackerNewsItem with nested replies for comment trees | | `HackerNewsUser` | Interface for Hacker News user profiles | | `HackerNewsUpdates` | Interface for updates endpoint response | [license-badge]: https://img.shields.io/npm/l/hacker-news-reel.svg [license]: https://opensource.org/licenses/MIT