Skip to content

Clarify afterCreate Usage for SQLite Dialects #582

Description

@mercmobily

Users are confused about the differences in afterCreate pool callback implementation between sqlite3 and better-sqlite3 drivers. Issue 4971 shows users copying sqlite3 examples that crash with better-sqlite3 due to different APIs.

Current Documentation Gaps

1. No Clear Differentiation

The documentation shows a generic PostgreSQL example but doesn't clarify the differences between SQLite drivers:

  • sqlite3 uses callback-style APIs (.run(), .exec() with callbacks)
  • better-sqlite3 uses synchronous APIs (.pragma(), .prepare(), no callbacks)

2. Missing Common Use Cases

No examples for the most common SQLite configurations:

  • Enabling foreign keys
  • Setting journal mode
  • Configuring busy timeout
  • Setting cache size

3. Incorrect Test Examples

Even the test files show inconsistent patterns:

  • /test/knexfile.js:31 uses connection.run('PRAGMA foreign_keys = ON', callback) for sqlite3
  • /test/knexfile.js:41 uses connection.prepare('PRAGMA foreign_keys = ON').run() for better-sqlite3

Documentation Changes Required

1. Update Main Pool Documentation (docs/src/guide/index.md)

Add a new section after the current afterCreate example (line 387):

#### Driver-Specific afterCreate Examples

##### PostgreSQL / MySQL
```js
const knex = require('knex')({
  client: 'pg', // or 'mysql', 'mysql2'
  connection: {...},
  pool: {
    afterCreate: function (conn, done) {
      // Callback-based API
      conn.query('SET timezone="UTC";', function (err) {
        if (err) {
          // Connection will be discarded
          done(err, conn);
        } else {
          // Connection is ready
          done(null, conn);
        }
      });
    }
  }
});
SQLite3 (callback-based)
const knex = require('knex')({
  client: 'sqlite3',
  connection: {
    filename: './mydb.sqlite'
  },
  pool: {
    afterCreate: function (conn, done) {
      // sqlite3 uses callback-style methods
      conn.run('PRAGMA foreign_keys = ON', done);
    }
  }
});
Better-SQLite3 (synchronous)
const knex = require('knex')({
  client: 'better-sqlite3',
  connection: {
    filename: './mydb.sqlite'
  },
  pool: {
    afterCreate: function (conn, done) {
      // better-sqlite3 uses synchronous methods
      try {
        conn.pragma('foreign_keys = ON');
        // For other PRAGMA statements:
        // conn.pragma('journal_mode = WAL');
        // conn.pragma('busy_timeout = 5000');
        done(null, conn);
      } catch (err) {
        done(err, conn);
      }
    }
  }
});
Common SQLite Configurations
// For sqlite3
pool: {
  afterCreate: function (conn, done) {
    conn.serialize(function() {
      conn.run('PRAGMA foreign_keys = ON');
      conn.run('PRAGMA journal_mode = WAL');
      conn.run('PRAGMA synchronous = NORMAL');
      conn.run('PRAGMA cache_size = -64000');
      conn.run('PRAGMA busy_timeout = 5000', done);
    });
  }
}

// For better-sqlite3
pool: {
  afterCreate: function (conn, done) {
    try {
      conn.pragma('foreign_keys = ON');
      conn.pragma('journal_mode = WAL');
      conn.pragma('synchronous = NORMAL');
      conn.pragma('cache_size = -64000');
      conn.pragma('busy_timeout = 5000');
      done(null, conn);
    } catch (err) {
      done(err, conn);
    }
  }
}

2. Add Warning Box

Add after the driver examples:

:::warning SQLite Driver Differences
**Important:** `sqlite3` and `better-sqlite3` have different APIs:
- **sqlite3**: Uses Node.js callback-style async methods (`.run()`, `.exec()`, `.all()`)
- **better-sqlite3**: Uses synchronous methods (`.pragma()`, `.prepare()`, `.exec()`)

Copying `afterCreate` code between these drivers will cause errors. Always use the appropriate API for your chosen driver.
:::

3. Update SQLite Section

In the SQLite configuration section (around line 90-160), add:

#### Choosing Between sqlite3 and better-sqlite3

| Feature | sqlite3 | better-sqlite3 |
|---------|---------|----------------|
| API Style | Asynchronous (callbacks/promises) | Synchronous |
| Performance | Good for concurrent operations | Faster for sequential operations |
| afterCreate | Uses callbacks | Synchronous, but still needs done() callback |
| Common Methods | `.run()`, `.get()`, `.all()` | `.prepare()`, `.pragma()` |
| Foreign Keys | `conn.run('PRAGMA foreign_keys = ON', cb)` | `conn.pragma('foreign_keys = ON')` |

4. Update FAQ/Recipes (docs/src/faq/recipes.md)

Update the SQLCipher example (line 76) to show both variants:

#### SQLCipher with sqlite3
```js
const knex = require('knex')({
  client: 'sqlite3',
  connection: {
    filename: 'file:foobar.db?cipher=sqlcipher'
  },
  pool: {
    afterCreate: function (conn, done) {
      conn.run("PRAGMA KEY = 'secret'", done);
    }
  }
});

SQLCipher with better-sqlite3

const knex = require('knex')({
  client: 'better-sqlite3',
  connection: {
    filename: 'file:foobar.db?cipher=sqlcipher'
  },
  pool: {
    afterCreate: function (conn, done) {
      try {
        conn.pragma("KEY = 'secret'");
        done(null, conn);
      } catch (err) {
        done(err, conn);
      }
    }
  }
});

5. Add Migration Guide

Create a new section in the documentation:

## Migrating from sqlite3 to better-sqlite3

If you're switching from `sqlite3` to `better-sqlite3`, update your `afterCreate`:

**Before (sqlite3):**
```js
afterCreate: function (conn, done) {
  conn.run('PRAGMA foreign_keys = ON', done);
}

After (better-sqlite3):

afterCreate: function (conn, done) {
  try {
    conn.pragma('foreign_keys = ON');
    done(null, conn);
  } catch (err) {
    done(err, conn);
  }
}

Additional Recommendations

  1. Add TypeScript definitions that show the different connection types for each driver
  2. Create a troubleshooting section for common errors like "conn.run is not a function"
  3. Add links to driver documentation:

Success Criteria

After implementing these changes:

  1. Users should immediately understand which code to use for their chosen SQLite driver
  2. Copy-paste errors between drivers should be eliminated
  3. Common PRAGMA configurations should be easily discoverable
  4. The error "conn.run is not a function" should lead users directly to the solution

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions