feat: add v2 status helper with unified kind formatter#5
feat: add v2 status helper with unified kind formatter#5shwuybot wants to merge 2 commits intozhaoworks:mainfrom
Conversation
📝 WalkthroughWalkthroughThe PR updates the plugin to v2.0.0, introducing a unified formatter (FormatInput) for both status and error responses, adds FastifyReply.status overloads, patches reply.status via an onRequest hook, and restructures README. tsconfig excludes expanded to include dist and node_modules. Changes
Sequence DiagramsequenceDiagram
participant Client as Client (HTTP)
participant Plugin as onRequest Hook
participant Reply as FastifyReply
participant Formatter as format(input)
participant Patch as patchStatusReplyMethod
Client->>Plugin: new request
Plugin->>Patch: patch reply.status
Patch->>Reply: override status method
Client->>Reply: reply.status(code, payload)
Reply->>Formatter: formatStatusPayload -> format({ kind: "status", ... })
Formatter-->>Reply: formatted payload
Reply-->>Client: send response
Client->>Reply: error occurs -> reply.send(error)
Reply->>Formatter: formatErrorPayload -> format({ kind: "error", ... })
Formatter-->>Reply: formatted error
Reply-->>Client: send error response
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (5)
src/index.ts (3)
76-78: Consider typing thethisparameter explicitly.
function (this, status, options)relies on implicitthisinference. Adding an explicit annotation (this: FastifyReply) makes the contract clearer and prevents accidental misuse.♻️ Proposed fix
- fastify.decorateReply('error', function (this, status, options) { + fastify.decorateReply('error', function (this: FastifyReply, status: number, options: ErrorOptions) { return this.status(status).send(formatErrorPayload(status, options, plugin)); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/index.ts` around lines 76 - 78, The anonymous reply decorator passed to fastify.decorateReply should have an explicit this parameter type to avoid implicit this inference—update the function signature used in fastify.decorateReply('error', function (this, status, options) { ... }) to declare this: FastifyReply (importing FastifyReply from 'fastify' if not already) so the decorator implementation and callers have a clear typed contract.
66-66: Parameterpluginshadows its own function name.
async function plugin(…, plugin: PluginOptions)— the second parameter shadows the function declaration. Renaming tooptions(orpluginOptions) improves readability and aligns with the type name.♻️ Suggested rename
-async function plugin(fastify: FastifyInstance, plugin: PluginOptions) { +async function plugin(fastify: FastifyInstance, options: PluginOptions) { fastify.addHook('onRequest', (_, reply, done) => { - patchStatusReplyMethod(reply, plugin); + patchStatusReplyMethod(reply, options); done(); }); fastify.decorateReply('error', function (this, status, options) { - return this.status(status).send(formatErrorPayload(status, options, plugin)); + return this.status(status).send(formatErrorPayload(status, options, options));Hmm — that creates a new conflict with the inner
optionsparameter in thedecorateReplycallback. A cleaner approach:-async function plugin(fastify: FastifyInstance, plugin: PluginOptions) { +async function plugin(fastify: FastifyInstance, pluginOptions: PluginOptions) { fastify.addHook('onRequest', (_, reply, done) => { - patchStatusReplyMethod(reply, plugin); + patchStatusReplyMethod(reply, pluginOptions); done(); }); fastify.decorateReply('error', function (this, status, options) { - return this.status(status).send(formatErrorPayload(status, options, plugin)); + return this.status(status).send(formatErrorPayload(status, options, pluginOptions)); }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/index.ts` at line 66, The parameter name shadows the function name: rename the second parameter in async function plugin(fastify: FastifyInstance, plugin: PluginOptions) to pluginOptions: PluginOptions (or pluginOpts) and update all references inside the function body to use pluginOptions, leaving inner callback parameters (like the decorateReply callback's options) unchanged so no new shadowing/conflict is introduced.
52-64: Monkey-patchingreply.statuson every request — verify this plays well with Fastify's internals.A few observations on this approach:
Per-request patching via
onRequestis the only viable path sincestatus()already exists on the reply prototype and can't be added viadecorateReply. This is a reasonable trade-off.
arguments.length >= 2check (line 58): This meansreply.status(200, undefined)will trigger asend(formatStatusPayload(200, undefined, ...)), which may be surprising. If callers pass an explicitundefined, they'd expect the 1-arg behavior. Consider usingpayload !== undefinedinstead if that intent is preferred.The 2-arg form is terminal — it calls
send()internally (line 59), yet the return type isFastifyReply, which suggests further chaining is possible. This is fine given the documented usage, but worth noting in the JSDoc or README that chaining after the 2-arg call is not supported.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/index.ts` around lines 52 - 64, The patchStatusReplyMethod currently monkey-patches reply.status per request and uses arguments.length to detect a 2-arg call, which makes reply.status(200, undefined) behave like the terminal 2-arg form; update patchStatusReplyMethod to detect the 2-arg terminal case by checking payload !== undefined (i.e., use the payload parameter rather than arguments.length) so explicit undefined behaves like the 1-arg form, and add a short JSDoc comment on patchStatusReplyMethod (or the module README) clarifying that the 2-arg form calls send() and does not support further chaining.README.md (2)
48-58: Consider documenting the default behavior whenformatis omitted.The API section documents the
format(input)hook but doesn't describe what happens when no custom formatter is provided. Users would benefit from knowing the defaults:
reply.error(status, { message })→{ error: { message } }reply.status(status, payload)→payload(passed through as-is)This helps users decide whether they need a custom formatter at all.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` around lines 48 - 58, Update the API docs to describe the default formatting when no custom format(input) is provided: specify that reply.error(status, { message }) results in a response body of { error: { message } } and that reply.status(status, payload) returns payload unchanged; mention the two input shapes format receives ({ kind: 'error', status, error } and { kind: 'status', status, payload }) and state that these defaults apply unless format(input) is implemented.
13-15: Usebashorshfor shell command code blocks.The
apachelanguage identifier is for Apache config syntax highlighting — it won't highlight a shell command correctly.♻️ Suggested fix
-```apache +```bash λ bun add `@zhaoworks/feh` ```🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` around lines 13 - 15, The fenced code block showing the shell command currently uses the incorrect language identifier "apache"; update that fenced block in README.md so the opening triple-backtick specifies "bash" (or "sh") instead of "apache" for the block containing "λ bun add `@zhaoworks/feh`" to enable proper shell syntax highlighting.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@README.md`:
- Around line 48-58: Update the API docs to describe the default formatting when
no custom format(input) is provided: specify that reply.error(status, { message
}) results in a response body of { error: { message } } and that
reply.status(status, payload) returns payload unchanged; mention the two input
shapes format receives ({ kind: 'error', status, error } and { kind: 'status',
status, payload }) and state that these defaults apply unless format(input) is
implemented.
- Around line 13-15: The fenced code block showing the shell command currently
uses the incorrect language identifier "apache"; update that fenced block in
README.md so the opening triple-backtick specifies "bash" (or "sh") instead of
"apache" for the block containing "λ bun add `@zhaoworks/feh`" to enable proper
shell syntax highlighting.
In `@src/index.ts`:
- Around line 76-78: The anonymous reply decorator passed to
fastify.decorateReply should have an explicit this parameter type to avoid
implicit this inference—update the function signature used in
fastify.decorateReply('error', function (this, status, options) { ... }) to
declare this: FastifyReply (importing FastifyReply from 'fastify' if not
already) so the decorator implementation and callers have a clear typed
contract.
- Line 66: The parameter name shadows the function name: rename the second
parameter in async function plugin(fastify: FastifyInstance, plugin:
PluginOptions) to pluginOptions: PluginOptions (or pluginOpts) and update all
references inside the function body to use pluginOptions, leaving inner callback
parameters (like the decorateReply callback's options) unchanged so no new
shadowing/conflict is introduced.
- Around line 52-64: The patchStatusReplyMethod currently monkey-patches
reply.status per request and uses arguments.length to detect a 2-arg call, which
makes reply.status(200, undefined) behave like the terminal 2-arg form; update
patchStatusReplyMethod to detect the 2-arg terminal case by checking payload !==
undefined (i.e., use the payload parameter rather than arguments.length) so
explicit undefined behaves like the 1-arg form, and add a short JSDoc comment on
patchStatusReplyMethod (or the module README) clarifying that the 2-arg form
calls send() and does not support further chaining.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/index.ts (2)
71-74: Consider guardingdoneagainst unexpected throws frompatchStatusReplyMethod.While
patchStatusReplyMethodis trivially safe today, any future change that could throw would silently hang the request sincedone()would never be called. A try-catch (or converting to an async hook) is a cheap safeguard.♻️ Proposed change
- fastify.addHook('onRequest', (_, reply, done) => { - patchStatusReplyMethod(reply, options); - done(); - }); + fastify.addHook('onRequest', async (_, reply) => { + patchStatusReplyMethod(reply, options); + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/index.ts` around lines 71 - 74, Wrap the call to patchStatusReplyMethod inside the onRequest hook in a try-catch (or convert the hook to async) so thrown errors don’t prevent done() from being invoked; specifically, in the fastify.addHook('onRequest', (_, reply, done) => { ... }) handler call patchStatusReplyMethod(reply, options) inside a try block, call done(err) with the caught error in catch, and ensure done() is still invoked (or omitted when using async/await) so the request never hangs.
28-29: Remove the redundant first overload and document the type-narrowing trade-off.The
status(code: number): FastifyReplyoverload on line 28 duplicates Fastify's existing definition. In TypeScript's interface merging, augmented overloads appear first in the merged list, shadowing Fastify 5's genericstatus<Code extends ...>method. This breaks per-status-code reply type narrowing for routes using aReplyschema generic or type provider (e.g.,@fastify/type-provider-typebox).Since this plugin semantically replaces
status(code, payload)behaviour, the type narrowing regression may be acceptable—but should be documented so consumers know not to rely on narrowed reply types after installing this plugin.♻️ Proposed change
declare module 'fastify' { interface FastifyReply { error: (status: number, options: ErrorOptions) => FastifyReply; - status(code: number): FastifyReply; status(code: number, payload: unknown): FastifyReply; } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/index.ts` around lines 28 - 29, Remove the redundant non-generic overload `status(code: number): FastifyReply` from the augmented `FastifyReply` interface (it shadows Fastify 5's generic `status<Code extends ...>` and breaks per-status-code reply type narrowing), keep only the `status(code: number, payload: unknown): FastifyReply` signature that matches the plugin's semantic replacement, and add a short JSDoc/TOP-level comment in the same file noting the trade-off: installing this plugin replaces `status(code, payload)` behavior and prevents Fastify's per-status-code reply type narrowing when using `Reply` schema generics or type providers (e.g., `@fastify/type-provider-typebox`) so consumers are aware.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/index.ts`:
- Around line 71-74: Wrap the call to patchStatusReplyMethod inside the
onRequest hook in a try-catch (or convert the hook to async) so thrown errors
don’t prevent done() from being invoked; specifically, in the
fastify.addHook('onRequest', (_, reply, done) => { ... }) handler call
patchStatusReplyMethod(reply, options) inside a try block, call done(err) with
the caught error in catch, and ensure done() is still invoked (or omitted when
using async/await) so the request never hangs.
- Around line 28-29: Remove the redundant non-generic overload `status(code:
number): FastifyReply` from the augmented `FastifyReply` interface (it shadows
Fastify 5's generic `status<Code extends ...>` and breaks per-status-code reply
type narrowing), keep only the `status(code: number, payload: unknown):
FastifyReply` signature that matches the plugin's semantic replacement, and add
a short JSDoc/TOP-level comment in the same file noting the trade-off:
installing this plugin replaces `status(code, payload)` behavior and prevents
Fastify's per-status-code reply type narrowing when using `Reply` schema
generics or type providers (e.g., `@fastify/type-provider-typebox`) so consumers
are aware.
Summary\n- introduce a v2 status helper\n- unify kind formatting in one place\n- keep status rendering consistent across outputs\n\n## Notes\n- branch: \n- commit: f805ec1
Summary by CodeRabbit
New Features
Documentation
Chores