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

@theoven/image

v0.6.1

Published

Image processing for Oven on Bun.Image — resize, convert and guard user uploads with no dependencies

Downloads

287

Readme

@theoven/image

Resize, convert and guard images on Bun.Image. No native module, nothing to install.

bun add @theoven/image
import { image } from '@theoven/image'

const app = createApp().use(image({ format: 'webp' }))
// src/routes/avatar.post.ts
export default async (ctx) => {
  const [file] = (await ctx.files()).avatar
  if (!file) throw new BadRequest('Expected an "avatar" file.')

  const avatar = await ctx.image.transform(file, { width: 256, height: 256 })
  await ctx.storage.upload(`avatars/${ctx.user.id}.webp`, avatar.bytes)
  return { ok: true }
}

Why a brick rather than calling Bun.Image directly

Because of the part that is easy to leave out: refusing an image before decoding it.

A 70 KB PNG can carry 4000×4000 pixels. Nothing about the request looks unusual — 70 KB is nothing — but decoding it costs 16 megapixels of memory, and a handful arriving together will end a small server. A byte limit cannot see it coming.

Reading the header first costs 0.6 µs against 44 ms for the decode, so the guard is effectively free:

| source | on the wire | header read | resize + encode | | --- | ---: | ---: | ---: | | 800×600 | 3 KB | 0.8 µs | 3.8 ms | | 2000×1500 | 15 KB | 0.4 µs | 7.7 ms | | 4000×4000 | 70 KB | 0.6 µs | 44.4 ms |

Header reads are constant time; decoding is linear in pixels. The bigger the attack, the wider the margin.

The brick also refuses to upscale by default — Bun.Image will happily render a 200px upload at 8000px if asked — and applies your format and quality defaults so they are not restated at every call site.

Portability, which will bite you otherwise

Bun.Image runs on two backends. system is the default on macOS and Windows; bun is what every Linux server uses.

| format | system | bun (Linux) | | --- | --- | --- | | JPEG, PNG, WebP | yes | yes — byte-identical | | AVIF, HEIC | yes | no |

So AVIF works on a Mac and fails on the server. This brick checks the configured format at boot and refuses to start with a message naming the problem, rather than letting you find out when a user uploads something.

Set backend: 'bun' in development to get exactly what production will produce.

Honest limits

  • AVIF and HEIC cannot be produced on Linux. Not a limitation we can fix.
  • variants() decodes once per width — a 320/640/1280 ladder is ~83 ms. That is a queue job, not a request.
  • No cropping, compositing, text, or colour management. Bun.Image does resize, rotate, flip and brightness/saturation. Anything more is a different tool.
  • No SVG. It is not a raster format and is a script-execution surface besides.

Image work does not block the event loop — a 110 ms resize still served 87 timer ticks — so a resize in a handler costs that request its latency, not the process's.

Full documentation: https://theoven.app/docs/bricks/image/

MIT