Date Generated: May 10, 2026 Project Type: TypeScript / Node.js (Bun) Library Architecture Pattern: Data Access Abstraction Layer / Middleware-Driven Repository
This document serves as a definitive reference for maintaining architectural consistency within the drizzle-castor codebase. It outlines the foundational design decisions, component boundaries, and implementation patterns used to build this robust, type-safe CRUD wrapper for Drizzle ORM.
The project is a TypeScript-based library built with Bun, acting as a high-level wrapper and Data Access Object (DAO) abstraction over drizzle-orm. The architectural pattern relies heavily on:
- Modular Repositories: Dynamically generated through a factory (
repoFactory). - Middleware Pipeline: An extensible pipeline handling security (Unified RBAC) and cross-cutting concerns around all database actions.
- AST Parsing and Query Building: Abstracted filtering (
QueryParser,AST Compiler) mapped down to Drizzle's SQL operators.
The repository is organized into distinct domain boundaries to separate concerns between the public API, context execution, AST translation, and database dialect management.
├───example/ # Playground and edge-case testing area
├───src/ # Main library source code
│ ├───context/ # Execution context manager (Thread-local state)
│ ├───errors/ # Custom error classes (Security, Parsing, Mutation)
│ ├───helper/ # Shared utilities (Dialect, Assertions, Types, Logger)
│ │ ├───logger-helper.ts # Pattern-based logging powered by Pino
│ │ ├───soft-delete-helper.ts# Declarative soft-delete injection
│ │ └───...
│ ├───middleware/ # Koa-style pipeline and Unified RBAC engine
│ ├───mutations/ # Physical executors for INSERT, UPDATE, DELETE
│ ├───queries/ # Physical executors for SELECT and Hydration
│ ├───query-parser/ # AST Translators (JSON -> Drizzle SQL)
│ │ ├───alias-manager.ts # Handles SQL aliasing for JOINs
│ │ ├───ast-compiler.ts # Translates filter/order/select payloads
│ │ ├───filter-builder.ts # Recursive $and/$or operator evaluator
│ │ └───json-resolver.ts # Dialect-specific JSON path handling
│ ├───types/ # TypeScript interfaces and global definitions
│ ├───schema-metadata.ts # Repository factory implementation
│ └───index.ts # Public API exports
└───tests/ # Test suites
├───integration/ # Dialect-specific testing (PG, MySQL, SQLite)
└───unit/ # Isolated module testing via Bun
drizzle-castor provides a structured, middleware-augmented Repository Pattern on top of Drizzle ORM.
Guiding Principles:
- Type Safety: Leverage TypeScript generics to provide end-to-end type safety from database schema to query results.
- Extensibility: Expose a generic middleware execution pipeline around all DB interactions.
- Security by Default: Provide a built-in Unified RBAC (Role-Based Access Control) to gatekeep actions inherently at the repository layer.
- Dialect Agnosticism: Abstract operations to work across PostgreSQL, MySQL, and SQLite safely (e.g., using specific returning strategies vs. temporary tables fallback for race conditions).
(Textual representation of component flow)
[ Application Logic ]
│
▼
[ Repository Interface ] (createOne, searchMany, etc.)
│
▼
[ Middleware Pipeline ] (Execution Context, RBAC, Custom Plugins)
│
▼
[ Executor Engine ] (single-executor, batch-executor)
│ (Uses Query Parser for AST compilation)
▼
[ Drizzle ORM ] -> [ Database ]
- Purpose: Acts as the root bootstrap mechanism that binds the Drizzle instance and table schemas into a factory capable of producing bound Repositories.
- Internal Structure: Instantiates global context, registers global middleware, initializes policies, and returns the
repoFactory.
- Purpose: Creates type-safe CRUD interfaces tailored to a specific base table.
- Interaction Patterns: Routes generic method calls (e.g.,
searchOne,updateOne) through the composedMiddlewarepipeline into specific executor functions.
- Purpose: Encapsulates the entire scope of a single database transaction/request, carrying context variables, execution parameters, translator configurations, and telemetry hooks.
- Purpose: Translates high-level, JSON-like query syntax (e.g., nested
$and,$or, relationship inclusions) into raw Drizzle SQL statements and join configurations. - Interaction Patterns: Extensively used by executors before executing physical queries. Handles aliasing, json extraction, and soft-delete conditionals dynamically.
- Purpose: The final physical layer that interacts with
drizzle-ormfunctions. - Evolution Patterns: Contains highly specialized strategies depending on database dialect support (e.g., using
RETURNINGclauses in PG/SQLite vs. temporary tables in MySQL for transaction safety).
- API Layer:
index.ts, exporting types and factory functions. - Context & Middleware Layer: Wraps executions, validates RBAC.
- AST Translation Layer: Converts abstract operations into concrete Drizzle constructs.
- Execution Layer: Runs the actual Drizzle ORM queries and hydrates the results.
Dependency Rule: Outer layers (Middlewares) can access Context and Executors, but Executors should not rely on Middleware logic. AST Translators act as pure functions utilized by Executors.
- Data Mapping: Abstract filters and projections are mapped to physical tables using
schema-metadata. - Relationships: Automatically resolved via nested path expressions, converting conceptual relationships into SQL
JOINs on the fly. - Soft Deletion: Baked directly into AST generation. When relations or base queries are built, active soft-delete filters are injected implicitly.
- Implementation: Handled entirely via
createUnifiedRbacMiddleware. Policies are defined on a per-table or global basis throughSchemaBuilder.policies(). - Pattern: Middleware intercepts the action type (
create,read,update, etc.) and the user profile, querying the policy definition before allowing the executor to run. - Hybrid Support: Supports both Declarative maps (Profile -> Config) and Imperative callbacks (Async switches based on table/action/context).
- Pattern: Middleware intercepts the action type and user profile, resolving the dynamic or static policy definition. It performs intelligent data trimming for filters, projections, and mutation sets.
- Implementation: Specific error classes (
MutationError,QueryParsingError,SecurityError). - Pattern: Thrown internally and optionally caught or propagated. Strict race-condition protection is applied using transaction-bound Temporary Tables (MySQL) or
RETURNINGclauses.
- Technology Stack:
- Logging: Powered by
pinowith a customPatternFormatter. - Telemetry: Event-driven using
mitt.
- Logging: Powered by
- Approach:
- Internal Logging: Configurable via
builder.withLogger(). Supports patterns (e.g.,%d %p [%c] (%t) %s) and context injection (extracting traceId or nested operation parameters). - External Telemetry: Emits structured events via an asynchronous event bus.
- Internal Logging: Configurable via
- Event Categories:
execution: Performance tracking, trace IDs, and result status.security: RBAC field trimming and access denial audits.error: Centralized failure reporting for the entire pipeline.soft-deleted/restored/hard-deleted: Tracking physical data changes for audit logs.
- Observability: Every operation is wrapped in an
ExecutionContextthat propagates a uniquetraceIdthrough all logs and events.
As a library, communication is primarily synchronous function invocation. Asynchronous patterns are utilized exclusively for physical database I/O (Promises over Drizzle async executors).
- Module Organization: Modular directories (
mutations,queries,middleware,query-parser). - Dependency Injection: Context is passed explicitly via
ExecutionContextrather than using complex DI frameworks, maintaining high performance and clear execution traces.
- Service Implementation: The
repoFactoryacts as a proxy/facade exposing strongly typed methods that delegate to standalone executors (executeCreateOne,executeSearchMany). - Interface Design: High reliance on type inference and complex TypeScript generics to ensure that query parameters (filters, select fields) match the underlying Drizzle schema exactly.
- Concurrency Handling: Managed at the dialect level. Specific
single-executor.tsstrategy ensures that updates/deletes securely capture the modified rows without race conditions.
- Strategy:
- Integration Tests: Grouped by dialect (
postgresql,mysql,sqlite) usingtestcontainersto ensure AST logic maps correctly to real DB constraints. - Unit Tests: Grouped by internal modules (
query-parser,middleware,errors) run natively via Bun's isolated test runner.
- Integration Tests: Grouped by dialect (
- Packaging: Distributed as an NPM module. Compiled using
tscand bundled usingtsup. Supports ESM and CJS natively through robustpackage.jsonexport mapping.
- Feature Addition: New AST operators or filters should be added purely within
src/query-parser/operator-builder.tsorfilter-builder.ts. - Middleware Integration: Consumers can inject custom middleware through
defineSchemaMetadatato handle multi-tenant isolation, specific auditing, or custom logging without modifying core library code.
// composeMiddleware iterates through layers, maintaining 'ctx' flow.
const pipeline = composeMiddleware([
...applicableMiddlewares,
unifiedRbacMiddleware
]);
const result = await pipeline(ctx, async () => {
return coreFn(ctx);
});return runInContext({ action, tableName, profile, params, translatorContext }, async () => {
const ctx = useExecutionContext();
return executeSearchOne(ctx);
});- ADR: Strict Separation of Dialects:
- Context: MySQL lacks robust
RETURNINGsupport in all required contexts. - Decision: Adopt a bifurcated mutation execution strategy (
executeSingleMutation), using pureRETURNINGfor PG/SQLite and Temporary Tables for MySQL. - Consequences: Guarantees data consistency across dialects at the cost of slightly higher complexity in the executor module.
- Context: MySQL lacks robust
- ADR: Middleware Over Direct Hooks:
- Context: Users needed ways to hook into CRUD lifecycles.
- Decision: Adopted an Express-like middleware pattern (Koa/Express style
(ctx, next) => ...) wrapped around the executor.