Skip to content

refactor(server): replace try/catch with neverthrow Result types#742

Open
kira-autonoma wants to merge 3 commits intoMerit-Systems:masterfrom
kira-autonoma:refactor/neverthrow-result-types
Open

refactor(server): replace try/catch with neverthrow Result types#742
kira-autonoma wants to merge 3 commits intoMerit-Systems:masterfrom
kira-autonoma:refactor/neverthrow-result-types

Conversation

@kira-autonoma
Copy link

Closes #581

Summary

Replaces all try/catch blocks in packages/app/server with typed neverthrow Result and ResultAsync types, providing exhaustive, compile-time error handling throughout the server.

Changes

New: src/errors/results.ts

Discriminated union error types for each layer:

  • DbError — database operation failures with structured context
  • AuthError — authentication/authorization failures with subtypes
  • SettleError — x402 payment settlement failure variants
  • RefundError — refund transfer failures
  • ResourceError — top-level handler errors composing the above

Refactored: handlers/settle.ts

  • Returns ResultAsync<SettleSuccess, SettleError> instead of Promise<... | undefined>
  • Uses fromThrowable to wrap the synchronous validateXPaymentHeader
  • Full .andThen() chain: smart account → header parse → payload validate → amount check → facilitator settle
  • Removed res: Response from signature — callers own the HTTP response

Refactored: handlers/refund.ts

  • Returns ResultAsync<void, RefundError> instead of swallowing errors
  • Callers decide how to handle refund failures (log, alert, etc.)

Refactored: resources/handler.ts

  • handleApiRequestResultAsync<TOutput, ResourceError> via chained .andThen()
  • executeResourceWithRefundResultAsync<TOutput, ResourceError>; refund on error via .mapErr()
  • handle402RequestResultAsync<TOutput, ResourceError> composing settle and executeResourceWithRefund
  • handleResourceRequest uses .match() at the HTTP boundary — no try/catch
  • handleResourceRequestWithErrorHandling wraps in ResultAsync.fromPromise and .match()

Refactored: services/DbService.ts

  • validateApiKeyResultAsync<ApiKeyValidationResult, AuthError>
  • getBalanceResultAsync<Balance, DbError>
  • createPaidTransactionResultAsync<Transaction, DbError>
  • createFreeTierTransactionResultAsync<{transaction, userSpendPoolUsage}, DbError>

Refactored: services/EchoControlService.ts + FreeTierService.ts

  • verifyApiKey, getBalance, createTransaction, createFreeTierTransaction, createPaidTransaction all propagate ResultAsync
  • Errors surfaced via .isErr() checks with typed error variants

Design Decisions

  • fromThrowable wraps sync-throwing functions at the boundary; async operations use ResultAsync.fromPromise
  • .match() at HTTP boundary — Results never leak into Express response handlers
  • cause: unknown on external-facing error variants where the underlying throw type cannot be statically known
  • Discriminated unions over class hierarchies — exhaustive switch on error.type is idiomatic neverthrow

TypeScript Status

Zero new type errors introduced. Pre-existing infrastructure errors (generated/prisma not generated, workspace SDK not built) are unaffected and unchanged.

Claude Agent and others added 3 commits March 15, 2026 19:56
Remove `gemini-2.0-flash-preview-image-generation` and
`gemini-2.5-flash-image-preview` from the SDK type definitions,
model pricing list, and smoke test blacklist. Update templates
to use the stable `gemini-2.5-flash-image` replacement.

Closes Merit-Systems#634

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Add typed error variants in errors/results.ts (DbError, AuthError, SettleError, RefundError, ResourceError)
- Refactor settle() to return ResultAsync<SettleSuccess, SettleError> with fromThrowable for sync validateXPaymentHeader
- Refactor refund() to return ResultAsync<void, RefundError> instead of swallowing errors
- Refactor DbService.validateApiKey() to return ResultAsync<ApiKeyValidationResult, AuthError>
- Refactor DbService.getBalance() to return ResultAsync<Balance, DbError>
- Refactor DbService.createPaidTransaction() to return ResultAsync<Transaction, DbError>
- Refactor DbService.createFreeTierTransaction() to return ResultAsync<..., DbError>
- Update EchoControlService to handle ResultAsync returns from DbService
- Update handlers.ts and resources/handler.ts to use .isOk()/.isErr()/.value on Result types
- Add neverthrow ^7.2.0 dependency to echo-server package

Closes Merit-Systems#581

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Replace all try/catch in handleApiRequest, handle402Request,
  executeResourceWithRefund, and handleResourceRequestWithErrorHandling
- Use ResultAsync chains with .andThen() and .match() throughout
- Remove PaymentRequiredError from handler; 402 errors now carried
  as RESOURCE_PAYMENT_FAILED variant in ResourceError
- Update ResourceError cause types to unknown for external rejections
@vercel
Copy link
Contributor

vercel bot commented Mar 16, 2026

Someone is attempting to deploy a commit to the Merit Systems Team on Vercel.

A member of the Team first needs to authorize it.

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.

[Refactor] Remove all try/catch from the server replace with neverthow

1 participant