npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jeremy46231/ferry

v0.4.0

Published

Framework-agnostic library that automates the Hack Club YSWS submission flow.

Readme

Ferry

Framework-agnostic library that automates the Hack Club YSWS submission flow. Mount it at /submit/* on any server-side route, feed it Requests, return the Responses it hands back. It handles Hack Club Auth, Hackatime, Airtable, and the Fillout hand-off.

See DESIGN.md for the architecture and decisions.

Status: the full flow works end to end — Hack Club Auth → eligibility → Airtable upsert → Hackatime (reuse-or-connect) → project sync → Fillout. Published as @jeremy46231/ferry. See DESIGN.md § Open items.

Usage

npm i @jeremy46231/ferry
import { createFerry } from '@jeremy46231/ferry'

const ferry = createFerry() // reads FERRY_* env; pass overrides to createFerry({...})

// in any server route mounted at /submit/*
const res = await ferry.handle(request)
if (res) return res
// null -> not a Ferry route -> respond 404

handle(request) never throws: misconfiguration and internal errors come back as an error Response (and a console.error), and non-Ferry paths return null. It depends only on Web-standard APIs (fetch, crypto.subtle, …), so the same build runs on Node 18+, Bun, Deno, Cloudflare Workers, and edge.

Configuration

Config comes from FERRY_* environment variables, overridable via createFerry({ ... }). See .env.example for the full list; the essentials:

| Variable | Notes | | --------------------------------------------------- | ------------------------------------------------------------------------------- | | FERRY_SECRET | Master secret, ≥32 chars (openssl rand -hex 32). Derives all encryption keys. | | FERRY_BASE_URL | Public origin, for building OAuth redirect URIs. | | FERRY_HCA_CLIENT_ID / _SECRET | Hack Club Auth OAuth app. | | FERRY_HACKATIME_MODE | required or off. | | FERRY_HACKATIME_CLIENT_ID / _SECRET | Hackatime OAuth app (when not off). | | FERRY_AIRTABLE_API_KEY / FERRY_AIRTABLE_BASE_ID | Airtable token + base. | | FERRY_FILLOUT_FORM_URL | Where submitters are sent to finish. | | FERRY_EVENT_START_DATE | Optional YYYY-MM-DD; scopes Hackatime time to on/after this date. |

Register your OAuth callbacks at <FERRY_BASE_URL><basePath>/hca and .../hackatime (default basePath is /submit).

Every route is one segment under basePath/submit (or /submit/start), /submit/hca, /submit/hackatime — and never nested. That means a host with only single-segment dynamic routing can serve the whole flow from one file; see sandbox/vercel.

Development

bun install
bun run test        # vitest
bun run typecheck   # tsc (library + tests)
bun run build       # tsdown -> dist/ (ESM + CJS + types)
bun run format      # biome (write)
bun run check       # biome lint + format + import sorting (check only)
bun run check:fix   # biome, with autofixes applied

Integration sandboxes live in sandbox/ — one per host (vercel, vite, sveltekit, nextjs, workers), each serving Ferry on port 5173. Put your dev creds in a repo-root .env (copy .env.example), then:

bun run sandbox:setup      # symlink .env into each sandbox and install
bun run sandbox:vercel     # run one (also :vite, :sveltekit, :nextjs, :workers)

sandbox/vercel is the reference setup for a typical YSWS site: a static Vite build with Ferry as a Vercel Function.

sandbox:setup runs each sandbox's own setup script, which symlinks the root .env to that host's env file (.env / .env.local / .dev.vars) — so edits to the root .env are picked up on the next server start, no re-setup needed — and installs. The sandboxes also import Ferry's src/ directly, so library edits are picked up live too — no build or publish step. See each sandbox's README.md.

Wiring Ferry into a host is a line or two:

| Host | Integration | | ------------------------------- | ---------------------------------------------------------------------- | | Vercel Function (api/…) | (await ferry.handle(request)) ?? new Response(null, { status: 404 }) | | Node (Vite/Express/Connect) | server.middlewares.use(ferry.middleware()) | | SvelteKit (hooks.server.ts) | (await ferry.handle(event.request)) ?? resolve(event) | | Next.js (catch-all route) | (await ferry.handle(request)) ?? new Response(null, { status: 404 }) | | Cloudflare Workers (fetch) | (await ferry.handle(request)) ?? env.ASSETS.fetch(request) |

The mount path is configurable: basePath drives both inbound matching and the OAuth redirect URIs Ferry builds, so set it to wherever the host mounts you (e.g. /api/submit on Vercel) and register the callbacks to match.

Web-native hosts call handle(request) directly; Node servers use the middleware() adapter. Runtimes without process.env (Workers) pass the env bag: createFerry({ env }).