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

@erikt/framework-node

v0.0.1

Published

Runs a `fetch` handler on Node's `node:http`, and implements `framework`'s `FileStore` port against the filesystem. This is the only package in the repo, besides the tests, that may import `node:*`.

Downloads

29

Readme

@erikt/framework-node

Runs a fetch handler on Node's node:http, and implements framework's FileStore port against the filesystem. This is the only package in the repo, besides the tests, that may import node:*.

It is also the reference for the two adapter ports: writing the same pair for Deno, Bun, Workers or Vercel is documented in ADAPTERS.md.

import { createApp, defineRoute } from '@erikt/framework'
import { serve } from '@erikt/framework-node'

const app = createApp()

app.get('/', defineRoute(() => 'hello'))

const server = await serve(app, { port: 3000 })

console.log(`listening on ${server.url}`)

await server.close()

serve(handler, options?)

handler is anything with a fetch(request): Response | Promise<Response> method — this framework's app, or any other WinterTC-style handler. The adapter does not depend on framework.

| Option | Default | | | --- | --- | --- | | port | 3000 | 0 binds an ephemeral port; read the real one from the handle | | hostname | 'localhost' | |

Resolves once the server is listening, with a handle:

| | | | --- | --- | | url | e.g. http://localhost:3000 | | hostname, port | The bound address — port is resolved, not the requested 0 | | close() | Closes the server and its open connections |

nodeStore(dir, options?)

The filesystem implementation of framework's FileStore. framework cannot read a directory, so it defines the port and this fills it in: walk dir, import a module from it, read a file out of it.

import { createApp, fileRouter } from '@erikt/framework'
import { nodeStore, serve } from '@erikt/framework-node'

const app = createApp()
const store = nodeStore(new URL('./', import.meta.url))

app.plugin(fileRouter({ store, dir: 'routes' }))

await serve(app, { port: 3000 })

One store per app; each feature scopes itself with its own dir, so the same store can back routes, layouts and assets.

dir is a file: URL or a path resolved against the working directory. A URL built from import.meta.url is the portable form — it does not depend on where the process was started.

| Option | Default | | | --- | --- | --- | | name | 'node' | Identifies the store in error messages |

| Method | | | --- | --- | | list({ prefix, extensions }) | Every file under dir, recursively, as a /-separated relative path. No stat per file, so no size or modified — those come from read | | read(path) | A Response carrying the bytes, content-length and last-modified. null if the file is not there | | import(path) | The module, imported through its file: URL — which is what lets a [id].ts filename load at all |

Both read and import refuse a path that would leave dir, so a traversal attempt is an error rather than a file read.

Reads are whole-file: read buffers the contents rather than streaming them. Fine for route modules and layouts, and worth revisiting when something in framework serves large assets.

Node imports

Four: node:http for the server, and node:fs/promises plus node:path and node:url for nodeStore. Everything else is a web API even where a Node one exists — which is also why read returns a Response rather than a Node stream.

Stream conversion uses the web Streams API rather than node:stream — a ReadableStream with a pull source for the request body, and getReader() for the response — so the Node surface stays as small as the bridge allows. Backpressure is preserved in both directions: pull only reads when the consumer asks, and writes wait for drain.

What the bridge does

  • Request: method, URL (from the Host header), and all headers, preserving duplicates. A body is streamed in for anything other than GET/HEAD. Client disconnects abort request.signal.
  • Response: status, headers, and a streamed body. Multiple set-cookie headers are sent as separate headers rather than joined.
  • Errors: if the handler throws and nothing has been sent yet, the client gets a plain 500.

Limits

HTTP/1.1 over plain TCP only — no TLS, no HTTP/2, no WebSocket upgrade. Add them here if you need them; they do not belong in framework.