Architecture Decision Records
Architecture Decision Records
Section titled “Architecture Decision Records”This document contains Architecture Decision Records (ADRs) for Tabula Lens, capturing key architectural decisions, their context, and consequences.
What are Architecture Decision Records?
Section titled “What are Architecture Decision Records?”Architecture Decision Records (ADRs) document important architectural decisions made during the development of a system. Each ADR captures:
- Context: The situation that led to the decision
- Decision: The decision that was made
- Consequences: The results of the decision, both positive and negative
ADR-001: HTTP API as Universal Interface
Section titled “ADR-001: HTTP API as Universal Interface”Status
Section titled “Status”Accepted
Context
Section titled “Context”Tabula Lens needed to work with any frontend technology and multiple backend frameworks (Express, Fastify, Next.js, etc.). A direct database connection from the frontend would expose credentials and create security vulnerabilities.
Decision
Section titled “Decision”Implement an HTTP API as the universal interface between frontend and backend. The backend handles database connections and exposes a RESTful API that any frontend can consume.
Consequences
Section titled “Consequences”Positive:
- Frontend-agnostic: Any frontend technology can be used
- Security: Database credentials never leave the backend
- Flexibility: Backend can be implemented in any language/framework
- Scalability: API can be cached, load-balanced, and monitored
- Standard: Uses well-understood HTTP/REST patterns
Negative:
- Additional layer: Adds HTTP overhead compared to direct connection
- Complexity: Requires API implementation and maintenance
- Latency: Network latency between frontend and backend
- State management: HTTP is stateless, requires session/token management
Alternatives Considered
Section titled “Alternatives Considered”- Direct Database Connection: Rejected due to security concerns
- GraphQL: Considered but REST chosen for simplicity and broader compatibility
- WebSocket: Rejected as overkill for query/response pattern
ADR-002: PostgreSQL as Primary Database
Section titled “ADR-002: PostgreSQL as Primary Database”Status
Section titled “Status”Superseded by ADR-007: Multi-Database Support Decision
Context
Section titled “Context”Tabula Lens needed a database that could handle structured data, support complex queries, and provide strong consistency guarantees. The database needed to be widely supported, well-documented, and suitable for production use.
Decision
Section titled “Decision”Use PostgreSQL as the primary and supported database for Tabula Lens.
Evolution
Section titled “Evolution”This decision was later superseded by ADR-007: Multi-Database Support Decision, which extended Tabula Lens to support MySQL, SQLite, and SQL Server in addition to PostgreSQL. PostgreSQL remains the recommended database for new projects due to its advanced features, but Tabula Lens now provides first-class support for multiple database engines through a unified query layer.
Consequences
Section titled “Consequences”Positive:
- Powerful: Advanced features (JSON, indexes, constraints, etc.)
- Reliable: ACID compliance and strong consistency
- Performant: Excellent query optimization and indexing
- Widely Supported: Available on all major cloud platforms
- Open Source: No licensing costs
- Extensible: Supports custom functions and extensions
Negative:
- Single Database: Limits database flexibility (though other databases could be added)
- Resource Intensive: Requires more resources than lighter databases
- Complexity: Advanced features have learning curve
Alternatives Considered
Section titled “Alternatives Considered”- MySQL: Good alternative but PostgreSQL chosen for advanced features
- SQLite: Rejected for production use cases
- MongoDB: Rejected as Tabula Lens focuses on structured data
- Multi-Database Support: Deferred to future versions
ADR-003: Modular Component Architecture
Section titled “ADR-003: Modular Component Architecture”Status
Section titled “Status”Accepted
Context
Section titled “Context”The DatabaseViewer React component was becoming monolithic, making it difficult to maintain, test, and customize. Users needed flexibility to customize individual UI elements while maintaining the overall component functionality.
Decision
Section titled “Decision”Refactor the DatabaseViewer component into a modular architecture with:
- Sub-components (LoadingState, ErrorState, EmptyState, etc.)
- Custom hooks (useLogger, useTableState, useDatabaseData)
- Utility functions (fetchHelpers, validationHelpers, styleHelpers)
- Runtime prop validation
Consequences
Section titled “Consequences”Positive:
- Maintainability: Easier to understand and modify
- Testability: Individual components can be tested in isolation
- Customizability: Users can override specific sub-components
- Reusability: Hooks and utilities can be used independently
- Performance: React.memo optimization on sub-components
- Developer Experience: Better code organization and documentation
Negative:
- Complexity: More files and components to manage
- Learning Curve: Users need to understand the architecture
- Bundle Size: Slightly larger due to modular structure
- Migration: Existing users need to understand new structure
Alternatives Considered
Section titled “Alternatives Considered”- Keep Monolithic Component: Rejected due to maintainability concerns
- Separate Package: Rejected as overkill for this use case
- Higher-Order Components: Rejected in favor of hooks pattern
ADR-004: CSS Custom Properties for Theming
Section titled “ADR-004: CSS Custom Properties for Theming”Status
Section titled “Status”Accepted
Context
Section titled “Context”Tabula Lens needed a theming system that would:
- Support dark mode
- Allow brand customization
- Work across different frontend frameworks
- Enable runtime theming without JavaScript
- Maintain consistency across components
Decision
Section titled “Decision”Use CSS custom properties (CSS variables) as the foundation of the theming system, with a --tlens- prefix to avoid conflicts.
Consequences
Section titled “Consequences”Positive:
- Runtime Theming: Theme changes without JavaScript re-render
- Dark Mode: Native support through CSS media queries
- Framework Agnostic: Works with any CSS-based framework
- Performance: No JavaScript overhead for theme switching
- Maintainability: Single source of truth for design tokens
- Customization: Easy for users to override specific tokens
Negative:
- Browser Support: Requires modern browsers (IE11 not supported)
- Fallbacks: Need fallback values for older browsers
- Complexity: CSS custom properties have learning curve
- Debugging: Can be harder to debug than preprocessor variables
Alternatives Considered
Section titled “Alternatives Considered”- CSS Preprocessors (Sass/Less): Rejected as they require build step
- JavaScript-based Theming: Rejected due to performance overhead
- CSS-in-JS: Rejected for framework agnosticism requirements
ADR-005: Comprehensive Logging System
Section titled “ADR-005: Comprehensive Logging System”Status
Section titled “Status”Accepted
Context
Section titled “Context”Tabula Lens needed a logging system that would:
- Provide visibility into database operations
- Help with debugging and troubleshooting
- Support different environments (development, production)
- Allow sensitive data masking
- Support multiple log formats (JSON, text, pretty)
Decision
Section titled “Decision”Implement a comprehensive logging system with:
- Multiple log levels (error, warn, info, debug, silent)
- Configurable log formats
- Request and query logging
- Sensitive data masking
- Environment-specific defaults
- Custom logger integration
Consequences
Section titled “Consequences”Positive:
- Debugging: Easier to troubleshoot issues
- Monitoring: Better visibility into system behavior
- Security: Sensitive data can be masked
- Flexibility: Users can integrate their own loggers
- Production-Ready: JSON format for log aggregation
- Configurable: Can be adjusted per environment
Negative:
- Complexity: Additional configuration required
- Performance: Logging has overhead (though minimal)
- Storage: Logs need storage and rotation strategy
- Noise: Can generate large amounts of log data
Alternatives Considered
Section titled “Alternatives Considered”- Console.log Only: Rejected as insufficient for production
- Third-Party Logger: Considered but custom implementation chosen for control
- No Logging: Rejected as it would make debugging impossible
ADR-006: Framework Adapter Pattern
Section titled “ADR-006: Framework Adapter Pattern”Status
Section titled “Status”Accepted
Context
Section titled “Context”Tabula Lens needed to support multiple Node.js frameworks (Express, Fastify, Koa, Next.js, etc.) while maintaining a consistent API. Each framework has different middleware patterns and request/response handling.
Decision
Section titled “Decision”Implement a framework adapter pattern that provides:
- Consistent API across frameworks
- Framework-specific adapters
- Middleware integration
- Request/response handling
- Authentication hooks
Consequences
Section titled “Consequences”Positive:
- Flexibility: Supports 15+ frameworks out of the box
- Consistency: Same API regardless of framework
- Maintainability: Framework-specific code isolated
- Extensibility: Easy to add new framework adapters
- Documentation: Clear examples for each framework
Negative:
- Maintenance: Need to maintain multiple adapters
- Testing: Each adapter needs testing
- Complexity: Additional abstraction layer
- Learning: Users need to understand adapter pattern
Alternatives Considered
Section titled “Alternatives Considered”- Framework-Specific Packages: Rejected due to maintenance overhead
- Core Package Only: Rejected as it would limit adoption
- Community Adapters: Rejected as it would create inconsistency
ADR-007: Multi-Database Support Decision
Section titled “ADR-007: Multi-Database Support Decision”Status
Section titled “Status”Accepted
Context
Section titled “Context”Tabula Lens started with PostgreSQL as the primary supported database. As adoption grew, users requested support for MySQL, SQLite, and Microsoft SQL Server because those engines were already in use in their existing environments. Supporting multiple relational databases would broaden adoption without changing the frontend or the public TabulaLens API.
Decision
Section titled “Decision”Support PostgreSQL, MySQL, SQLite, and Microsoft SQL Server through a unified query layer:
- Use Knex.js as the shared query builder.
- Auto-detect the database type from the connection URL via
detectDatabaseType. - Allow explicit
typeoverride in theTabulaLensConfigobject. - Map each
DatabaseTypeto the correct Knex client and underlying driver:pg->pgmysql->mysql2sqlite->better-sqlite3mssql->tedious
- Isolate engine-specific SQL and metadata behavior behind the
DialectStrategyinterface.
Consequences
Section titled “Consequences”Positive:
- Broader Adoption: Works with the four most common relational databases
- Unified API: Frontend and backend consumers use the same interface regardless of engine
- Extensibility: New relational engines can be added by implementing a dialect
- Ecosystem: Knex provides mature, well-tested multi-database support
Negative:
- Maintenance Overhead: Four drivers and dialects to test and maintain
- Driver Quirks: Each engine has subtle differences in metadata, types, and operators
- Test Matrix: Unit and integration tests must cover all supported engines
- Dependency Management: Users must install the correct peer driver for their database
Alternatives Considered
Section titled “Alternatives Considered”- PostgreSQL Only: Rejected because it would limit adoption in MySQL/SQL Server/SQLite environments
- Separate Packages Per Database: Rejected due to fragmentation and duplicated code
- Community Adapters: Rejected because inconsistent adapter quality would harm the unified API
- Raw Driver Abstraction Without Knex: Rejected due to the amount of duplicated SQL and connection logic required
ADR-008: Knex.js over Drizzle
Section titled “ADR-008: Knex.js over Drizzle”Status
Section titled “Status”Accepted
Context
Section titled “Context”To support multiple databases, Tabula Lens needed a query builder or ORM that could run against PostgreSQL, MySQL, SQLite, and SQL Server. Modern TypeScript ORMs such as Drizzle and Prisma are attractive because they offer type safety, but they typically require a predefined schema and generated client code. Tabula Lens introspects arbitrary tables at runtime and must work with any existing schema without code generation.
Decision
Section titled “Decision”Use Knex.js as the query builder.
- Knex is schema-agnostic: it does not require a generated schema or client.
- It supports all four target databases through the same API.
- It allows raw SQL fallbacks when engine-specific syntax is required.
- It handles connection pooling and driver loading transparently.
Consequences
Section titled “Consequences”Positive:
- Runtime Introspection: Works with any existing database schema without code generation
- Broad Database Support: PostgreSQL, MySQL, SQLite, and SQL Server are first-class clients
- Flexibility: Raw queries are available when dialect differences cannot be abstracted
- Maturity: Knex has a large ecosystem and stable API
Negative:
- No Schema-Derived Type Safety: Query results are not statically typed from a schema
- Manual Query Construction: Complex queries still require careful construction
- Dialect Differences: Some behaviors (e.g.,
ILIKEvsLIKE,information_schemavsPRAGMA) must be handled explicitly
Alternatives Considered
Section titled “Alternatives Considered”- Drizzle ORM: Rejected because it requires a schema-first approach and generated code
- Prisma: Rejected because it requires a schema file and client generation, which conflicts with runtime introspection
- TypeORM: Considered but rejected due to configuration complexity and inconsistent cross-database behavior
- Raw Database Drivers Only: Rejected because it would duplicate connection pooling, query building, and dialect handling
ADR-009: Dialect Strategy Pattern
Section titled “ADR-009: Dialect Strategy Pattern”Status
Section titled “Status”Accepted
Context
Section titled “Context”Each supported database has different metadata catalogs, case-sensitivity rules, and text-search operators. PostgreSQL uses ILIKE and information_schema in the public schema. MySQL also uses information_schema but scopes it to DATABASE(). SQLite has no information_schema and instead uses PRAGMA table_info(...) and sqlite_master. SQL Server uses information_schema but has its own type names and collations. Embedding these differences directly into TabulaLens would create brittle, hard-to-test conditional logic.
Decision
Section titled “Decision”Encapsulate all engine-specific behavior behind a DialectStrategy interface and a createDialect factory.
The interface defines four responsibilities:
getTables(db)— list all user tablesgetColumns(db, table)— list column names and types for a tablegetFilterableTypes()— return the type names considered text-searchablegetLikeOperator()— returnLIKEorILIKEfor case-insensitive filtering
Implementations are provided for PostgreSQL, MySQL, SQLite, and SQL Server. TabulaLens instantiates the correct dialect once at startup and delegates metadata and operator decisions to it.
Consequences
Section titled “Consequences”Positive:
- Isolation: Engine-specific SQL is centralized in one place per database
- Testability: Each dialect can be unit tested independently
- Extensibility: Adding a new database only requires a new dialect implementation
- Simplicity:
TabulaLensquery logic stays generic and readable
Negative:
- Additional Abstraction: Developers must understand the strategy pattern to add a new engine
- Duplicated Metadata Concepts: Similar
information_schemaqueries exist in multiple dialects with small variations - Naming Collisions: Type names differ between engines and must be normalized carefully
Alternatives Considered
Section titled “Alternatives Considered”- Inline Conditionals in
TabulaLens: Rejected because it would scatter database-specific logic throughout the query builder - Single Dialect with Raw Overrides: Rejected because it would still require engine checks and would not scale
- ORM Metadata API: Rejected because ORMs abstract metadata in ways that do not always map to the raw catalogs we need
- Knex-Specific Plugins: Considered but rejected because a custom interface gives us precise control over behavior
ADR-010: Peer Dependency Approach for Database Drivers
Section titled “ADR-010: Peer Dependency Approach for Database Drivers”Status
Section titled “Status”Accepted
Context
Section titled “Context”Tabula Lens supports four database engines, each requiring a native or database-specific driver (pg, mysql2, better-sqlite3, tedious). Bundling all four drivers as required dependencies would force every user to install packages they do not need, including native build toolchains for SQLite (better-sqlite3) even if they only use PostgreSQL.
Decision
Section titled “Decision”Declare database drivers as optional peer dependencies.
pg: ^8.0.0mysql2: ^3.0.0better-sqlite3: ^12.0.0tedious: ^20.0.0
Knex loads whichever driver corresponds to the configured client at runtime. Users install only the driver(s) for the database(s) they connect to. Missing drivers produce a runtime error from the underlying Knex client when a connection is first attempted.
Consequences
Section titled “Consequences”Positive:
- Smaller Installs: Users are not forced to download drivers for engines they do not use
- No Unnecessary Native Builds: SQLite native compilation is only required for SQLite users
- Flexibility: The same package supports local SQLite development, managed PostgreSQL/MySQL production, and SQL Server deployments
- Clear Ownership: Users explicitly choose and install the driver they need
Negative:
- Manual Installation Step: Users must install a driver in addition to
@tabula-lens/node - Runtime Errors: A missing driver is only detected when a query runs, not at install time
- Peer Dependency Warnings: Package managers may warn about unmet peer dependencies for unused engines
- Documentation Overhead: Installation instructions must list each driver separately
Alternatives Considered
Section titled “Alternatives Considered”- Bundle All Drivers as Required Dependencies: Rejected because it would bloat installs and require native build tools for every user
- One Package Per Database Engine: Rejected because it would fragment the codebase and complicate the public API
- Optional Dependencies: Considered but rejected because optional dependencies still attempt installation and may fail on unsupported platforms
- Runtime Dynamic Imports: Rejected because it would complicate bundling and type checking without solving the peer-dependency problem
ADR-011: Manual API Documentation Over TypeDoc
Section titled “ADR-011: Manual API Documentation Over TypeDoc”Status
Section titled “Status”Accepted
Context
Section titled “Context”Tabula Lens needed comprehensive API documentation for both Node and React packages. The codebase had extensive JSDoc comments, and TypeDoc could auto-generate API documentation from these comments.
Decision
Section titled “Decision”Use comprehensive manual documentation instead of TypeDoc auto-generation. Manual documentation provides:
- Detailed explanations and context
- Real-world usage examples
- Best practices and guidance
- Clear organization and structure
- Better developer experience
Consequences
Section titled “Consequences”Positive:
- Quality: Higher quality documentation with context
- Examples: Real-world usage patterns
- Guidance: Best practices and recommendations
- Organization: Logical structure for navigation
- Experience: Better for developers learning the system
Negative:
- Maintenance: Manual updates required when code changes
- Effort: More time to create initially
- Synchronization Risk: Documentation could become out of sync
- Consistency: Requires discipline to maintain
Alternatives Considered
Section titled “Alternatives Considered”- TypeDoc Auto-Generation: Evaluated but manual chosen for quality
- Hybrid Approach: Considered but manual only chosen for simplicity
- Community Documentation: Rejected as it would be inconsistent
ADR-012: Vitest as Testing Framework
Section titled “ADR-012: Vitest as Testing Framework”Status
Section titled “Status”Accepted
Context
Section titled “Context”Tabula Lens needed a modern testing framework that would:
- Work with TypeScript
- Support React component testing
- Provide fast test execution
- Have good watch mode
- Integrate well with modern build tools
Decision
Section titled “Decision”Use Vitest as the testing framework for both Node and React packages, with:
- Node environment for backend testing
- jsdom environment for React testing
- React Testing Library for component testing
- Jest-compatible API for familiarity
Consequences
Section titled “Consequences”Positive:
- Performance: Faster test execution than Jest
- Modern: Built with modern tooling (Vite)
- TypeScript: Native TypeScript support
- React: Excellent React Testing Library integration
- Familiarity: Jest-compatible API
- Watch Mode: Fast and reliable watch mode
Negative:
- Ecosystem: Smaller ecosystem than Jest
- Maturity: Newer framework with less battle-testing
- Migration: Requires migration from Jest if used previously
Alternatives Considered
Section titled “Alternatives Considered”- Jest: Considered but Vitest chosen for performance
- Mocha: Rejected due to configuration complexity
- Ava: Rejected due to smaller ecosystem
ADR-013: Astro with Starlight for Documentation
Section titled “ADR-013: Astro with Starlight for Documentation”Status
Section titled “Status”Accepted
Context
Section titled “Context”Tabula Lens needed a documentation site that would:
- Be fast and performant
- Support MDX content
- Provide excellent navigation
- Have built-in search
- Support dark mode
- Be easy to deploy
Decision
Section titled “Decision”Use Astro with Starlight theme for the documentation site, providing:
- Static site generation for performance
- MDX support for rich content
- Built-in navigation and search
- Dark mode support
- Responsive design
- Easy deployment
Consequences
Section titled “Consequences”Positive:
- Performance: Static site generation for fast loading
- Developer Experience: Excellent DX with Astro
- Features: Built-in search, navigation, dark mode
- Modern: Modern tooling and best practices
- Customization: Starlight theme is highly customizable
- Deployment: Easy to deploy to any static host
Negative:
- Learning Curve: Team needs to learn Astro
- Build Time: Static generation requires build step
- Dynamic Content: Limited dynamic content support
- Theme: Customizing Starlight requires understanding
Alternatives Considered
Section titled “Alternatives Considered”- Docusaurus: Considered but Astro chosen for performance
- VitePress: Rejected due to less mature theme
- Custom Next.js: Rejected as overkill for documentation
ADR-016: Security-First Architecture
Section titled “ADR-016: Security-First Architecture”Status
Section titled “Status”Accepted
Context
Section titled “Context”Tabula Lens deals with database access and sensitive data. Security needed to be a core consideration from the ground up, not an afterthought.
Decision
Section titled “Decision”Implement security-first architecture with:
- Credential isolation (never in frontend)
- Authentication at API level
- Authorization enforcement
- Input validation and sanitization
- Secure error handling
- Comprehensive security documentation
Consequences
Section titled “Consequences”Positive:
- Security: Strong security posture by default
- Trust: Users can trust the system with sensitive data
- Compliance: Helps with regulatory compliance (GDPR, SOC 2, etc.)
- Best Practices: Encourages security best practices
- Documentation: Comprehensive security guidance
Negative:
- Complexity: Additional security layers add complexity
- Performance: Security checks have overhead
- Configuration: Requires proper security configuration
- Learning: Users need to understand security model
Alternatives Considered
Section titled “Alternatives Considered”- Security as Add-On: Rejected as security must be core
- User-Managed Security: Rejected as it would lead to insecure implementations
- Minimal Security: Rejected as insufficient for production use
ADR-017: Design System Source of Truth
Section titled “ADR-017: Design System Source of Truth”Status
Section titled “Status”Accepted
Context
Section titled “Context”Tabula Lens needed a design system that would be used across:
- React package components
- Documentation site
- Future applications
A decision needed to be made about where to store the design system source of truth.
Decision
Section titled “Decision”Use the React package CSS files as the source of truth for the design system. The React package CSS files (variables.css, global.css) contain all design tokens and styles.
Consequences
Section titled “Consequences”Positive:
- Single Source: One source of truth for design tokens
- Consistency: Ensures consistency across all uses
- Simplicity: No additional files or directories needed
- Maintenance: Design tokens updated in one place
- Package-Centric: Design system lives with the components
Negative:
- Coupling: Documentation site depends on React package
- Access: Design tokens not accessible without package
- Updates: Documentation site needs React package updates
- Separation: Design system not clearly separated
Alternatives Considered
Section titled “Alternatives Considered”- Shared styles/ Directory: Rejected as it would create duplication
- Separate Design Package: Rejected as overkill for current needs
- Documentation-Only: Rejected as React components need the styles
ADR-018: Error Handling with TabulaLensError
Section titled “ADR-018: Error Handling with TabulaLensError”Status
Section titled “Status”Accepted
Context
Section titled “Context”Tabula Lens needed a consistent error handling approach that would:
- Provide clear error messages
- Include error context
- Support error codes
- Enable proper error handling in applications
- Distinguish between different error types
Decision
Section titled “Decision”Implement a custom TabulaLensError class with:
- Error codes for different error types
- Detailed error messages
- Error context and metadata
- Stack trace preservation
- Consistent error structure
Consequences
Section titled “Consequences”Positive:
- Consistency: Consistent error structure across the system
- Debugging: Better error information for debugging
- Handling: Easier to handle different error types
- Context: Error context helps understand issues
- Codes: Error codes enable programmatic handling
Negative:
- Complexity: Custom error class adds complexity
- Learning: Users need to understand error structure
- Maintenance: Error codes and messages need maintenance
Alternatives Considered
Section titled “Alternatives Considered”- Standard Error Objects: Rejected as insufficient context
- Third-Party Error Library: Rejected as overkill
- No Structured Errors: Rejected as it would make handling difficult
ADR Template
Section titled “ADR Template”When creating new ADRs, use this template:
## ADR-XXX: [Decision Title]
### Status[Proposed | Accepted | Deprecated | Superseded]
### Context[Describe the context and problem statement]
### Decision[Describe the decision that was made]
### Consequences[Describe the consequences, both positive and negative]
### Alternatives Considered[List and describe alternatives that were considered]Contributing ADRs
Section titled “Contributing ADRs”To contribute a new ADR:
- Create a new ADR using the template above
- Discuss with the team
- Update the status to “Accepted” once approved
- Reference the ADR in relevant code and documentation
- Review and update ADRs as the system evolves
Next Steps
Section titled “Next Steps”- Review existing ADRs to understand architectural decisions
- Propose new ADRs for significant architectural changes
- Update existing ADRs as the system evolves
- Reference ADRs in code comments and documentation
- Use ADRs to guide future architectural decisions