hono-wait-until provides a waitUntil helper + middleware that make background/async work survive after the response returns.
It automatically falls through to the platform's native waitUntil (Cloudflare Workers, Deno, Bun, Netlify, Vercel Edge, ...) via c.executionCtx.waitUntil when available — no shim, no blocking.
On runtimes without a native waitUntil (Node, AWS Lambda, ...), it shims one: the middleware collects all wrapped promises and blocks until they settle before returning the response, so the platform won't kill your app with uncompleted async tasks.
# npm
npm install hono-wait-until
# yarn
yarn add hono-wait-until
# pnpm (recommended)
pnpm install hono-wait-untilimport type { WaitUntilList } from 'hono-wait-until'
import {
waitUntil, // Optional helper function instead of accessing via context
waitUntilMiddleware,
} from 'hono-wait-until'
const app = new Hono<{ Variables: { waitUntilList: WaitUntilList } }>()
// Preferably, use the waitUntilMiddleware as early as you can.
.use(waitUntilMiddleware())
.get('/context', async (c) => {
const waitUntilList = c.get('waitUntilList')
waitUntilList.waitUntil(sleep(300))
return c.text(`Using waitUntil via context variable`)
})
.get('/helper', async (c) => {
waitUntil(sleep(300), c)
return c.text(`Using waitUntil via helper function`)
})If any of the wrapped async tasks rejects, the middleware logs the errors and responds with a 500 (Some async tasks were rejected).
On platforms that expose a native execution-context waitUntil (Cloudflare Workers, Deno, Bun, Netlify, Vercel Edge, ...), both waitUntil() and waitUntilMiddleware() delegate straight to c.executionCtx.waitUntil:
waitUntil()works without the middleware.- The middleware becomes a no-op (it will not block, since the runtime already keeps execution alive and reports errors itself).
On runtimes without native waitUntil (Node, AWS Lambda, ...), the middleware must be applied and will block until every wrapped task settles.
Pass { continueWithoutSettled: true } to waitUntilMiddleware to respond immediately without blocking until all async tasks settle. This effectively disables the middleware and is useful when you have migrated to a platform that supports background async tasks, but want a test run without removing the waitUntil wrappers:
app.use(waitUntilMiddleware({ continueWithoutSettled: true }))Releases are version-first and dispatched by hand: one workflow run does the whole release,
so a git push on its own never publishes anything.
- Go to Actions → Release → Run workflow and give it the version to ship, e.g.
2.2.0. .github/workflows/release.ymlverifies the version, lints/type-checks/tests, then lets changelogen write the changelog, bumppackage.json, commit and tagv<version>. It pushes that commit and tag, creates the GitHub release, and publishes to npm with a short-lived OIDC token and--provenance.
Tick dry-run to do everything up to the commit and stop there — nothing is written back.
Locally, pnpm run release:check <version> validates a version against package.json, and
pnpm run release:preview prints the changelog the next release would get.
One-time setup: publish the package once by hand (npm only offers a trusted publisher for a
package that already exists), then on npmjs.com enable Settings → Publishing access → Trusted
Publishing for NamesMT/hono-wait-until with the workflow filename release.yml.
- Become the legendary 10000x developer