kysely-org/kysely
A type-safe TypeScript SQL query builder
Generates type-safe SQL queries from TypeScript code with dialect-specific compilation
Kysely processes queries through three distinct phases: First, developers use fluent TypeScript APIs (SelectQueryBuilder, InsertQueryBuilder, etc.) which create an abstract syntax tree of OperationNode objects capturing query structure without database-specific syntax. Next, the DefaultQueryCompiler traverses this AST using dialect-specific visitors to generate SQL strings with parameter placeholders. Finally, the DefaultQueryExecutor acquires database connections, binds parameters, executes the SQL, and maps results back to TypeScript types based on the original schema definitions.
Under the hood, the system uses 2 feedback loops, 2 data pools, 3 control points to manage its runtime behavior.
A 10-component library. 471 files analyzed. Data flows through 4 distinct pipeline stages.
How Data Flows Through the System
Kysely processes queries through three distinct phases: First, developers use fluent TypeScript APIs (SelectQueryBuilder, InsertQueryBuilder, etc.) which create an abstract syntax tree of OperationNode objects capturing query structure without database-specific syntax. Next, the DefaultQueryCompiler traverses this AST using dialect-specific visitors to generate SQL strings with parameter placeholders. Finally, the DefaultQueryExecutor acquires database connections, binds parameters, executes the SQL, and maps results back to TypeScript types based on the original schema definitions.
- Query Construction — Developer chains method calls on QueryBuilder instances (db.selectFrom('users').select(['name', 'email']).where('active', '=', true)) which progressively build OperationNode trees while TypeScript tracks column types and table relationships [DatabaseSchema types → OperationNode]
- AST Compilation — DefaultQueryCompiler.compileQuery() walks the OperationNode tree using visitSelectQuery, visitWhereClause, etc. methods that delegate to dialect-specific implementations to generate SQL strings with parameter placeholders [OperationNode → CompiledQuery]
- Query Execution — DefaultQueryExecutor.executeQuery() acquires a connection from the connection provider, calls connection.executeQuery() with the compiled SQL and parameters, then processes the raw database results [CompiledQuery → QueryResult]
- Result Transformation — QueryResult.rows are mapped back to TypeScript types using the original schema information captured during query building, ensuring type safety end-to-end [QueryResult → typed result objects]
Data Models
The data structures that flow between stages — the contracts that hold the system together.
src/operation-node/operation-node.tsunion type with kind discriminator — SelectQueryNode, InsertQueryNode, UpdateQueryNode etc. each containing arrays of child nodes representing clauses
Created during query building, transformed by visitors during compilation, consumed by dialect-specific code generators
src/query-compiler/compiled-query.tsobject with sql: string, parameters: readonly unknown[], query: OperationNode
Generated by query compiler from operation nodes, passed to database drivers for execution
src/util/type-utils.tsgeneric type mapping table names to column definitions — Record<string, Record<string, ColumnType<SelectType, InsertType, UpdateType>>>
Defined by users to describe their database structure, flows through TypeScript's type system to provide compile-time validation
src/driver/database-connection.tsobject with rows: unknown[], numAffectedRows?: bigint | number, insertId?: bigint | number
Returned by database drivers, transformed into type-safe results based on schema definitions
src/dialect/dialect-adapter.tsinterface with methods createDriver(), createQueryCompiler(), createIntrospector() returning database-specific implementations
Instantiated during Kysely initialization, used throughout query lifecycle to provide database-specific behavior
Hidden Assumptions
Things this code relies on but never validates. These are the things that cause silent failures when the system changes.
Connection pools maintain healthy connections indefinitely without timeouts or connection limits exhaustion — acquireConnection() waits infinitely for an available connection
If this fails: If connection pool is exhausted or connections become stale, queries hang forever without timeout or error, causing application deadlock
src/driver/default-connection-provider.ts:DefaultConnectionProvider
Database drivers return QueryResult.rows as unknown[] where each row matches the expected TypeScript schema types, but no runtime validation occurs between database values and TypeScript types
If this fails: When database schema changes or returns unexpected types (NULL for NOT NULL columns, strings for numbers), Kysely returns incorrectly typed data that passes TypeScript checks but fails at runtime
src/query-executor/default-query-executor.ts:DefaultQueryExecutor.executeQuery
Pool configuration from user contains valid PostgreSQL connection parameters and the pool factory function always returns a connected pg.Pool instance
If this fails: Invalid connection configs or network issues cause cryptic pg.Pool errors that bubble up without context, making it unclear whether the issue is configuration, network, or database
src/dialect/postgres/postgres-dialect.ts:PostgresDialect.createDriver
Visitor pattern traverses OperationNode trees in dependency-correct order where referenced nodes (tables, columns) are visited before dependent nodes (expressions, conditions) that use them
If this fails: If traversal order is wrong, SQL generation produces invalid queries with undefined table aliases or column references, especially in complex joins and subqueries
src/operation-node/operation-node-visitor.ts:OperationNodeVisitor
Compiled query cache entries remain valid for the lifetime of the Kysely instance — cached SQL never becomes stale due to schema changes or dialect updates
If this fails: After database schema changes (column renames, table drops), cached queries contain outdated SQL that executes against non-existent objects, causing runtime SQL errors
src/query-compiler/default-query-compiler.ts:DefaultQueryCompiler
TypeScript's type system can handle arbitrarily complex nested generic types from chained query operations like selectFrom().join().where().select() without hitting compiler limits
If this fails: Extremely complex queries with many joins and subqueries cause TypeScript compilation to hang or fail with 'Type instantiation is excessively deep' errors
src/query-builder/select-query-builder.ts:SelectQueryBuilder
Parameter binding uses 1-based indexing ($1, $2, $3) and parameters array indices match PostgreSQL's parameter placeholder numbering exactly
If this fails: Off-by-one errors in parameter binding cause queries to use wrong values — $1 gets parameters[1] instead of parameters[0], leading to data corruption or query failures
src/dialect/postgres/postgres-query-compiler.ts:PostgresQueryCompiler
Database configuration from Config contains valid PostgreSQL connection parameters and the target database exists and is accessible when Pool is created
If this fails: Missing database, wrong credentials, or network issues cause pg.Pool creation to fail with connection errors that don't distinguish between configuration vs infrastructure problems
example/src/app.ts:App.constructor
All dialect-specific DatabaseConnection implementations handle parameter binding consistently and return QueryResult with the same structure — rows, numAffectedRows, insertId have identical semantics across PostgreSQL, MySQL, SQLite
If this fails: Switching dialects breaks applications that depend on specific result formats — MySQL returns insertId as number but PostgreSQL might return bigint, causing type mismatches
src/driver/database-connection.ts:DatabaseConnection.executeQuery
Memory usage scales linearly with query complexity and result set size — large result sets or deeply nested OperationNode trees don't cause exponential memory growth during compilation or execution
If this fails: Queries returning millions of rows or with hundreds of joins consume excessive memory, potentially causing out-of-memory crashes in production
src/query-executor/default-query-executor.ts:DefaultQueryExecutor
System Behavior
How the system operates at runtime — where data accumulates, what loops, what waits, and what controls what.
Data Pools
Database connections maintained by driver-specific pools (pg.Pool, mysql.createPool) to avoid connection overhead
Compiled query cache that avoids re-parsing identical operation node structures
Feedback Loops
- Transaction Retry (retry, balancing) — Trigger: Transaction serialization failure or deadlock detection. Action: DefaultQueryExecutor.executeQuery() catches database errors and re-executes the entire transaction block. Exit: Transaction succeeds or max retry count exceeded.
- Connection Recovery (circuit-breaker, balancing) — Trigger: Database connection failure or timeout. Action: Connection providers mark connections as failed and attempt to establish new ones. Exit: Connection successfully established or permanent failure declared.
Delays
- Connection Acquisition (async-processing, ~Variable based on pool availability) — Query execution waits for available connection from pool before proceeding
- Compilation Caching (cache-ttl) — First execution of query pattern incurs compilation overhead, subsequent executions use cached result
Control Points
- Dialect Selection (architecture-switch) — Controls: Which SQL syntax, query compiler, and database driver are used for all operations
- Connection Pool Size (runtime-toggle) — Controls: Maximum concurrent database connections and query throughput capacity. Default: Driver-dependent defaults
- Query Logging (feature-flag) — Controls: Whether compiled SQL queries and parameters are logged for debugging
Technology Stack
Provides type safety and compile-time SQL validation through advanced type system features
Primary runtime environment with support for multiple database drivers
Alternative JavaScript runtime supported through ESM and zero-dependency architecture
Supported database with dialect providing Postgres-specific SQL generation and type mapping
Supported database with dialect handling MySQL syntax variations and connection pooling
Supported database for embedded use cases with file-based storage
Test runner for comprehensive test suite covering multiple runtime environments
Fast TypeScript compilation and bundling for distribution builds
Key Components
- Kysely (orchestrator) — Main entry point that coordinates query building, compilation, and execution while maintaining transaction boundaries and connection management
src/kysely.ts - SelectQueryBuilder (processor) — Fluent API for building SELECT queries with progressive type narrowing that tracks selected columns, joins, and filtering
src/query-builder/select-query-builder.ts - DefaultQueryCompiler (transformer) — Converts operation node AST into SQL strings using visitor pattern traversal with dialect-specific customization points
src/query-compiler/default-query-compiler.ts - OperationNodeVisitor (processor) — Base visitor class that traverses operation node trees, allowing dialects to override specific node handling for SQL generation
src/operation-node/operation-node-visitor.ts - PostgresDialect (adapter) — PostgreSQL-specific implementation providing query compiler, introspector, and driver integration for Postgres syntax and features
src/dialect/postgres/postgres-dialect.ts - DefaultQueryExecutor (executor) — Manages query execution lifecycle including connection acquisition, parameter binding, result streaming, and transaction coordination
src/query-executor/default-query-executor.ts - QueryBuilder (factory) — Base class for all query builders providing common functionality like raw SQL integration and operation node management
src/query-builder/query-builder.ts - SchemaBuilder (processor) — Provides DDL operations for creating, altering, and dropping database schema objects with type-safe column definitions
src/schema/schema.ts - DatabaseConnection (gateway) — Abstract interface over actual database connections providing consistent API for query execution across different drivers
src/driver/database-connection.ts - ExpressionBuilder (processor) — Creates type-safe SQL expressions, function calls, and operators while maintaining column type information through TypeScript generics
src/expression/expression-builder.ts
Package Structure
The main Kysely type-safe SQL query builder library with comprehensive dialect support and schema management capabilities.
Documentation website built with Docusaurus showcasing features, examples, and getting started guides.
Test suite verifying Kysely compatibility with Cloudflare Workers runtime environment.
Explore the interactive analysis
See the full architecture map, data flow, and code patterns visualization.
Analyze on CodeSeaRelated Library Repositories
Frequently Asked Questions
What is kysely used for?
Generates type-safe SQL queries from TypeScript code with dialect-specific compilation kysely-org/kysely is a 10-component library written in TypeScript. Data flows through 4 distinct pipeline stages. The codebase contains 471 files.
How is kysely architected?
kysely is organized into 5 architecture layers: Query Builder API, Operation Node System, Query Compilation, Database Dialects, and 1 more. Data flows through 4 distinct pipeline stages. This layered structure keeps concerns separated and modules independent.
How does data flow through kysely?
Data moves through 4 stages: Query Construction → AST Compilation → Query Execution → Result Transformation. Kysely processes queries through three distinct phases: First, developers use fluent TypeScript APIs (SelectQueryBuilder, InsertQueryBuilder, etc.) which create an abstract syntax tree of OperationNode objects capturing query structure without database-specific syntax. Next, the DefaultQueryCompiler traverses this AST using dialect-specific visitors to generate SQL strings with parameter placeholders. Finally, the DefaultQueryExecutor acquires database connections, binds parameters, executes the SQL, and maps results back to TypeScript types based on the original schema definitions. This pipeline design keeps the data transformation process straightforward.
What technologies does kysely use?
The core stack includes TypeScript (Provides type safety and compile-time SQL validation through advanced type system features), Node.js (Primary runtime environment with support for multiple database drivers), Deno (Alternative JavaScript runtime supported through ESM and zero-dependency architecture), PostgreSQL (Supported database with dialect providing Postgres-specific SQL generation and type mapping), MySQL (Supported database with dialect handling MySQL syntax variations and connection pooling), SQLite (Supported database for embedded use cases with file-based storage), and 2 more. A focused set of dependencies that keeps the build manageable.
What system dynamics does kysely have?
kysely exhibits 2 data pools (Connection Pool, Query Cache), 2 feedback loops, 3 control points, 2 delays. The feedback loops handle retry and circuit-breaker. These runtime behaviors shape how the system responds to load, failures, and configuration changes.
What design patterns does kysely use?
4 design patterns detected: Fluent Builder Pattern, Visitor Pattern, Plugin Architecture, Type-Level Programming.
Analyzed on April 20, 2026 by CodeSea. Written by Karolina Sarna.