Refactor database connection management to auto-close based on usage

Resolved 💬 2 comments Opened Jan 18, 2026 by jodysalt Closed Jan 18, 2026

Current Problem

The database client in @pinstripe/database uses a module-level connectionCounter that requires manual destroy() calls. If any context forgets to destroy, or accesses the database outside a workspace, connections stay open indefinitely.

This caused an issue where reset_database_from_sql called destroy() but the connection stayed open because another context (the start_server command) had also accessed the database.

Proposed Solution

Replace reference counting with usage-based auto-close:

  • Track active locks, transactions, and in-flight queries
  • When all are 0, automatically close connection (with short debounce)
  • Reconnect lazily on next query

Implementation Sketch

// packages/@pinstripe/database/lib/client.js

let connection;
let activeOperations = 0;  // locks + transactions + queries
let closeTimeout;

async run(query, options = {}){
    clearTimeout(closeTimeout);
    activeOperations++;
    try {
        if(!connection) await this.connect();
        // ... execute query
    } finally {
        activeOperations--;
        this.scheduleClose();
    }
}

scheduleClose(){
    if(activeOperations > 0) return;
    clearTimeout(closeTimeout);
    closeTimeout = setTimeout(() => {
        if(activeOperations === 0 && connection) {
            this.closeConnection();
        }
    }, 100);  // debounce delay TBD
}

Changes Needed

  1. run() - increment before, decrement after (in finally block)
  2. lock() - increment/decrement around lock operations
  3. transaction() - increment/decrement around transaction
  4. Remove or deprecate destroy() method
  5. Handle reconnection in run() if connection was auto-closed

Open Questions

  1. What debounce delay? (0ms, 100ms, 1000ms?)
  2. Should destroy() be kept for explicit cleanup, or removed entirely?
  3. Any concern about connection churn in high-throughput scenarios?

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗