From TypeScript Errors to Architectural Insights: A Code Quality Journey
Part 1 of 2 in the "Code Quality and Analytical Humility" series
Series Overview
This two-part series documents a code quality session that evolved from routine TypeScript fixes into deeper lessons about architecture and analysis.
- Part 1 (you are here): The technical journey—fixing errors, creating abstractions, discovering boundary layers
- Part 2: Analysis Mistakes to Architectural Wisdom—the analytical missteps that revealed the architecture's true sophistication
These essays are companions. Part 1 celebrates what went right technically; Part 2 examines what went wrong analytically—and why both matter.
The Context
This story unfolds in a polyglot monorepo using Moon orchestration, with a Hono API (Deno), Next.js frontend (Node), and shared validation package—each with different runtime constraints and Zod version dependencies. The trigger was a CI pipeline failure during a routine moon run api:typecheck execution.
The Problem Emerges
What began as a technical fix evolved into a deep exploration of software architecture, code quality, and sustainable development practices when 10 blocking TypeScript errors surfaced across the API codebase.
The Initial Errors
The typecheck errors spanned three critical areas:
Zod Version Incompatibility
The most complex issue involved @hono/zod-validator incompatibility with Zod 4. The project had upgraded to zod: ^4.1.12, but the Hono middleware still expected Zod 3 patterns:
_zod.version.minortype mismatches- Deprecated
.flatten()method calls - Incompatible error handling APIs
Missing Imports and Error Handling
Three locations in apps/api/src/routes/projects.ts lacked proper Zod imports and used outdated error flattening methods.
Schema and Logger Mismatches
Email mapping required messageId fields, and logger instantiation failed interface compliance checks.
The Quick Fix vs. Sustainable Solution Dilemma
The initial approach was straightforward: patch the immediate errors. But deeper analysis revealed patterns worth addressing:
- Duplicated Error Handling: Three nearly identical Zod error response blocks
- Version Dependency Coupling: Zod 4 compatibility issues affecting middleware choices
- Technical Debt Documentation: No clear path for future middleware migration
Code Review as Catalyst
The code review process transformed reactive fixes into proactive architecture:
Error Handling Consolidation
Instead of accepting duplicated error responses, we created a centralized utility:
// packages/validation/src/errors.ts
export function zodErrorResponse(c: Context, error: z.ZodError) {
return c.json({
error: "validation_error",
message: "Invalid request body",
details: z.flattenError(error),
}, 400);
}
This single abstraction reduced 24 lines of duplicated code to 9 lines across three locations—a 62% reduction that scales as the API grows.
Technical Debt Documentation
Rather than hiding the custom Zod validator, we documented it comprehensively:
/**
* Custom Zod validator for Hono - TEMPORARY WORKAROUND
*
* @hono/zod-validator doesn't support Zod 4 yet
* TODO: Migrate back to @hono/zod-validator when they release Zod 4 support
* See: https://github.com/honojs/middleware/issues/1148
*
* This validator handles all validation locations (json, query, form, param)
* using Zod 4 compatible patterns.
*/
The Boundary Layer Revelation
The refactoring revealed @repo/validation as a critical architectural boundary:
┌─────────────────────────────────────┐
│ Route Handlers (business logic) │
├─────────────────────────────────────┤
│ Validation Layer (@repo/validation) │ ← Boundary layer
│ - Zod schemas & error formatting │
│ - Custom validators │
│ - Middleware helpers │
├─────────────────────────────────────┤
│ Core Types (packages/core) │
│ - Drizzle schemas │
│ - Base Zod schemas │
└─────────────────────────────────────┘
This positioning provides three key benefits:
Change Absorption
External dependency changes (Zod versions, middleware updates) get contained within the validation layer, preventing ripple effects through business logic.
Testing Boundaries
Clear separation enables focused testing:
- Validation layer tests: Input sanitization and error formatting
- Business logic tests: Domain logic with mocked validation
- Integration tests: End-to-end request flows
Evolution Flexibility
The Zod 4 migration demonstrated contained, incremental evolution. When @hono/zod-validator adds Zod 4 support, migration involves only the validation package—route handlers remain untouched.
Principles Learned
Three key principles emerged from this exercise:
1. Error Handling Utilities Pay Dividends
Centralized validation responses prevent API contract drift. As the API grows, consistent error handling becomes increasingly valuable for client reliability.
2. Technical Debt Documentation Matters
TODO comments with GitHub issue links transform debt into trackable work items. This creates accountability and enables prioritization alongside feature development.
3. Small Abstractions Compound
The 62% line reduction across three call sites demonstrates compounding returns. What seems minor initially scales significantly as the codebase grows.
4. Consolidate Duplicates at the Right Layer
A concrete example: validateDocumentStatus existed in three locations:
// Location 1: packages/orpc-server/src/lib/utils.ts (good - used Zod enum)
// Location 2: packages/use-cases/src/projects/getProject.ts (duplicate - hardcoded array)
// Location 3: apps/api/src/routes/documents.ts (duplicate - local Set)
The fix wasn't complex—move the canonical version to @indicia/core where the DOCUMENT_STATUS constant lives:
// packages/core/src/domain/constants.ts
export const DOCUMENT_STATUS = {
UPLOADING: 'uploading',
PROCESSING: 'processing',
COMPLETE: 'complete',
ERROR: 'error',
DELETED: 'deleted',
} as const
const VALID_DOCUMENT_STATUSES = Object.values(DOCUMENT_STATUS) as readonly string[]
export function validateDocumentStatus(status: string): DocumentStatus {
return VALID_DOCUMENT_STATUSES.includes(status)
? (status as DocumentStatus)
: DOCUMENT_STATUS.ERROR
}
Then update all three locations to import from core. ~25 lines of duplicate code eliminated, single source of truth established.
Part 2 Preview: These principles emerged cleanly. But the session also produced suggestions that were rejected—improvements that seemed reasonable but missed the mark entirely. Part 2 examines those analytical failures and what they reveal about understanding unfamiliar codebases.
Architectural Patterns Recognized
This boundary layer approach mirrors proven architectural patterns:
- Hexagonal Architecture: Validation as an adapter isolating core business logic
- Clean Architecture: Validation as infrastructure, not domain logic
- Ports & Adapters: Validation layer as a port for external input adaptation
Long-term Impact
The @repo/validation package now serves as a proper validation utilities layer, ready for:
- Rate limiting error responses
- Authentication error handling
- Business logic validation patterns
- Input sanitization utilities
Tradeoffs
Every architectural decision has costs. The validation layer improvements introduced several tradeoffs:
- Custom validator maintenance: We now own this code until upstream catches up
- Abstraction overhead: New developers must learn the validation layer API
- Migration risk: When @hono/zod-validator supports Zod 4, we need to verify parity
These costs are outweighed by the benefits of clean separation and future flexibility, but they represent real engineering debt that must be tracked and eventually resolved.
Patterns to Watch For
When fixing TypeScript errors, look for opportunities to:
- Extract repeated patterns into utilities
- Document workarounds with migration paths
- Identify boundary layers absorbing external changes
Pre-Refactoring Checklist
Before diving into fixes, consider:
- [ ] Identify root cause vs. symptoms
- [ ] Count duplicated patterns
- [ ] Check for upstream issue tracking
- [ ] Map affected architectural layers
Conclusion
What began as fixing 10 TypeScript errors evolved into architectural improvement. The journey demonstrated how code review, when approached thoughtfully, can transform routine maintenance into strategic enhancement.
The validation boundary layer created during this process will serve the codebase for years, absorbing external changes while maintaining internal stability. This is the essence of sustainable software development—building systems that evolve gracefully rather than accumulating technical debt.
The experience reinforced that excellent code quality emerges not from following rules, but from recognizing patterns, understanding architectural boundaries, and making thoughtful investments in abstractions that compound over time.
Continue to Part 2
This essay told the success story—the technical wins, the clean abstractions, the architectural insights. But it's only half the picture.
The same session that produced these improvements also produced confident suggestions that were completely wrong. Suggestions based on assumptions about Hono RPC that missed the actual oRPC architecture. Suggestions for features the codebase didn't need.
Part 2: Analysis Mistakes to Architectural Wisdom tells that story—and argues that the failures were ultimately more instructive than the successes.
"Your suggestions were solutions looking for problems. Our architecture doesn't have those problems."
That single piece of feedback transformed the entire session. Part 2 explores why.