Reference solution
The findings below are ordered by priority.
Findings
1. Ordinary queries create and leak a new pool each time
Location: src/database.ts:12-15
query calls the factory directly instead of using getPool, and the returned pool is never ended or retained. Steady request traffic can create unbounded connection pools while transactions use a different one.
Route every operation through the instance's one shared pool lifecycle.
2. Concurrent first transactions can create multiple pools
Location: src/database.ts:39-44
The field remains undefined while factory.create() is pending. Two first callers can both pass the check, create separate pools, and overwrite one reference, leaking the losing pool.
Memoize the in-flight creation promise, clear it after a creation failure so later use may retry, and publish only one resolved pool.
3. Transaction clients leak on failure paths
Location: src/database.ts:17-30
release() runs only after a successful commit. Work failure and rollback success both skip it, and failures from BEGIN, COMMIT, or ROLLBACK also escape without release. Enough failures will exhaust the pool.
Place exactly-once release in a finally covering every step after checkout, while preserving the relevant transaction error when rollback also fails.
4. Shutdown neither closes admission nor waits for accepted work
Location: src/database.ts:32-36
close simply ends the current pool. A transaction may still own a client, another call can enter concurrently, repeated close calls end the pool repeatedly, and query calls can keep creating entirely new pools afterward.
Introduce an atomic closing state, reject new admission, track and join accepted operations, and memoize one close operation that ends the shared pool only after the tracker drains.
Reasonable non-findings
- Lazy creation is allowed and avoids connecting when
close occurs before first use.
- Explicit
BEGIN, COMMIT, and ROLLBACK calls are acceptable with correct cleanup.
- Letting the original operation error reject is correct; the lifecycle wrapper need not translate it.