Repository navigation
feat(sdk): middleware setup/dispose lifecycle hooks - #702
Merged
Merged
Conversation
`defineMiddleware` gains an object form with optional `setup(ctx)` and `dispose()` around `handle`. `setup` runs once per runtime, lazily, before the first gated request; concurrent first requests share one in-flight call, and a rejection fails only that request and is retried by the next, so a transient outage never poisons the process. `dispose` runs from the fetch core's `close()` after the request drain, which is the path `b4 dev`, `b4 start` and the generated server.mjs all shut down through. The function form is unchanged. The runtime binds a definition once at boot (`bindMiddleware`); route handlers only ever receive the bound handler, so `setup` cannot be bypassed by construction. `selectMiddlewareExport` accepts both shapes so dev and the static manifest bind identically. Closes #683 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
blove
temporarily deployed
to
vercel-preview
September 17, 2026 05:55 — with
GitHub Actions
Inactive
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #683.
What
defineMiddlewarekeeps its function form and gains an object form:setup(ctx)runs at most once per runtime, lazily, before the first gated request. Concurrent first requests share one in-flight call. A rejection fails only the request that awaited it (the existing opaque 500, cause on stderr) and clears the memo, so the next request retries.handlenever runs before a successfulsetup.ctxcarriesappRootand deliberately no shutdownAbortSignal(the fetch core mints signals per request for workerd; the same hazard applies here).dispose()runs once from the fetch core'sclose()after the request drain, which is the shutdown pathb4 dev,b4 startand the generatedserver.mjsall funnel into (SIGTERM/SIGINT). It runs when there is nosetupor after a successful one, never for asetupthat did not run or only ever failed. A rejection is logged and does not wedge shutdown. Hono/edgeapp.mjshas no shutdown hook, so it is documented as not invoked there.How
@b4run/sdk:B4MiddlewarebecomesMiddlewareHandler | MiddlewareDefinition;defineMiddleware<T extends B4Middleware>(m: T): Tis type-preserving, so the function form is source-compatible.@b4run/cli:bindMiddleware(pure, edge-safedev/middleware.ts) folds the lazysetupinto a plain handler and ownsdispose; the fetch core binds once at boot and every route handler dependency is now typedMiddlewareHandler, so a definition cannot reach a request unbound.selectMiddlewareExportand the static-manifest guard accept both shapes so dev and built targets bind identically.disposetable), SDK API page, SDK README. Spec indocs/superpowers/specs/2026-09-16-middleware-lifecycle-hooks-design.md. Patch changeset.Tests
defineMiddleware.test/middleware.test.ts): lazy setup, single-flight concurrency, failure retry withhandleskipped, dispose ordering/idempotence, dispose skipped when setup never ran or only failed, dispose awaiting an in-flight setup, request-after-dispose rejection,selectMiddlewareExporton the object form.test/middleware-lifecycle.test.ts): a real fetch handler proves setup runs on the first gated request (not boot),close()runs dispose once, a failed setup answers 500 and retries, a throwing dispose is logged and does not failclose().handleare rejected as malformed.pnpm lint,pnpm build,pnpm typecheck,node scripts/check-docs.mjsgreen. Fullpnpm test: 9 failures under the parallel load of the whole suite (5 timeouts, the docker smoke watchdog, a spawnETIMEDOUTin the docs inventory, and the stale SEO lastmod manifest, which is regenerated here); each reruns green in isolation.Found in passing, not fixed here
reject(401)with no body has been answered with a 500 since #373:statusResponsecallsResponse.json(undefined), which throws. The docs show body-lessreject(403). Flagged as a separate task.🤖 Generated with Claude Code