Feat/centralized error handling#112
Merged
josephchimebuka merged 12 commits intoTevaLabs:mainfrom Mar 25, 2026
Merged
Conversation
Introduces AppError base class and typed subclasses: ValidationError (400), AuthenticationError (401), AuthorizationError (403), NotFoundError (404), ConflictError (409), BusinessRuleError (422), ExternalServiceError (503), ConfigurationError (500). Closes TevaLabs#92 Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
errorHandler maps AppError subclasses, Prisma known errors (P2025→404, P2002→409), and unknown errors to consistent HTTP responses with structured logging. Also exports asyncHandler for zero-boilerplate async route wrappers. Closes TevaLabs#92 Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Instead of responding directly, the middleware now calls next(new ValidationError(...)) so the centralized error handler owns all error responses. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Replaces ad-hoc error object with error.code = 'ACTIVE_ROUND_EXISTS' with a typed ConflictError so the centralized handler returns 409 automatically. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
All route catch blocks now call next(error) instead of responding directly. Inline 401/404 guards use typed error classes (AuthenticationError, NotFoundError, etc.) so the centralized handler owns the full error response lifecycle. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Replaces the inline global error handler with the new errorHandler middleware. Updates the 404 handler to return the standardized error shape. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
ErrorResponse now documents error (class name), message, code (machine-readable), and optional details array so API consumers can rely on a single documented error shape. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
18 tests covering: - All AppError subclass properties (statusCode, code, instanceof) - errorHandler HTTP mapping for each error type - Unknown Error → 500 fallback - Stack trace excluded in production - 404 handler shape via createApp integration Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Add missing authChallenge.updateMany mock to auth route tests
- Update error field assertions from human-readable labels
(e.g. "Validation Error") to class names ("ValidationError")
- Update notifications 404 assertions to check message field
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
The package ships as ESM ("type": "module") but the backend compiles to
CJS. A static import would be compiled to require() and fail at runtime
with ERR_REQUIRE_ESM.
- Use `import type` for compile-time types (zero runtime cost)
- Move Client instantiation to a private async init() stored as this.ready
- Use `await import()` inside init() — works across ESM/CJS boundary
- Make ensureInitialized() async so all methods await this.ready
- Delete src/types/xelma-bindings.d.ts — real package types are now used
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
prediction.service.spec.ts: jest.mock() is hoisted before const declarations, causing TDZ ReferenceError. Fix by creating jest.fn() instances inside the factory and getting named references from the mocked module after import. prediction.service.ts: move mode-specific validation (side/priceRange) before any DB writes, and add an explicit user.findUnique check so "User not found" is distinguished from "Insufficient balance". Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
prediction.service.spec.ts: - Keep HEAD factory (includes user.findUnique needed by updated service) - Use exact where-clause assertion from origin/main for atomic update - Add mockUserFindUnique setup to success cases auth.routes.spec.ts: - Keep mockAuthChallengeUpdate (service calls authChallenge.update at line 360) - Keep ValidationError shape (centralized error handler) - Keep findUnique mock in success test (route re-fetches challenge after updateMany) - Keep explanatory comments
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.
Summary
AppError+ 8 subclasses) replacing ad-hocErrorobjects scattered across routes and serviceserrorHandlerExpress middleware that owns all error-to-HTTP mapping, structured logging, and Prisma error translationnext(error)instead of responding inline, eliminating ~140 lines of duplicated error handling codeChanges
New files
src/utils/errors.ts—AppErrorbase class +ValidationError(400),AuthenticationError(401),AuthorizationError(403),NotFoundError(404),ConflictError(409),BusinessRuleError(422),ExternalServiceError(503),ConfigurationError(500)src/middleware/errorHandler.middleware.ts— Centralized error handler; mapsAppErrorsubclasses, translates Prisma errors (P2025→404, P2002→409), falls back to 500; exportsasyncHandlerwrappersrc/tests/errorHandler.spec.ts— 18 tests covering all error classes and middleware HTTP mappingModified files
src/middleware/validate.middleware.ts— Callsnext(new ValidationError(...))instead of responding directlysrc/services/round.service.ts— ThrowsConflictError("...", "ACTIVE_ROUND_EXISTS")instead of ad-hoc error objectsrc/routes/*(8 files) — Allcatchblocks usenext(error); inline 404/401 guards use typed error classessrc/index.ts— Replaces inline global error handler witherrorHandlermiddlewaresrc/docs/openapi.ts—ErrorResponseschema updated to document the full standardized shapeError response contract
{ "error": "ValidationError", "message": "walletAddress is required", "code": "VALIDATION_ERROR", "details": [{ "field": "walletAddress", "message": "walletAddress is required" }] }ValidationErrorVALIDATION_ERRORAuthenticationErrorINVALID_SIGNATURE,CHALLENGE_EXPIREDAuthorizationErrorAUTHORIZATION_ERRORNotFoundErrorNOT_FOUNDConflictErrorACTIVE_ROUND_EXISTSBusinessRuleErrorINVALID_ROUND_STATEExternalServiceErrorEXTERNAL_SERVICE_ERRORNOT_FOUNDCONFLICTErrorINTERNAL_ERRORTest plan
npm run lintpasses (no type errors)errorHandler.spec.ts— 18 tests, all greenauth.routes.spec.ts— all tests pass (updated mocks + error field assertions)notifications.routes.spec.ts— all tests pass{ error: "ValidationError", code: "VALIDATION_ERROR", details: [...] }{ error: "AuthenticationError", code: "INVALID_SIGNATURE" }code: "ACTIVE_ROUND_EXISTS"code: "NOT_FOUND"Closes #92