Hidden Assumptions in kysely

13 assumptions this code never checks · 4 critical · spanning Resource, Shape, Contract, Ordering, Temporal, Scale, Domain, Environment

Every codebase relies on things it never checks. Most of them are routine. CodeSea looked at kysely-org/kysely and picked out the few most likely to cause trouble. The full list is just below.

Most of what this code assumes is routine. These 3 are the ones most likely to cause trouble here. The rest are minor; they're under "Show everything".

Worth your attention first

If connection pool is exhausted or connections become stale, queries hang forever without timeout or error, causing application deadlock

Worth your attention first

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

Worth your attention first

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

Show everything (10 more)
Ordering

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
Temporal

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
Scale

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
Domain

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
Environment

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
Contract

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
Resource

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
Temporal

Database connection cleanup happens synchronously after this.#db.destroy() resolves — no queries are still executing or connections still active

If this fails: If queries are still running when destroy() is called, they might complete with connection errors or leave connections open, causing resource leaks

example/src/app.ts:App.stop
Shape

User-provided DatabaseSchema types accurately reflect actual database structure — table names, column names, and types match what exists in the database at runtime

If this fails: Outdated schema types cause TypeScript to allow queries against non-existent columns or tables, leading to runtime SQL errors that could have been caught at compile time

src/util/type-utils.ts:DatabaseSchema
Domain

SQL expressions created through ExpressionBuilder methods generate valid SQL for the target dialect — function calls, operators, and syntax variations are correctly translated

If this fails: Using PostgreSQL-specific functions in a MySQL dialect or vice versa generates invalid SQL that fails at execution time with database-specific error messages

src/expression/expression-builder.ts:ExpressionBuilder

See the full structural analysis of kysely: the pipeline, data models, and system behavior that put these assumptions in context.

Full analysis of kysely-org/kysely →

Frequently Asked Questions

What does kysely assume that could break in production?

The one most likely to cause trouble: 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

How many hidden assumptions does kysely have?

CodeSea found 13 assumptions kysely relies on but never validates, 4 of them critical, spanning Resource, Shape, Contract, Ordering, Temporal, Scale, Domain, Environment. Most are routine — the analysis flags the two or three most likely to actually bite.

What is a hidden assumption?

Something the code depends on but never checks: a data shape, an ordering, an environment condition, a scale limit, or a contract with another service. It holds until the world it runs in changes, then fails silently.