← Public packages
@kody/hn-pulse
Quick onboarding example: Hacker News top stories via Firebase HN API (zero OAuth).
src/index.ts
74 lines · 2.1 KB · TypeScriptexport type TopStoriesInput = {
/** How many top stories to return (1–30, default 10). */
limit?: number
}
export type HnStory = {
id: number
title: string
url: string | null
by: string | null
score: number | null
descendants: number | null
}
function clampLimit(limit: unknown): number {
const n = Number(limit)
if (!Number.isFinite(n)) return 10
return Math.min(30, Math.max(1, Math.floor(n)))
}
/**
* Fetch current Hacker News top stories (Firebase HN API, no auth).
* Prefer the `./getTopStories` export from callers.
*
* @param input.limit - How many top stories to return (1–30, default 10).
* @returns Hydrated HN stories newest/top ranked.
*
* @example
* import { getTopStories } from 'kody:@kody/hn-pulse/getTopStories'
* const stories = await getTopStories({ limit: 10 })
*/
export async function getTopStories(
input: TopStoriesInput = {},
): Promise<HnStory[]> {
const limit = clampLimit(input?.limit)
const idsRes = await fetch(
'https://hacker-news.firebaseio.com/v0/topstories.json',
)
const ids = await idsRes.json().catch(() => null)
if (!idsRes.ok || !Array.isArray(ids) || ids.length === 0) {
throw new Error('Failed to load Hacker News topstories.json')
}
const slice = ids.slice(0, limit)
const items = await Promise.all(
slice.map(async (id: number) => {
const res = await fetch(
'https://hacker-news.firebaseio.com/v0/item/' + id + '.json',
)
const item = await res.json().catch(() => null)
if (!res.ok || !item || typeof item !== 'object') {
return null
}
const story: HnStory = {
id: Number(item.id),
title: String(item.title || '(untitled)'),
url: item.url ? String(item.url) : null,
by: item.by ? String(item.by) : null,
score: Number.isFinite(Number(item.score)) ? Number(item.score) : null,
descendants: Number.isFinite(Number(item.descendants))
? Number(item.descendants)
: null,
}
return story
}),
)
return items.filter((story): story is HnStory => story !== null)
}
/** Root dispatcher for packages.invoke({ exportName: '.', params }). */
export default async function hnPulse(input: TopStoriesInput = {}) {
return getTopStories(input)
}