hacker-news-reel
Version:
A lightweight, typed client for the Hacker News API with validation using Zod
192 lines (149 loc) • 8.36 kB
Markdown
# Hacker News Reel
[![License][license-badge]][license]
[](https://www.npmjs.com/package/hacker-news-reel)
[](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