Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: add v2 status helper with unified kind formatter#5

Open
shwuybot wants to merge 2 commits intozhaoworks:mainfrom
shwuybot:feat/v2
Open

feat: add v2 status helper with unified kind formatter#5
shwuybot wants to merge 2 commits intozhaoworks:mainfrom
shwuybot:feat/v2

Conversation

@shwuybot
Copy link

@shwuybot shwuybot commented Feb 21, 2026

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

    • Unified formatter for both status and error responses
    • reply.status now supports code-only and code+payload overloads
  • Documentation

    • Reorganized, simplified docs with Features/Installation/Usage flow
    • Added API section describing the unified formatter and examples
  • Chores

    • Version bumped to 2.0.0
    • Updated package description to reflect response helper focus

@coderabbitai
Copy link

coderabbitai bot commented Feb 21, 2026

📝 Walkthrough

Walkthrough

The 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

Cohort / File(s) Summary
Documentation Restructure
README.md
Rewrote and reorganized docs: new header, Features/Installation/Usage flow, moved examples and added API section describing format(input) unified formatter.
Package Metadata
package.json
Bumped version to 2.0.0 and updated description to reflect Fastify response helper for status and error replies.
Core Plugin Logic
src/index.ts
Added FormatInput discriminated union and format?: (input: FormatInput) => unknown option; implemented formatErrorPayload/formatStatusPayload, patchStatusReplyMethod, onRequest hook to patch reply.status, and FastifyReply status overloads.
TypeScript Configuration
tsconfig.json
Expanded exclude to include dist and node_modules in addition to src/server.ts.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I patched the status, with a hop and a grin,
One format to rule errors and wins.
From README to types, the changes all play,
A unified hop, to brighten the day. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: introducing a v2 status helper with a unified formatter that consolidates formatting logic for consistent response handling across the API.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (5)
src/index.ts (3)

76-78: Consider typing the this parameter explicitly.

function (this, status, options) relies on implicit this inference. 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: Parameter plugin shadows its own function name.

async function plugin(…, plugin: PluginOptions) — the second parameter shadows the function declaration. Renaming to options (or pluginOptions) 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 options parameter in the decorateReply callback. 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-patching reply.status on every request — verify this plays well with Fastify's internals.

A few observations on this approach:

  1. Per-request patching via onRequest is the only viable path since status() already exists on the reply prototype and can't be added via decorateReply. This is a reasonable trade-off.

  2. arguments.length >= 2 check (line 58): This means reply.status(200, undefined) will trigger a send(formatStatusPayload(200, undefined, ...)), which may be surprising. If callers pass an explicit undefined, they'd expect the 1-arg behavior. Consider using payload !== undefined instead if that intent is preferred.

  3. The 2-arg form is terminal — it calls send() internally (line 59), yet the return type is FastifyReply, 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 when format is 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: Use bash or sh for shell command code blocks.

The apache language 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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/index.ts (2)

71-74: Consider guarding done against unexpected throws from patchStatusReplyMethod.

While patchStatusReplyMethod is trivially safe today, any future change that could throw would silently hang the request since done() 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): FastifyReply overload 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 generic status<Code extends ...> method. This breaks per-status-code reply type narrowing for routes using a Reply schema 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant