Skip to content

Commit 167cebe

Browse files
committed
fix(hono): createHonoApp's two catch arms render a declared ADR-0112 envelope through #16545's recogniser (#23365)
Both arms read statusCode alone, so a throw declaring status and a registered code (a missing record's 404, the /data version refusals' 409 and 400, an authorization-store outage's 503) answered 500 INTERNAL_ERROR. They now ask declaredEnvelopeForThrow, exported from @objectstack/plugin-hono-server for this, and keep their unchanged answer for every other throw. Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU Co-authored-by: Claude <noreply@anthropic.com>
1 parent 84672ff commit 167cebe

2 files changed

Lines changed: 48 additions & 3 deletions

File tree

‎packages/adapters/hono/src/index.ts‎

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {
4444
hasWildcardPattern,
4545
DEFAULT_CORS_ALLOW_HEADERS,
4646
DEFAULT_CORS_EXPOSE_HEADERS,
47+
declaredEnvelopeForThrow,
4748
} from '@objectstack/plugin-hono-server';
4849

4950
export interface ObjectStackHonoCorsOptions {
@@ -508,6 +509,33 @@ export function createHonoApp(options: ObjectStackHonoOptions): Hono {
508509
return c.json({ success: false, error: { code, message } }, status);
509510
};
510511

512+
/**
513+
* [#23365] What BOTH catch arms below answer for a throw that escaped them.
514+
*
515+
* A throw that carries a declared ADR-0112 `status` and a registered `code`
516+
* is answered as that envelope. Both arms used to read `statusCode` alone, so
517+
* a declared `status` and `code` were lost: a missing record's
518+
* `404 RECORD_NOT_FOUND`, the `/data` version refusals' `409` and `400`, and
519+
* an authorization-store outage's `503` all left `dispatch()` and answered
520+
* `500 INTERNAL_ERROR` on this host only.
521+
*
522+
* The recognition is `declaredEnvelopeForThrow`, the ONE rule #16545 landed
523+
* for `@objectstack/plugin-hono-server`'s direct-mount routes, imported and
524+
* ⛔ never restated here, so the two Hono doors answer one throw alike: the
525+
* same two conditions, the same 5xx disclosure filter, and the same body
526+
* (`details` and `userMessage` only when the producer declared them).
527+
*
528+
* A throw the recogniser does not accept takes the arm's answer exactly as
529+
* before: a bare `Error` is still `500` carrying `code` and `message`, with
530+
* no `cause`, and a throw declaring `statusCode` with no registered code
531+
* keeps that status.
532+
*/
533+
const thrownJson = (c: any, err: any) => {
534+
const declared = declaredEnvelopeForThrow(err);
535+
if (declared) return c.json(declared.body, declared.status);
536+
return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);
537+
};
538+
511539
const toResponse = (c: any, result: HttpDispatcherResult) => {
512540
if (result.handled) {
513541
if (result.response) {
@@ -779,7 +807,11 @@ export function createHonoApp(options: ObjectStackHonoOptions): Hono {
779807
if (!result.handled) return yieldUnowned(c, next, () => toResponse(c, result));
780808
return toResponse(c, result);
781809
} catch (err: any) {
782-
return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);
810+
// [#23365] The same rendering as the catch-all below (`thrownJson`). No
811+
// in-repo producer was found that reaches this arm with a declared
812+
// envelope (better-auth answers its own failures as a `Response`), so it
813+
// takes the rule for uniformity: one rule for both arms, not two.
814+
return thrownJson(c, err);
783815
}
784816
});
785817

@@ -828,7 +860,9 @@ export function createHonoApp(options: ObjectStackHonoOptions): Hono {
828860
const result = await dispatcher.dispatch(method, subPath, body, queryParams, { request: c.req.raw }, prefix);
829861
return toResponse(c, result);
830862
} catch (err: any) {
831-
return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);
863+
// [#23365] A declared ADR-0112 envelope that left `dispatch()` (its foot
864+
// catch re-raises everything but a denial) is answered as declared.
865+
return thrownJson(c, err);
832866
}
833867
});
834868

‎packages/plugins/plugin-hono-server/src/adapter.ts‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,19 @@ function toLoggableError(thrown: unknown): Error {
280280
* producer's spelling IS the registered member sitting in `code`, so
281281
* `demotedDeclaredCode` returns `undefined` by construction — forwarding it
282282
* would put two spellings of one fact on every envelope this seam renders.
283+
*
284+
* ## Exported for the other Hono door (#23365)
285+
*
286+
* `@objectstack/hono`'s `createHonoApp` is a second Hono door: it builds its own
287+
* app, and its two catch arms (the auth mount's and the `dispatch()`
288+
* catch-all's) read `statusCode` alone, so a declared `status` and `code` were
289+
* lost there and the answer was `500`. Those arms now ask THIS function, so the
290+
* two doors answer one throw alike. It is exported so that they can, and ⛔ it
291+
* is not restated there: a second recogniser is how the doors would start
292+
* disagreeing about what counts as declared. Each caller keeps its own
293+
* fallback arm for the `undefined` answer.
283294
*/
284-
function declaredEnvelopeForThrow(thrown: unknown): {
295+
export function declaredEnvelopeForThrow(thrown: unknown): {
285296
status: number;
286297
body: { success: false; error: Record<string, unknown> };
287298
} | undefined {

0 commit comments

Comments
 (0)